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:
- With Request Delegates
- By Convention
- Factory-Based
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
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ConventionMiddleware(
RequestDelegate next,
ILogger logger)
{
_next = next;
}
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 : IMiddleware
{
private readonly ILogger _logger;
public FactoryMiddleware(ILogger logger)
{
_logger = logger;
}
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 theIMiddleware
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
.
P.S. Whenever you're ready, there are 2 ways I can help you:
Pragmatic Clean Architecture: This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. Join 950+ students here.
Patreon Community: Think like a senior software engineer with access to the source code I use in my YouTube videos and exclusive discounts for my courses. Join 820+ engineers here.