/// <summary> /// Process direct request with deserialized generic response. This method is called by all verb actions /// </summary> /// <typeparam name="T">Generic return type to deserialize response data in to</typeparam> /// <param name="options">RextOption to configure http call</param> /// <returns>Deserialized response of T</returns> public async Task <CustomHttpResponse <T> > MakeRequest <T>(RextOptions options) { var rsp = await ProcessRequest(options); var newRsp = new CustomHttpResponse <T> { StatusCode = rsp.StatusCode, Message = rsp.Message, Content = rsp.Content }; bool deserializeSuccessOnly = options?.DeserializeSuccessResponseOnly ?? ConfigurationBundle.HttpConfiguration.DeserializeSuccessResponseOnly; if (newRsp.StatusCode == System.Net.HttpStatusCode.OK || !deserializeSuccessOnly) { if (!string.IsNullOrEmpty(rsp.Content)) { bool throwExOnFail = options.ThrowExceptionOnDeserializationFailure ?? ConfigurationBundle.HttpConfiguration.ThrowExceptionOnDeserializationFailure; (bool status, string message, T result)output = (false, null, default(T)); if (options.ExpectedResponseFormat == ContentType.Application_JSON) { output = Helpers.DeserializeJSON <T>(rsp.Content, throwExOnFail); } else if (options.ExpectedResponseFormat == ContentType.Application_XML) { output = Helpers.DeserializeXML <T>(rsp.Content, throwExOnFail); } if (output.status) { newRsp.Data = output.result; } else { if (newRsp.StatusCode != System.Net.HttpStatusCode.OK && !deserializeSuccessOnly) { newRsp.Message = $"Type deserialization failed: To prevent deserialization of unsuccessful response types, set DeserializeSuccessResponseOnly=true"; if (throwExOnFail) { throw new RextException(newRsp.Message + " ---> To prevent deserialization of unsuccessful response types, set DeserializeSuccessResponseOnly = true"); } return(newRsp); } else { newRsp.Message = output.message; } } } } else { newRsp.Message += " --> To allow deserialization even when response status code is not successful, set DeserializeSuccessResponseOnly = false"; } return(newRsp); }
private async Task <CustomHttpResponse <string> > ProcessRequest(RextOptions options) { if (this.Client == null) { throw new ArgumentNullException("HttpClient object cannot be null"); } // validate essential params if (string.IsNullOrEmpty(options.Url)) { if (string.IsNullOrEmpty(ConfigurationBundle.HttpConfiguration.BaseUrl)) { throw new ArgumentNullException(nameof(options.Url), $"{nameof(options.Url)} is required. Provide fully qualified api endpoint."); } else { throw new ArgumentNullException(nameof(options.Url), $"{nameof(options.Url)} is required. Provide the other part of api endpoint to match the BaseUrl."); } } if (options.Method == null) { throw new ArgumentNullException(nameof(options.Method), $"{nameof(options.Method)} is required. Use GET, POST etc.."); } if (string.IsNullOrEmpty(options.ContentType)) { throw new ArgumentNullException(nameof(options.ContentType), $"{nameof(options.ContentType) } is required. Use application/json, text.plain etc.."); } if (string.IsNullOrEmpty(options.ExpectedResponseFormat)) { throw new ArgumentNullException(nameof(options.ExpectedResponseFormat), $"{nameof(options.ExpectedResponseFormat)} is required. Use application/json, text.plain etc.."); } // execute all user actions pre-call ConfigurationBundle.BeforeCall?.Invoke(); var rsp = new CustomHttpResponse <string>(); var response = new HttpResponseMessage(); string responseString = string.Empty; try { Uri uri = options.CreateUri(ConfigurationBundle.HttpConfiguration.BaseUrl); if (uri == null) { throw new UriFormatException("Invalid request Uri"); } var requestMsg = new HttpRequestMessage(options.Method, uri); // set header if object has value if (this.Headers != null) { requestMsg.SetHeader(this.Headers); } if (options.Header != null) { requestMsg.SetHeader(options.Header); } if (ConfigurationBundle.HttpConfiguration.Header != null) { requestMsg.SetHeader(ConfigurationBundle.HttpConfiguration.Header); } if (!string.IsNullOrEmpty(options.ContentType)) { requestMsg.SetHeader("Accept", options.ExpectedResponseFormat); } // POST request if (options.Method != HttpMethod.Get && options.Payload != null) { string strPayload = string.Empty; if (options.IsForm) { strPayload = options.Payload.ToQueryString()?.TrimStart('?'); // form-data content post if (!options.IsUrlEncoded) { // handle multipart/form-data var formData = strPayload.Split('&'); var mpfDataBucket = new MultipartFormDataContent(); foreach (var i in formData) { var row = i.Split('='); if (row.Length == 2) // check index to avoid null { mpfDataBucket.Add(new StringContent(row[1]), row[0]); } } requestMsg.Content = mpfDataBucket; } else { // handle application/x-www-form-urlencoded requestMsg.Content = new StringContent(strPayload, Encoding.UTF8, "application/x-www-form-urlencoded"); } } else { // convert object to specified content-type if (options.ContentType == ContentType.Application_JSON) { strPayload = options.Payload.ToJson(); } else if (options.ContentType == ContentType.Application_XML) { strPayload = options.Payload.ToXml(); } else { strPayload = options.Payload.ToString(); } requestMsg.Content = new StringContent(strPayload, Encoding.UTF8, options.ContentType); } } // use stopwatch to monitor httpcall duration if (ConfigurationBundle.EnableStopwatch) { Stopwatch = new Stopwatch(); Stopwatch.Start(); } // check if HttpCompletionOption option is used HttpCompletionOption httpCompletionOption = ConfigurationBundle.HttpConfiguration.HttpCompletionOption; if (options.HttpCompletionOption.HasValue) { if (ConfigurationBundle.HttpConfiguration.HttpCompletionOption != options.HttpCompletionOption.Value) { httpCompletionOption = options.HttpCompletionOption.Value; } } response = await this.Client.SendAsync(requestMsg, httpCompletionOption, CancellationToken.None).ConfigureAwait(false); // set watch value to public member if (ConfigurationBundle.EnableStopwatch) { Stopwatch.Stop(); } rsp.StatusCode = response.StatusCode; if (options.IsStreamResponse) { var stream = await response.Content.ReadAsStreamAsync(); if (stream.Length > 0) { using (var rd = new StreamReader(stream)) responseString = rd.ReadToEnd(); } } else { responseString = await response.Content.ReadAsStringAsync(); } if (response.IsSuccessStatusCode) { rsp.Content = responseString; rsp.Message = "Http call successful"; } else { // this will always run before custom error-code actions // always set ThrowExceptionIfNotSuccessResponse=false if you will use custom error-code actions // perform checks for neccessary override bool throwExIfNotSuccessRsp = options.ThrowExceptionIfNotSuccessResponse ?? ConfigurationBundle.HttpConfiguration.ThrowExceptionIfNotSuccessResponse; if (throwExIfNotSuccessRsp) { throw new RextException($"Server response is {rsp.StatusCode}"); } if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { rsp.Content = $"Url not found: {requestMsg.RequestUri}"; } else { rsp.Content = responseString; } rsp.Message = "Http call completed but not successful"; // handle code specific error from user int code = (int)response.StatusCode; if (code > 0 && ConfigurationBundle.StatusCodesToHandle != null && ConfigurationBundle.StatusCodesToHandle.Contains(code)) { ConfigurationBundle.OnStatusCode?.Invoke(ReturnStatusCode); } } // execute all user actions post-call ConfigurationBundle.AfterCall?.Invoke(); return(rsp); } catch (Exception ex) { // execute all user actions on error ConfigurationBundle.OnError?.Invoke(); if (ConfigurationBundle.SuppressRextExceptions) { if (ex?.Message.ToLower().Contains("a socket operation was attempted to an unreachable host") == true) { rsp.Message = "Network connection error"; } else if (ex?.Message.ToLower().Contains("the character set provided in contenttype is invalid") == true) { rsp.Message = "Invald response ContentType. If you are expecting a Stream response then set RextOptions.IsStreamResponse=true"; } else { rsp.Message = ex?.Message; //{ex?.InnerException?.Message ?? ex?.InnerException?.Message}"; } return(rsp); } else { throw ex; } } }