In this newsletter, we'll be covering three ways to create middleware in ASP.NET Core applications.
Middleware allows us to introduce additional logic before or after executing an HTTP request.
You are already using many of the built-in middleware available in the framework.
I'm going to show you three approaches to how you can define custom middleware:
Let's go over each of them and see how we can implement them in code.
Adding Middleware With Request Delegates
The first approach to defining a middleware is by writing a Request Delegate.
You can do that by calling the Use
method on the WebApplication
instance
and providing a lambda method with two arguments.
The first argument is the HttpContext
and the second argument is
the actual next request delegate in the pipeline RequestDelegate
.
Here's what this would look like:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use(async (context, next) =>
{
// Add code before request.
await next(context);
// Add code after request.
});
By awaiting the next
delegate, you are continuing the request pipeline execution.
You can short-circuit the pipeline by not invoking the next
delegate.
This overload of the Use
method is the one suggested by Microsoft.
Adding Middleware By Convention
The second approach requires us to create a class that will represent our middleware. We have to follow the convention when creating this class so that we can use it as middleware in our application.
I'm first going to show you what this class looks like, and then explain what is the convention we are following here.
Here's how this class would look like:
public class ConventionMiddleware(
RequestDelegate next,
ILogger<ConventionMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
logger.LogInformation("Before request");
await next(context);
logger.LogInformation("After request");
}
}
The convention we are following has a few rules:
- We need to inject a
RequestDelegate
in the constructor - We need to define an
InvokeAsync
method with anHttpContext
argument - We need to invoke the
RequestDelegate
and pass it theHttpContext
instance
There's one more thing that's required, and that is to tell our application to use this middleware.
We can do that by calling the UseMiddleware
method:
app.UseMiddleware<ConventionMiddleware>();
And with this, we have a functioning middleware.
Adding Factory-Based Middleware
The third and last approach requires us to also create a class that will represent our middleware.
However, this time we're going to implement the IMiddleware
interface.
This interface has only one method - InvokeAsync
.
Here's what this class would like:
public class FactoryMiddleware(ILogger<FactoryMiddleware> logger) : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
logger.LogInformation("Before request");
await next(context);
logger.LogInformation("After request");
}
}
The FactoryMiddleware
class will be resolved at runtime from dependency injection.
Because of this, we need to register it as a service:
builder.Services.AddTransient<FactoryMiddleware>();
And like the previous example, we need to tell our application to use our factory-based middleware:
app.UseMiddleware<FactoryMiddleware>();
With this, we have a functioning middleware.
A Word On Strong Typing
I'm a big fan of strong typing whenever possible.
Out of the three approaches I just showed you, the one using the
IMiddleware
interface satisfies this constraint the most.
This is also my preferred way to implement middleware.
Since we're implementing an interface, it's very easy to create a generic solution to never forget to register your middleware.
You can use reflection to scan for classes implementing
the IMiddleware
interface and add them to dependency injection,
and also add them to the application by calling UseMiddleware
.