Conditionally add middleware in ASP.NET Core

Some weeks ago I wrote a blog post named How to modify response headers in ASP.NET Core middleware that explained how to set a custom response header in ASP.NET Core middleware. Since writing the post I had MULTIPLE people (okay, it was two people) write and ask how to only return the custom header for certain requests in the cleanest way. In this post, I'll show you a couple of ways to do that.

Conditionally add middleware in ASP.NET Core

I'll use the code from the previous post to illustrate how to conditionally add middleware in ASP.NET Core. To summarize, the middleware sets a custom response header named X-Took:

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 it to the Program.cs file requires only a single line:

app.UseMiddleware<TimingMiddleware>();

Now, let's say we only want to run the middleware/include the response header when the requested endpoint URL starts with /api. There are a couple of options available. The first one will be to include the condition inside the middleware class itself:

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

    if (context.Request.Path.StartsWithSegments("/api")) // ⬅️ this is where the magic happens
    {
        context.Response.OnStarting(() =>
        {
            context.Response.Headers["X-Took"] = $"{stopwatch.ElapsedMilliseconds} ms";
            return Task.CompletedTask;
        });
    }

    await _next(context);
}

The if statement make sure we only register the OnStarting callback if the request URL starts with /api. I typically don't recommend this approach since the middleware will still run for all requests to the backend. This probably won't be a performance issue unless you are developing Facebook, but the middleware class will become part of any stack traces logged in the request. Consider whether you want that or not.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

A better approach is to use the UseWhen method in the Program.cs file instead of the Use method:

app.UseWhen(
    context => context.Request.Path.StartsWithSegments("/api"),
    builder => builder.UseMiddleware<TimingMiddleware>()
);

The method takes two parameters. The first one is a Func which will return true in the case where we want the middleware to run and false if we don't. In this example, I simply copied the code including the StartsWithSegments method call from the previous example. The second parameter is an Action accepting an IApplicationBuilder object. This object is used to register the TimingMiddleware class using the UseMiddleware method from the previous post. You can use the builder object to register one or more middleware classes as well as all of the other features you have available on the app object. You can even create a nested UseWhen structure if you'd like.

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