HttpContent.ReadAsStringAsync causes request to hang - c#

In ASP.NET WebAPi 2 code, I have a delegate handler
public class RequestHandler1 : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var formData2 = await ReadContent(request);
return await base.SendAsync(request, cancellationToken);
}
private static async Task<string> ReadContent(HttpRequestMessage request)
{
using (var ms = new MemoryStream())
{
await request.Content.CopyToAsync(ms);
ms.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(ms, Encoding.UTF8, true, 100, true))
{
return sr.ReadToEnd();
}
}
}
private static async Task<string> ReadContent3(HttpRequestMessage request)
{
var text = await request.Content.ReadAsStringAsync();
return text;
}
}
The issue is related to HttpContent.ReadAsStringAsync causes request to hang (or other strange behaviours) but it was never properly answered in that thread.
By the time I call return await base.SendAsync(request, cancellationToken); it just hangs. it does not matter if I call ReadContent or ReadContent3
Any more suggestions?

try this code
public class CustomLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var logMetadata = await BuildRequestMetadata(request);
var response = await base.SendAsync(request, cancellationToken);
logMetadata = await BuildResponseMetadata(logMetadata, response);
await SendToLog(logMetadata);
return response;
}
private async Task<LogMetadata> BuildRequestMetadata(HttpRequestMessage request)
{
LogMetadata log = new LogMetadata
{
RequestMethod = request.Method.Method,
RequestTimestamp = DateTime.Now,
RequestUri = request.RequestUri.ToString(),
RequestContent = await request.Content.ReadAsStringAsync(),
};
return log;
}
private async Task<LogMetadata> BuildResponseMetadata(LogMetadata logMetadata, HttpResponseMessage response)
{
logMetadata.ResponseStatusCode = response.StatusCode;
logMetadata.ResponseTimestamp = DateTime.Now;
logMetadata.ResponseContentType = response.Content == null ? string.Empty : response.Content.Headers.ContentType.MediaType;
logMetadata.Response = await response.Content.ReadAsStringAsync();
return logMetadata;
}
private async Task<bool> SendToLog(LogMetadata logMetadata)
{
try
{
//write your code
}
catch
{
return false;
}
return true;
}
}

Related

Why Task.WhenAll return null?

I have two methods which return independent data. I assume it's a good idea to run them in parallel.
Without any modifications code look's like:
private async Task<Entity> MethodAsync()
{
...
var model1 = await client.Method1(Id1, cancellationToken);
var model2 = await client.Method2(Id2, cancellationToken);
...
}
Those methods return data as I expecting. Now when I change code all methods return "null". When I inspect Task object in visual studio there are properties Id = 2356, Status = RanToCompletion, Method = "{null}", Result = "".
private async Task<Entity> MethodAsync()
{
var model1Task = client.Method1(Id1, cancellationToken);
var model2Task = client.Method2(Id2, cancellationToken);
var task = Task.WhenAll(new Task[] { model1Task ,model2Task });
await task; //doesn't work
//task.Wait(); //doesn't work
//await Task.WhenAll(new Task[] { model1Task , model2Task }); //doesn't work
//Task.WhenAll(new Task[] { model1Task, model2Task}); //doesn't work
}
Code of client methods almost the same:
public async Task<Model> Method1(Guid Id, CancellationToken cancellationToken)
{
HttpResponseMessage responseMessage = await client.GetAsync($"customEndPoint");
ResponseMessageSingle<Model> response = JsonSerializer.Deserialize<ResponseMessageSingle<Model>>(
await responseMessage.Content.ReadAsStringAsync(cancellationToken),
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return response.result;
}
private class ResponseMessageSingle<T>
{
public bool success { get; set; }
public string message { get; set; }
public T result { get; set; }
}
Also there is AuthorizeInterceptor(DelegatingHandler):
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
some logic...
request.SetBearerToken(TokenResponse.AccessToken);
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return await Task.FromResult(response);
}
Task.WhenAll does not return a result in your case, because you create an array of Task, which has no result. And since your tasks return different results, you cannot get that in an array nicely.
You should change your code like this to get the results of your methods:
private async Task<Entity> MethodAsync()
{
var model1Task = client.Method1(Id1, cancellationToken);
var model2Task = client.Method2(Id2, cancellationToken);
var task = Task.WhenAll(new Task[] { model1Task ,model2Task });
await task;
var result1 = await model1Task;
var result2 = await model2Task;
...
}
Since both methods will be completed, awaiting them will just immediately get you the results.

Xamarin - Delegating Handler failing with System.Threading.Tasks.TaskCanceledException: A task was canceled

On my Xamarin Forms project I'm trying to logout when the token is no longer valid and it returns 401 response.
For that I'm trying to use a DelegatingHandler but it will stop at SendAsync method without giving any errors.
Here is the DelegatingHandler class
public class HttpDelegatingHandler : DelegatingHandler
{
public HttpDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// before request
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// after request
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await Logout();
}
return response;
}
private async Task Logout()
{
CurrentPropertiesService.Logout();
CurrentPropertiesService.RemoveCart();
await Shell.Current.GoToAsync($"//main");
}
And here is my class AzureApiService where GetAsync stops the application
public class AzureApiService
{
HttpClient httpClient;
public AzureApiService()
{
#if DEBUG
var httpHandler = new HttpDelegatingHandler(new HttpClientHandler());
#else
var httpHandler = HttpDelegatingHandler(new HttpClientHandler());
#endif
httpClient = new HttpClient(httpHandler);
httpClient.Timeout = TimeSpan.FromSeconds(15);
httpClient.MaxResponseContentBufferSize = 256000;
}
public async Task<string> LoginAsync(string url, AuthUser data)
{
var user = await HttpLoginPostAsync(url, data);
if (user != null)
{
//Save data on constants
CurrentPropertiesService.SaveUser(user);
return user.Token;
}
else
{
return string.Empty;
}
}
// Generic Get Method
public async Task<T> HttpGetAsync<T>(string url, string token)
{
T result = default(T);
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.GetAsync(url);
HttpContent content = response.Content;
var jsonResponse = await content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
}
catch (Exception ex)
{
OnError(ex.ToString());
}
return result;
}
Please help I don't know where the issue is. Thanks.

HttpClient ObjectDisposedException Android

