3 Ways To Create Middleware In ASP.NET Core

Milan Jovanović - Sep 10 '23 - - Dev Community

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.
});
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

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 an HttpContext argument
  • We need to invoke the RequestDelegate and pass it the HttpContext 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>();
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

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>();
Enter fullscreen mode Exit fullscreen mode

And like the previous example, we need to tell our application to use our factory-based middleware:

app.UseMiddleware<FactoryMiddleware>();
Enter fullscreen mode Exit fullscreen mode

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:

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

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .