How to modify response headers in ASP.NET Core middleware

I had a problem setting response headers in ASP.NET Core that I wanted to share some findings on. This might be clear to a lot of you but hopefully, this will help someone in the same situation as me. Here goes!

How to modify response headers in ASP.NET Core middleware

In a small internal API, I wanted to include some additional response headers on all requests made to the API. ASP.NET Core middleware is a great way of including code like this and something that I've blogged about multiple times already (like this and this). The middleware for adding the response headers looks similar to this:

internal class TimingMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();

        await _next(context);
        
        context.Response.Headers["X-Took"] = $"{stopwatch.ElapsedMilliseconds} ms";
    }
}

All the middleware does it to create a new Stopwatch and add the elapsed ms as a custom response header named X-Took. I know that there are alternative and better ways of measuring performance on an ASP.NET Core API but that's not really the focus here so play along.

The middleware class is added in the Program.cs file:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

app.UseMiddleware<TimingMiddleware>(); // ⬅️ this is where the magic happens

// ...

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

When running the project, I was expecting an X-Took header but all I got was this exception:

InvalidOperationException

After some googling, I figured out that this exception is caused by the fact that I'm trying to add headers after invoking the next (the next piece of middleware in the pipeline). ASP.NET Core doesn't allow headers to be added after generating the response body.

One solution is to migrate my code to Action Filters which I use for other projects. But luckily there's a hook in the pipeline named OnStarting that I can use for this purpose. With a bit of refactoring, the code looks like this:

internal class TimingMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();

        context.Response.OnStarting(() =>
        {
            context.Response.Headers["X-Took"] = $"{stopwatch.ElapsedMilliseconds} ms";
            return Task.CompletedTask;
        });

        await _next(context);
    }
}

Adding the response header is moved inside the OnStarting action. This action is called just before response headers are sent to the client, which makes it a great place to include custom headers like this.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website