How can I capture the HttpContext.Response.Headers for all web api responses?

Viewed 669

I'm attempting to capture all response headers for all responses in a Web API, but the HttpContext.Response.Headers collection is always empty. I'm currently running .NET Core and ASP.Net Core 3.1.

Implementation starts with the Startup.cs where middleware is added.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
  
    app.UseMiddleware<CaptureResponseHeaders>();
   
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

}

Next the middleware class

public class CaptureResponseHeaders
{
    public CaptureResponseHeaders(RequestDelegate next)
    {
        _next = next;
    }
    private readonly RequestDelegate _next;

    public async Task Invoke(HttpContext httpContext)
    {
     
        if(httpContext.Response.Headers.Count > 0) //this is always zero.

And the request, a simple Get API endpoint

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
   
private readonly ILogger<WeatherForecastController> _logger;

public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
    _logger = logger;
}

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    
    var rng = new Random();
    
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = rng.Next(-20, 55),
        Summary = Summaries[rng.Next(Summaries.Length)]
    })
    .ToArray();
   
}

And the response headers I expect to capture

content-type: application/json; charset=utf-8
date: Mon, 14 Dec 2020 18:47:43 GMT
server: Microsoft-IIS/10.0
x-powered-by: ASP.NET
2 Answers

You can do the following in your invoke method:

public async Task Invoke(HttpContext context)
{
    context.Response.OnStarting(state => {
        var httpContext = (HttpContext)state;

        if (httpContext.Response.Headers.Count > 0)
            CaptureHeaders(); //TODO your implementation

        return Task.CompletedTask;
    }, context);

    await _next(context);
}

This lets you capture the Response right before it is sent to the client. You can also use OnCompleted to get the Response after it is sent.

An image of the headers can be seen below:

enter image description here

It is my understanding that if you move the addition of your middleware up the stack, it will be executed early for the request and late for the response.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    // here 
    app.UseMiddleware<CaptureResponseHeaders>();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

}

As for the zero you keep seeing, try to put your capture logic after the execution of the rest of the pipeline.

public async Task Invoke(HttpContext httpContext)
    {
        await _next();

        if(httpContext.Response.Headers.Count > 0) 
        {
            // do responsive things
        }
    }
Related