I am testing an HTTP Request to my API while the server is down.
It should receive an error response, but instead, it returns null and it gives me this exception:
System.ObjectDisposedException: Cannot access a closed Stream.
This happens in Android only, iOS I get an error response. this is my code:
using (HttpClient client = new HttpClient())
{
try
{
//pedido de token
var loginInfo = new StringContent(JsonConvert.SerializeObject(userAuth).ToString(), Encoding.UTF8, "application/json");
var requestToken = await client.PostAsync(URLs.url + URLs.getToken, loginInfo);
var receiveToken = await requestToken.Content.ReadAsStringAsync();
It doesn't reach the ReadAsString, throws the exception in the PostAsync.
Don't dispose of HttpClient. It is designed to be reused and handle multiple simultaneous requests.
Here's more info about how HttpClient works: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Here is a generic implementation that I use for all HttpClient services in my Xamarin.Forms apps:
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Net.Http;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using Xamarin.Forms;
namespace NameSpace
{
public abstract class BaseHttpClientService
{
#region Constant Fields
static readonly Lazy<JsonSerializer> _serializerHolder = new Lazy<JsonSerializer>();
static readonly Lazy<HttpClient> _clientHolder = new Lazy<HttpClient>(() => CreateHttpClient(TimeSpan.FromSeconds(30)));
#endregion
#region Fields
static int _networkIndicatorCount = 0;
#endregion
#region Events
public static event EventHandler<string> HttpRequestFailed;
#endregion
#region Properties
static HttpClient Client => _clientHolder.Value;
static JsonSerializer Serializer => _serializerHolder.Value;
#endregion
#region Methods
protected static async Task<T> GetObjectFromAPI<T>(string apiUrl)
{
using (var responseMessage = await GetObjectFromAPI(apiUrl).ConfigureAwait(false))
return await DeserializeResponse<T>(responseMessage).ConfigureAwait(false);
}
protected static async Task<HttpResponseMessage> GetObjectFromAPI(string apiUrl)
{
try
{
UpdateActivityIndicatorStatus(true);
return await Client.GetAsync(apiUrl).ConfigureAwait(false);
}
catch (Exception e)
{
OnHttpRequestFailed(e.Message);
Report(e);
throw;
}
finally
{
UpdateActivityIndicatorStatus(false);
}
}
protected static async Task<TResponse> PostObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PostObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PostObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(HttpMethod.Post, apiUrl, requestData);
protected static async Task<TResponse> PutObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PutObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PutObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(HttpMethod.Put, apiUrl, requestData);
protected static async Task<TResponse> PatchObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PatchObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PatchObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(new HttpMethod("PATCH"), apiUrl, requestData);
protected static async Task<TResponse> DeleteObjectFromAPI<TResponse>(string apiUrl)
{
using (var responseMessage = await DeleteObjectFromAPI(apiUrl).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> DeleteObjectFromAPI(string apiUrl) => SendAsync<object>(HttpMethod.Delete, apiUrl);
static HttpClient CreateHttpClient(TimeSpan timeout)
{
HttpClient client;
switch (Device.RuntimePlatform)
{
case Device.iOS:
case Device.Android:
client = new HttpClient();
break;
default:
client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip });
break;
}
client.Timeout = timeout;
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
return client;
}
static async Task<HttpResponseMessage> SendAsync<T>(HttpMethod httpMethod, string apiUrl, T requestData = default)
{
using (var httpRequestMessage = await GetHttpRequestMessage(httpMethod, apiUrl, requestData).ConfigureAwait(false))
{
try
{
UpdateActivityIndicatorStatus(true);
return await Client.SendAsync(httpRequestMessage).ConfigureAwait(false);
}
catch (Exception e)
{
OnHttpRequestFailed(e.Message);
Report(e);
throw;
}
finally
{
UpdateActivityIndicatorStatus(false);
}
}
}
protected static void UpdateActivityIndicatorStatus(bool isActivityIndicatorDisplayed)
{
if (isActivityIndicatorDisplayed)
{
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage.IsBusy = true);
_networkIndicatorCount++;
}
else if (--_networkIndicatorCount <= 0)
{
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage.IsBusy = false);
_networkIndicatorCount = 0;
}
}
static async ValueTask<HttpRequestMessage> GetHttpRequestMessage<T>(HttpMethod method, string apiUrl, T requestData = default)
{
var httpRequestMessage = new HttpRequestMessage(method, apiUrl);
switch (requestData)
{
case T data when data.Equals(default(T)):
break;
case Stream stream:
httpRequestMessage.Content = new StreamContent(stream);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
break;
default:
var stringPayload = await Task.Run(() => JsonConvert.SerializeObject(requestData)).ConfigureAwait(false);
httpRequestMessage.Content = new StringContent(stringPayload, Encoding.UTF8, "application/json");
break;
}
return httpRequestMessage;
}
static async Task<T> DeserializeResponse<T>(HttpResponseMessage httpResponseMessage)
{
httpResponseMessage.EnsureSuccessStatusCode();
try
{
using (var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var reader = new StreamReader(contentStream))
using (var json = new JsonTextReader(reader))
{
if (json is null)
return default;
return await Task.Run(() => Serializer.Deserialize<T>(json)).ConfigureAwait(false);
}
}
catch (Exception e)
{
Report(e);
throw;
}
}
static void OnHttpRequestFailed(string message) => HttpRequestFailed?.Invoke(null, message);
static void Report(Exception e, [CallerMemberName]string callerMemberName = "") => Debug.WriteLine(e.Message);
#endregion
}
}
I had the same problem (it worked fine in UWP but this error on Android).
Please see this linked question for what fixed it for me: HttpClient.SendAsync throws ObjectDisposedException on Xamarin.Forms Android but not on UWP

Web API request content empty

