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);
}
}
Related
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.
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
}
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;
}
}
I have this C# controller which preforming a different SQL functions by string receive as input.
public HttpResponseMessage GetFunction(string SQLstring)
{
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(SQLFunctions.SQLsyncFunctionGet(SQLstring), System.Text.Encoding.UTF8, "application/json")
};
return response;
}
I'm trying to rebuild it in Async method:
First I change the SQL sync function to async without any problem:
public async Task<string> SQLasyncFunctionGET(string SQLString)
How do I change the GetFunction class to activate it in the Web API that I've built?
public async Task<IHttpActionResult> GetFunction(string SQLString)
{
var content = await ???????????????
return ok(content);
}
I'm not that familiar with this stack, so I don't remember if you can return Task<HttpResponseMessage> or not. But if you can, this should work:
public async Task<HttpResponseMessage> GetFunction(string SQLstring)
{
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(await SQLFunctions.SQLsyncFunctionGet(SQLstring), System.Text.Encoding.UTF8, "application/json")
};
return response;
}
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)
});
}
}