/// <summary> /// Executes an asynchronous request to the given resource and deserializes the response to an object of T. /// </summary> /// <typeparam name="T">The type to deserialize to.</typeparam> /// <param name="restRequest">The RestRequest to execute.</param> /// <returns>An object of T.</returns> public async Task <T> ExecuteAsync <T>(RestRequest restRequest) where T : class { T result = null; var url = restRequest.GetFormattedResource(BaseUrl); if (string.IsNullOrWhiteSpace(restRequest.DateFormat) && !string.IsNullOrWhiteSpace(DateFormat)) { restRequest.DateFormat = DateFormat; } var handler = new CompressedHttpClientHandler { AllowAutoRedirect = true }; _client = new HttpClient(handler); if (!string.IsNullOrWhiteSpace(UserAgent)) { _client.DefaultRequestHeaders.Add("user-agent", UserAgent); } var message = new HttpRequestMessage(restRequest.Method, new Uri(restRequest.Resource, UriKind.RelativeOrAbsolute)); foreach (var header in Headers) { message.Headers.Add(header.Key, header.Value); } if (restRequest.Method == HttpMethod.Post || restRequest.Method == HttpMethod.Put) { var contentString = new StringContent(restRequest.GetRequestBody(), Encoding.UTF8, restRequest.GetContentType()); message.Content = contentString; } HttpResponseMessage response = null; response = await _client.SendAsync(message); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); //TODO: Handle Error if (response.Content.Headers.ContentType.MediaType == "application/xml") { // RWM: IDEA - The DataContractSerializer doesn't like attributes, but will handle everything else. // So instead of trying to mess with a double-conversion to JSON and then to the Object, we'll just turn the attributes // into elements, and sort the elements into alphabetical order so the DataContracterializer doesn't crap itself. // On post, use a C# attribute to specify if a property is an XML attribute, DataContractSerialize to XML, then // query the object for [XmlAttribute] attributes and move them from elements to attributes using code similar to below. // If the POST request requires the attributes in a certain order, oh well. Shouldn't have used PHP :P. XElement root = XElement.Parse(responseContent); XElement newRoot = (XElement)Transform(restRequest.IgnoreRootElement ? root.Descendants().First() : root, restRequest); using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(newRoot.ToString()))) { var settings = new XmlReaderSettings { IgnoreWhitespace = true }; using (var reader = XmlReader.Create(memoryStream, settings)) { try { var serializer = new DataContractSerializer(typeof(T)); result = serializer.ReadObject(reader) as T; } catch (SerializationException ex) { throw new PortableRestException(string.Format("The serializer failed on node '{0}'", reader.Name), reader.Name, ex); } } } } else { result = JsonConvert.DeserializeObject <T>(responseContent); } return(result); }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="restRequest"></param> /// <param name="cancellationToken"></param> /// <returns></returns> private async Task <HttpResponseMessage> GetHttpResponseMessage <T>([NotNull] RestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken)) { //RWM If we've specified a DateFormat for the Client, but not not the Request, pass it down. if (!string.IsNullOrWhiteSpace(DateFormat) && string.IsNullOrWhiteSpace(restRequest.DateFormat)) { restRequest.DateFormat = DateFormat; } //RWM: If we've specified JsonSerializerSettings for the Client, but not not the Request, pass it down. if (JsonSerializerSettings != null && restRequest.JsonSerializerSettings == null) { restRequest.JsonSerializerSettings = JsonSerializerSettings; } //RWM: We've moved this call to inside the HttpHandler setter... let's see if that solves our Mono problems. //ConfigureHandler(HttpHandler); _client = new HttpClient(HttpHandler); if (string.IsNullOrWhiteSpace(UserAgent)) { SetUserAgent <T>(); } _client.DefaultRequestHeaders.Add("user-agent", UserAgent); var message = new HttpRequestMessage(restRequest.Method, restRequest.GetResourceUri(BaseUrl)); //RWM: Add the global headers for all requests. foreach (var header in Headers) { message.Headers.Add(header.Key, header.Value); } //RWM: Add request-specific headers. foreach (var header in restRequest.Headers) { message.Headers.Add(header.Key, header.Value.ToString()); } //RWM: Not sure if this is sufficient, or if HEAD supports a body, will need to check into the RFC. if (restRequest.Method != HttpMethod.Get && restRequest.Method != HttpMethod.Head && restRequest.Method != HttpMethod.Trace) { //RWM: This feels hacky. May need some tweaking. if (restRequest.ContentType == ContentTypes.ByteArray) { //RWM: A fix for an issue uncovered by @scottisafool. if (restRequest.Parameters.Count > 0) { message.Content = new ByteArrayContent(restRequest.Parameters[0].GetEncodedValue() as byte[]); } } //RWM: This may not be the best place to keep this... might be better to refactor RestRequest.GetRequestBody to return a HttpContent object instead. else if (restRequest.ContentType == ContentTypes.MultiPartFormData) { var content = new MultipartFormDataContent(); foreach (var p in restRequest.Parameters) { if (p is FileParameter) { var fileParameter = p as FileParameter; if (string.IsNullOrEmpty(fileParameter.Filename)) { content.Add(new StreamContent(fileParameter.Value as Stream), fileParameter.Key); } else { content.Add(new StreamContent(fileParameter.Value as Stream), fileParameter.Key, fileParameter.Filename); } } else if (p.Encoding == ParameterEncoding.ByteArray) { content.Add(new ByteArrayContent(p.GetEncodedValue() as byte[])); } else { content.Add(new StringContent(p.GetEncodedValue().ToString()), p.Key); } } message.Content = content; } else { var contentString = new StringContent(restRequest.GetRequestBody(), Encoding.UTF8, restRequest.GetContentType()); message.Content = contentString; } } return(await _client.SendAsync(message, cancellationToken).ConfigureAwait(false)); }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="restRequest"></param> /// <param name="cancellationToken"></param> /// <returns></returns> private async Task <HttpResponseMessage> GetHttpResponseMessage <T>(RestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken)) { //RWM If we've specified a DateFormat for the Client, but not not the Request, pass it down. if (!string.IsNullOrWhiteSpace(DateFormat) && string.IsNullOrWhiteSpace(restRequest.DateFormat)) { restRequest.DateFormat = DateFormat; } //RWM If we've specified JsonSerializerSettings for the Client, but not not the Request, pass it down. if (JsonSerializerSettings != null && restRequest.JsonSerializerSettings == null) { restRequest.JsonSerializerSettings = JsonSerializerSettings; } ConfigureHandler(HttpHandler); _client = new HttpClient(HttpHandler); if (string.IsNullOrWhiteSpace(UserAgent)) { SetUserAgent <T>(); } _client.DefaultRequestHeaders.Add("user-agent", UserAgent); var message = new HttpRequestMessage(restRequest.Method, restRequest.GetResourceUri(BaseUrl)); //RWM: Add the global headers for all requests. foreach (var header in Headers) { message.Headers.Add(header.Key, header.Value); } //RWM: Add request-specific headers. foreach (var header in restRequest.Headers) { message.Headers.Add(header.Key, header.Value.ToString()); } //RWM: Not sure if this is sufficient, or if HEAD supports a body, will need to check into the RFC. if (restRequest.Method != HttpMethod.Get && restRequest.Method != HttpMethod.Head && restRequest.Method != HttpMethod.Trace) { //REM: This feels hacky. May need some tweaking. if (restRequest.ContentType == ContentTypes.ByteArray) { message.Content = new ByteArrayContent(restRequest.Parameters[0].GetEncodedValue() as byte[]); } else { var contentString = new StringContent(restRequest.GetRequestBody(), Encoding.UTF8, restRequest.GetContentType()); message.Content = contentString; } } return(await _client.SendAsync(message, cancellationToken).ConfigureAwait(false)); }