Search code examples
c#asp.net-coremiddlewareasp.net-core-middleware

Write headers after Endpoint middleware?


I'm writing custom middleware which requires i attach the results as response headers to a given request. To do this, i've created simple middleware which looks like the following:

public class MyCustomMiddleware
{
  private readonly RequestDelegate _next;

  public MyCustomMiddleware(RequestDelegate next)
  {
    _next = next;
  }

  public async Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, IConfiguration configuration)
  {
    debug.print("I'm before the next piece of middleware");
    
    await _next(context);
    
    Context.Response.Headers.Add("x-special-header-4", "super secret response header");    
  }
}

This of course results in the following exception:

Exception thrown: 'System.InvalidOperationException' in Microsoft.AspNetCore.Server.Kestrel.Core.dll System.InvalidOperationException: Headers are read-only, response has already started.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException() at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.System.Collections.Generic.IDictionary<System.String,Microsoft.Extensions.Primitives.StringValues>.Add(String key, StringValues value)

This occurs when i'm using an ActionResult - but also when i've requested something that cannot be routed at all (and would as a result return a 404).

The App Startup order looks like:

app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

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

(I have a helper to setup the middleware, which i've omitted from this post, but just adds the custom middleware)

The real aim of the middleware is to measure time taken / other factors, which would need me to 'complete' the request, and then add headers to the response - all without modifying existing user code.

Is there a way that this can be done?


Solution

  • You are add the header after calling await _next(context);, which is too late because the response has already started. To fix this issue, you could try the httpContext.Response.OnStarting() delegate:

    public class MyCustomMiddleware
    {
      private readonly RequestDelegate _next;
    
      public MyCustomMiddleware(RequestDelegate next)
      {
        _next = next;
      }
    
      public async Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, IConfiguration configuration)
      {
        Debug.Print("I'm before the next piece of middleware");
    
        context.Response.OnStarting(state =>
        {
            var ctx = (HttpContext)state;
            ctx.Response.Headers.Add("x-special-header-4", "super secret response header");
            return Task.FromResult(0);
        }, context);
    
        await _next(context);
      }
    }