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.
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.
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.