ASP.NET Core – How to add your own middleware function

Middleware functions have access to requests before they are sent to the controllers. Likewise, they have access to responses from the controllers before they are returned to the client.

This is useful for doing things like logging the request and response, generating stats about requests, handling exceptions, and many more scenarios.

In this article, first I’ll show two ways to add your own middleware (class vs inline), and then I’ll into more details about middleware functions.

Option 1 – Add middleware class

To add your own middleware function, first add a middleware class. The constructor needs to accept a RequestDelegate parameter, and you need to add a method that accepts an HttpContext parameter. Note: This example doesn’t deal with injecting dependencies into the middleware.

In the middleware function body, you can inspect the request, execute the next middleware function, and then inspect the response.

public class StatsLoggerMiddleware { private readonly RequestDelegate NextMiddleware; public StatsLoggerMiddleware(RequestDelegate nextMiddleware) { NextMiddleware = nextMiddleware; } public async Task InvokeAsync(HttpContext context) { //1 - Inspect the request if (context.Request.Headers.ContainsKey("Debug")) { Console.WriteLine($"Got request. Method={context.Request.Method} Path={context.Request.Path}"); var sw = Stopwatch.StartNew(); //2 - Call the next middleware await NextMiddleware(context); //3 - Inspect the response sw.Stop(); Console.WriteLine($"Request finished. Method={context.Request.Method} Path={context.Request.Path} StatusCode={context.Response.StatusCode} ElapsedMilliseconds={sw.ElapsedMilliseconds}"); } } }
Code language: C# (cs)

Note: This uses the convention-based middleware activation approach, instead of the factory-based approach.

Then in Startup.Configure(), register your middleware class by calling app.UseMiddleware():

public class Startup { //rest of class public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseMiddleware<StatsLoggerMiddleware>(); //rest of method } }
Code language: C# (cs)

Now when requests come in, your middleware function will be called.

Here’s an example of what this middleware function outputs when multiple requests come in:

Got request. Method=POST Path=/Stocks/ Request finished. Method=POST Path=/Stocks/ StatusCode=400 ElapsedMilliseconds=180 Got request. Method=POST Path=/Stocks/ Request finished. Method=POST Path=/Stocks/ StatusCode=200 ElapsedMilliseconds=15 Got request. Method=GET Path=/Stocks/ Request finished. Method=GET Path=/Stocks/ StatusCode=405 ElapsedMilliseconds=0 Got request. Method=GET Path=/Stocks/1 Request finished. Method=GET Path=/Stocks/1 StatusCode=200 ElapsedMilliseconds=16
Code language: plaintext (plaintext)

There can be multiple middleware functions in the request pipeline. Your middleware function is responsible for passing execution to the next middleware function in the pipeline. To do that, call the passed in RequestDelegate. That’s what the await NextMiddleware(context) line is doing. After that call returns, it means the response is coming back through the pipeline, and your middleware function has a chance to do something with the response.

Option 2 – Add inline middleware

Instead of adding a middleware class to contain the middleware function, an alternative approach is to declare your middleware function directly in Startup.Configure():

public class Startup { //rest of class public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.Use(async (context, NextMiddleware) => { //1 - Operate on the request if (context.Request.Headers.ContainsKey("Debug")) { Console.WriteLine($"Got request. Method={context.Request.Method} Path={context.Request.Path}"); var sw = Stopwatch.StartNew(); //2 - Call the next middleware await NextMiddleware(); //3 - Operate on the response sw.Stop(); Console.WriteLine($"Request finished. Method={context.Request.Method} Path={context.Request.Path} StatusCode={context.Response.StatusCode} ElapsedMilliseconds={sw.ElapsedMilliseconds}"); } }); //rest of method } }
Code language: C# (cs)

This is referred to as an inline middleware function. One problem with this approach is you can’t unit test the middleware function. You may want to only use inline middleware functions for very simple scenarios.

Middleware functions are called in the order they are registered

When you call app.UseMiddleware(), you’re registering the middleware functions. The order that you register them in determines their position in the request pipeline.

Let’s say you have three middleware classes – FirstMiddleware, SecondMiddleware, and ThirdMiddleware – and you register them as follows:

public class Startup { //rest of class public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseMiddleware<FirstMiddleware>(); app.UseMiddleware<SecondMiddleware>(); app.UseMiddleware<ThirdMiddleware>(); //rest of method } }
Code language: C# (cs)

These middleware classes simply log that they got the request and response. When a request comes in, here’s the output:

FirstMiddleware got request. Calling next middleware. SecondMiddleware got request. Calling next middleware. ThirdMiddleware got request. Calling next middleware. ThirdMiddleware got response SecondMiddleware got response FirstMiddleware got response
Code language: plaintext (plaintext)

This shows the order of execution. The middleware functions get executed in the order they are registered in Startup.Configure(). Keep this in mind when you’re dealing with multiple middleware functions.

Modify the response headers

When you want to modify the response headers, you have to use context.Response.OnStarting(), and you have to set it up before calling the RequestDelegate, like this:

public class StatsAppenderMiddleware { private readonly RequestDelegate NextMiddleware; public StatsAppenderMiddleware(RequestDelegate nextMiddleware) { NextMiddleware = nextMiddleware; } public async Task InvokeAsync(HttpContext context) { var sw = Stopwatch.StartNew(); //Modify the response headers before calling the next middleware context.Response.OnStarting(() => { sw.Stop(); context.Response.Headers.Add("Stats", sw.ElapsedMilliseconds.ToString()); return Task.CompletedTask; }); await NextMiddleware(context); } }
Code language: C# (cs)

Notice context.Response.OnStarting() is being set up before calling the next middleware function on line 22.

This middleware function will add a response header with the elapsed execution time in milliseconds:

Stats=155
Code language: plaintext (plaintext)

Leave a Comment