I have a DelegatingHandler implementation to log request/response content:
public class RequestAndResponseLoggerDelegatingHandler : DelegatingHandler
{
public IDataAccess Data { get; set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var started = DateTime.UtcNow;
var response = await base.SendAsync(request, cancellationToken);
await Log(started, request, response);
return response;
}
private async Task Log(DateTime start, HttpRequestMessage request, HttpResponseMessage response)
{
var finished = DateTime.UtcNow;
var requestContent = await request.Content.ReadAsStringAsync();
var responseContent = await response.Content.ReadAsStringAsync();
var info = new ApiLogEntry(start, finished, requestContent, responseContent, request, response);
Data.Log(info);
}
}
but for some reason requestContent is coming up empty. request.Content.Length is confirming that there is content, it's just not being extracted.
Any ideas?
The request body stream is read and bound into parameters and as a result of binding, the stream has been positioned to the end. That is why it is coming as empty. If you seek to the beginning before request.Content.ReadAsStringAsync(), it should work. Instead of that, you can simply read the request body first before binding happens, some thing like this.
public class RequestAndResponseLoggerDelegatingHandler : DelegatingHandler
{
public IDataAccess Data { get; set; }
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var started = DateTime.UtcNow;
var requestContent = await request.Content.ReadAsStringAsync();
var response = await base.SendAsync(request, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
await Log(started, request, response, requestContent, responseContent);
return response;
}
private async Task Log(DateTime start, HttpRequestMessage request,
HttpResponseMessage response, string requestContent,
string responseContent)
{
var finished = DateTime.UtcNow;
var info = new ApiLogEntry(start, finished, requestContent, responseContent,
request, response);
Data.Log(info);
}
}

checking Internet connection with HttpClient

I am having difficulties to understand on how the bellow code could handle occasional internet connection loss. Ideally I would like to pause the app, once the connection is lost, and resume when it is up again. Is there any guideline on how to do it?
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.UseDefaultCredentials = true;
HttpClient client = new HttpClient(clientHandler) { MaxResponseContentBufferSize = 1000000 };
HttpResponseMessage response = await client.GetAsync(Url, ct);
The following example is not a direct solution, but it is an example I built to show how to return "pre-canned" content to requests whilst offline and then return back online when connectivity is restored. If you can get what I'm doing here, building what you want should be fairly easy.
[Fact]
public async Task Getting_a_response_when_offline()
{
var offlineHandler = new OfflineHandler(new HttpClientHandler(), new Uri("http://oak:1001/status"));
offlineHandler.AddOfflineResponse(new Uri("http://oak:1001/ServerNotRunning"),
new HttpResponseMessage(HttpStatusCode.NonAuthoritativeInformation)
{
Content = new StringContent("Here's an old copy of the information while we are offline.")
});
var httpClient = new HttpClient(offlineHandler);
var retry = true;
while (retry)
{
var response = await httpClient.GetAsync(new Uri("http://oak:1001/ServerNotRunning"));
if (response.StatusCode == HttpStatusCode.OK) retry = false;
Thread.Sleep(10000);
}
}
public class OfflineHandler : DelegatingHandler
{
private readonly Uri _statusMonitorUri;
private readonly Dictionary<Uri, HttpResponseMessage> _offlineResponses = new Dictionary<Uri, HttpResponseMessage>();
private bool _isOffline = false;
private Timer _timer;
public OfflineHandler(HttpMessageHandler innerHandler, Uri statusMonitorUri)
{
_statusMonitorUri = statusMonitorUri;
InnerHandler = innerHandler;
}
public void AddOfflineResponse(Uri uri, HttpResponseMessage response)
{
_offlineResponses.Add(uri,response);
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_isOffline == true) return OfflineResponse(request);
try
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.ServiceUnavailable || response.StatusCode == HttpStatusCode.BadGateway)
{
MonitorOfflineState();
return OfflineResponse(request);
}
return response;
}
catch (WebException ex)
{
MonitorOfflineState();
return OfflineResponse(request);
}
}
private void MonitorOfflineState()
{
_isOffline = true;
_timer = new Timer( async state =>
{
var request = new HttpRequestMessage() {RequestUri = _statusMonitorUri};
try
{
var response = await base.SendAsync(request, new CancellationToken());
if (response.StatusCode == HttpStatusCode.OK)
{
_isOffline = false;
_timer.Dispose();
}
}
catch
{
}
}, null, new TimeSpan(0,0,0),new TimeSpan(0,1,0));
}
private HttpResponseMessage OfflineResponse(HttpRequestMessage request)
{
if (_offlineResponses.ContainsKey(request.RequestUri))
{
return _offlineResponses[request.RequestUri];
}
return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
}
}
}

Categories

Resources