How can I handle httpclient response code globally? - c#

public class DateObsessedHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestDate = request.Headers.Date;
// do something with the date ...
var response = await base.SendAsync(request, cancellationToken);
// if(response.statuscode is 403)
// How do I redirect?
return response;
}
I've tried the delegation handler above.
How do I redirect to a controller Action?

As far as I know, if you get the response from httpclient in your application, it will not directly redirect to another page. You still need write some codes to handle the response and return a new context from your application.
Normally, we could firstly throw the exception in the DateObsessedHandler and then we could use ExceptionHandler middleware to handle the exception and redirect to another page.
More details, you could refer to below codes:
public class DateObsessedHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestDate = request.Headers.Date;
// do something with the date ...
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Forbidden)
{
throw new Exception("403");
}
// if(response.statuscode is 403)
// How do I redirect?
return response;
}
}
Startup.cs Configure method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error.InnerException.Message == "403")
{
context.Response.Redirect("http://www.google.com");
}
});
});
// other middleware
}

Related

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.

Navigate to another page without executing code next to it

I have written a custom HttpClientHandler to set the token in each HTTP request and to handle 401 responses.
Here is the helper class.
public class CustomHttpClientHandler : HttpClientHandler
{
private readonly ISyncLocalStorageService localStorageService;
private readonly NavigationManager navigationManager;
public CustomHttpClientHandler(ISyncLocalStorageService localStorageService, NavigationManager NavigationManager)
{
this.localStorageService = localStorageService;
navigationManager = NavigationManager;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = localStorageService.GetItem<string>(Auth.Token.ToString());
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await base.SendAsync(request, cancellationToken);
if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
navigationManager.NavigateTo("login");
}
return response;
}
}
Now what I am trying to achieve is after navigating to the login page. I want this method to exit from here and not execute the return line.
What I know is I can throw a custom exception and catch it globally and then redirect from there. But just to know, is there any other way for it?
if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
navigationManager.NavigateTo("login"); // i want to exit code from here
}
return response; // and not want to return response.
What's happening is even though I redirect it to the login page it returns the response and my razor component will throw null reference exception because of the below line, as I am reading the response and returning it after deserializing it, which returns null.
var stringResult = await httpResult.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TOutput>(stringResult);
What I can do is to check everywhere after getting an HTTP response for null, but I want to handle it generically.

ObjectDisposedException when reading requests HttpContent in test

I'm integrating a 3rd part API using TDD, and so I am implementing a HttpClient wrapper interface that exposes the possible api calls and so on.
I want to test that the correct payload was sent in a post method, but when I try to read the string content from my injected fake HttpMessageHandler I get an ObjectDisposedException. Is there a better way to test this?
Test code:
[Fact]
public async void PostSignupRequest_RequestSent_PostedSerializedRequestAsContent()
{
var client = MakeOnboardingClient();
_fakeJsonSerializer.SerializedResult = "some json";
await client.PostSignupRequest(_someSignupRequest);
Assert.Equal("some json", await _fakeMessageHandler.Request.Content.ReadAsStringAsync());
}
My HttpMessageHandler spy/test double:
public class FakeHttpMessageHandler : HttpMessageHandler
{
public HttpRequestMessage Request;
public string ResponseContent = string.Empty;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Request = request;
return await Task.FromResult(new HttpResponseMessage
{
Content = new StringContent(ResponseContent)
});
}
}
Production code:
public async Task<SignupRequestResponse> PostSignupRequest(SignupRequest request)
{
var json = _jsonSerializer.Serialize(request);
await _httpClient.PostAsync(/* url */, new StringContent(json));
return null;
}
I've found a fix now. In my HttpMessageHandler fake I don't just save the Request now, I also explicitly save the content string (which can be extracted at that point since the HttpClient hasn't disposed the request yet). My fake now looks like this:
public class FakeHttpMessageHandler : HttpMessageHandler
{
public HttpRequestMessage Request;
public string LastRequestString = string.Empty;
public string ResponseContent = string.Empty;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Content != null) // needed this to prevent some NPEs in other tests, YMMV
{
LastRequestString = await request.Content.ReadAsStringAsync();
}
Request = request;
return await Task.FromResult(new HttpResponseMessage
{
Content = new StringContent(ResponseContent)
});
}
}

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);
}
}

How do I set the WWW-Authentication header in an IAuthenticationFilter implementation?

I'm implementing basic authentication using MVC5's IAuthenticationFilter interface. My understanding is that this is now the preferred approach instead of using a DelegatingHandler. I've got it working but the www-authenticate header is not being returned in the response. This is my implementation of ChallengeAsync:
public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var result = await context.Result.ExecuteAsync(cancellationToken);
if (result.StatusCode == HttpStatusCode.Unauthorized)
{
result.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", "realm=localhost"));
}
}
The header is returned if I set it in AuthenticateAsync but I think I'm supposed to set it in ChallengeAsync. Sample implementations have been hard to find.
In ChallengeAsync, set context.Result to an instance of type IHttpActionResult, like so.
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken)
{
context.Result = new ResultWithChallenge(context.Result);
return Task.FromResult(0);
}
Provide an implementation, like so.
public class ResultWithChallenge : IHttpActionResult
{
private readonly IHttpActionResult next;
public ResultWithChallenge(IHttpActionResult next)
{
this.next = next;
}
public async Task<HttpResponseMessage> ExecuteAsync(
CancellationToken cancellationToken)
{
var response = await next.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(
new AuthenticationHeaderValue("Basic", "realm=localhost"));
}
return response;
}
}

Categories

Resources