I'm trying to log the HTTP Response Headers of my Web API project.
The project is developed by VS2012, .NET 4.5 and ASP.NET MVC 4.
I've wrote a DelegatingHandler subclass like this:
public class LoggingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Execute the request
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
return response;
});
}
}
However, the problem is, I can't get the header values from the response. response.Headers is an empty collection, response.Content.Headers contains nothing but a key named Content-Type, and HttpContext.Current is null.
I've seen the code of WebAPIContrib which use the same logic to log the headers, but their code does not seem to work either.
So how should I trace the HTTP Response Headers in Web API project?
Message handlers are called in the same order that they appear in
MessageHandlers collection. Because they are nested, the response
message travels in the other direction. That is, the last handler is
the first to get the response message.
Make sure that the logging handler is registered early in the pipeline. Preferably first.
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MessageHandlers.Add(new LoggingHandler(...));
//...add other handlers
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());
// Other code not shown...
}
}
That way any other handlers would have their chance to populate the response and have that info logged.
You can also simplify the class using async/await syntax to make accessing the response cleaner.
public class LoggingHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
//...Extract and log request
LogRequest(request);
// Send the request on to the inner handler(s) and get the response
var response = await base.SendAsync(request, cancellationToken);
//...Extract details from response for logging
LogResponse(response);
return response;
}
private void LogRequest(HttpRequestMessage request) {
//... code removed for brevity
}
private void LogResponse(HttpResponseMessage response) {
//... code removed for brevity
}
}
Should be able to access the necessary details from the response before returning it.
Reference : HTTP Message Handlers in ASP.NET Web API
Try out the below code.
return base.SendAsync(request, cancellationToken).ContinueWith(
task =>
{
var headers = task.Result.ToString();
var body = task.Result.Content.ReadAsStringAsync().Result;
// RETURN THE ORIGINAL RESULT
var response = task.Result;
return response;
}
);
You must look at the right place:
request.Content.Headers.ContentType
Assuming there's a Content-Type: application/json request header, then the aforementioned will return "application/json".
So basically some headers are associated with the Content and that's where you should be reading them from.
Same is true for the response headers. Depending on their type you might need to extract them from the response content (for requests that return body)
response.Content.Headers.ContentType
Related
Since Microsoft recommends that the HttpClient be created once and reused throughout the life of a program, I wondering how to update DefaultRequestHeaders when for example, the token has expired and need to be refresh.
DefaultRequestHeaders is more over not thread safe (for what I know) and the list of headers defined there, shared by all potentially pending requests. Clear() the list and Add() the header with a new token, seems not the wise thing to do.
Update
To be more precise, I don't want/need to change request headers for every request. Only when I've got a HTTP 401 status code.
Wire up a message handler with your HttpClient when you register the IHttpClient in the DI container registry phase or use another pattern such as a factory or singleton to return an instance of the IHttpClient with a custom message handler. Inspect the outbound call and add the necessary headers.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/httpclient-message-handlers
Sample header message handler
class MessageHandler1 : DelegatingHandler
{
private int _count = 0;
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
System.Threading.Interlocked.Increment(ref _count);
request.Headers.Add("X-Custom-Header", _count.ToString());
return base.SendAsync(request, cancellationToken);
}
}
Sample logger message handler:
class LoggingHandler : DelegatingHandler
{
StreamWriter _writer;
public LoggingHandler(Stream stream)
{
_writer = new StreamWriter(stream);
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
_writer.WriteLine("{0}\t{1}\t{2}", request.RequestUri,
(int)response.StatusCode, response.Headers.Date);
}
return response;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_writer.Dispose();
}
base.Dispose(disposing);
}
}
Add it to the pipeline
HttpClient client = HttpClientFactory.Create(new Handler1(), new Handler2(), new Handler3());
Threading Concerns
Regarding threading concerns or concurrency, the HttpRequestMessage parameter on the SendAsync method will be per request. If you add the header to the request.Headers collection, you will updating headers for that instance of the request only (i.e., not globally )
Or use the Authorization property on the request.Headers instance:
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", bearerToken);
Please see MSDN link below
https://msdn.microsoft.com/en-us/library/system.net.http.httprequestmessage
If you use DefaultRequestHeaders on a static, shared, singleton, Lifestyle.Singleton, etc, instance of the HttpClient then you will have threading concerns and need proper synchronization to update the DefaultRequestHeaders collection.
I have a web project (C#, MVC5 but no WebAPI) and a simple HTTP REST client that is calling an external REST service and acquires an accessToken etcing.
I want to check the response from all my Get/PostAsync calls for statusCode 401 but I see that I can only override the SendAsync method when implementing the DelegatingHandler.
class CustomDelegatingHandler : DelegatingHandler
{
async protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
Is there something else I can do so as not to change the implementation of all my async calls to use SendAsync?
(What I really really want to do is refresh the accessToken.)
I think you're on the right track. You wouldn't need to change your calls to use SendAsync instead of GetAsync, PostAsync, etc though. Rather, you'd need to change how you instantiate HttpClient:
var client = new HttpClient(new CustomDelegatingHandlerTokenRefresher());
// GetAsync, PostAsync, etc will hit your handler
Use the Decorator or Interceptor pattern.
Example concrete decorator:
public class CustomDelegatingHandlerTokenRefresher : DelegatingHandler
{
public CustomDelegatingHandlerTokenRefresher(DelegatingHandler handler)
{
InnerHandler = handler;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
RefreshToken();
return await InnerHandler.SendAsync(request, cancellationToken);
}
}
For logging purposes, I am trying to monitor the requests being made through a WebAPI. I have created and I am looking for a way to get back the body sent through in a request after the request has been fulfilled and responded to. I am trying to do this through using a ActionFilter but thus far have failed in reading the body from the request.
Can anybody give some advice how I may access this information?
For context I am trying to do this within this code:
public class LoggingActionFilter : ActionFilterAttribute
{
public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
var test = actionExecutedContext.Request.Content.ReadAsStringAsync().Result;
return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
}
}
I have tried reading back the Content on the actionExecutedContext variable in order to get back the body but have found this to return just blank so far.
you're just dealing with request body so don't need to use OnActionExecutedAsync method, you can just override OnActionExecuting like this,
public override void OnActionExecuting(HttpActionContext actionContext)
{
var test = (actionContext.Request.Content as ObjectContent).Value.ToString();
// your logging code here
}
Another option available in WebAPI is DelegatingHandler. if you want to log just request body then override SendAsync method,
public class ApiLogHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestBody = request.Content.ReadAsStringAsync().Result;
// your logging code here
return base.SendAsync(request, cancellationToken);
}
}
If you decided to choose DelegatingHandler then you need to register that handler to Global message handlers.
I have to write unit test on ASP.NET MVC Web API Controller with Rhino.Mock
I have a handler named AHandler.cs with inherts from System.Net.Http.HttpClientHandler class.
The singnature SendAsync method of AHandler.cs is like followings :
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
.....
var response = base.SendAsync(request, cancellationToken).Result;
if (response.IsSuccessStatusCode)
{
.....
}
}
the base keyword above means HttpClientHandler and its SendAsync() method is "protected"!!!
Now I try to mock the object "base.SendAsync(request, cancellationToken).Result" and got the hand-made response result I wanted.
But it seems that Rhino mocks can't see the "base" keyword when I wrote the followings code :
var mockbase = MockRepository.GenerateMock<AHandler>;
mockbase.Stub(x => x.base <=== can't see base keyword
^^^^^
So I change another way and try to mock the HttpClientHandler class
var mockbase = MockRepository.GenerateMockHttpClientHandler>;
mockbase.Stub(x => x. <== I can't see SendAsync() method, becase it is protected !!
Now I really suffer in it !!
Can anybody give me some advice that how to made a custom response in MVC handler ?!
very thanks !!
Why do you want to mock a handler in first place ? You can inject an specific dummy implementation for your tests. That handler will return a new HttpResponse message expected by your tests.
public class MyDummyHttpHandler : DelegatingHandler
{
HttpResponseMessage response;
public MyDummyHttpHandler(HttpResponseMessage response)
{
this.response = response;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
.....
TaskCompletionSource<HttpResponseMessage> tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(this.response);
return tsc.Task;
}
}
I am currently using several delegation handlers (classes derived from DelegatingHandler) to work on the request before it is sent, for things like validating a signature etc. This is all very nice, because I don't have to duplicate signature validation on all calls (for example).
I would like to use the same principle on the response from the same web request. Is there something similar to the DelegatingHandler for the response? A way to catch the response before it has returned to the method, in a way?
Additional information:
I am calling a web api using HttpClient.PutAsync(...)
Yes. You can do that in the continuation task.
I explain it here.
For example, this code (from the blog above) traces request URI and adds a dummy header to response.
public class DummyHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// work on the request
Trace.WriteLine(request.RequestUri.ToString());
var response = await base.SendAsync(request, cancellationToken);
response.Headers.Add("X-Dummy-Header", Guid.NewGuid().ToString());
return response;
}
}
Here is an example for intercepting the request, and the response. the overridden method SendAsync is used to capture the original request, whereas the method called ResponseHandler is used to capture the response.
Example to capture original request and response
using System.Net.Http;
using System.Threading.Tasks;
namespace webAPI_Test
{
public class MessageInterceptor : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// CATCH THE REQUEST BEFORE SENDING TO THE ROUTING HANDLER
var headers = request.ToString();
var body = request.Content.ReadAsStringAsync().Result;
var fullRequest = headers + "\n" + body;
// SETUP A CALLBACK FOR CATCHING THE RESPONSE - AFTER ROUTING HANDLER, AND AFTER CONTROLLER ACTIVITY
return base.SendAsync(request, cancellationToken).ContinueWith(
task =>
{
// GET THE COPY OF THE TASK, AND PASS TO A CUSTOM ROUTINE
ResponseHandler(task);
// RETURN THE ORIGINAL RESULT
var response = task.Result;
return response;
}
);
}
public void ResponseHandler(Task<HttpResponseMessage> task)
{
var headers = task.Result.ToString();
var body = task.Result.Content.ReadAsStringAsync().Result;
var fullResponse = headers + "\n" + body;
}
}
}
To use this method, the class needs to be identified and registered as a MessageHandler. I added the following line to my Global.asax file...
Example how to register the new MessageInterceptor class
GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageInterceptor());
Here is my complete Global.asax file. Notice how the MessageInterceptor is referenced...
Full version of Global.asax showing MessageInterceptor integration
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace webAPI_Test
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageInterceptor());
}
}
}