HomeNikola Knezevic
Banner

Middlewares in ASP.NET Core

19 Feb 2025
5 min

Sponsor Newsletter

When building APIs, handling HTTP requests typically requires multiple steps, such as authentication, logging, error handling and more.

Rather than repeating logic across multiple parts of the system, middlewares allow you to process requests in a maintainable way.

Middleware offers a centralized mechanism for managing cross-cutting concerns within an application by intercepting and processing HTTP requests and responses in a pipeline.

Middleware Pipeline

The middleware pipeline is a sequence of middlewares that are executed one after the other.

Each middleware handles its specific concern and then passes the request to the next middleware in the pipeline.

Once the response is generated, it travels back through the previous middlewares in reverse order.

Description of Image

This structure means that each middleware can perform tasks both before and after the next middleware, which makes the order of execution crucial.

For instance, middleware that handles exceptions should be placed early in the pipeline to catch any errors that may occur later.

Built-in Middlewares

.NET provides many built-in middleware components that you might find useful in your projects. Here are a few that I regularly use:

  • UseExceptionHandler
  • UseHttpsRedirection
  • UseHttpLogging
  • UseRouting
  • UseCors
  • UseAuthentication
  • UseAuthorization

However, built-in middleware is often not enough, so we frequently need to create custom middleware. .NET provides three ways to implement custom middleware:

  • Delegate-Based Middleware
  • Convention-Based Middleware
  • Factory-Based Middleware

Delegate-Based Middlewares

Delegate-based middleware is the simplest way to define middleware in ASP.NET Core. It allows you to define inline logic directly in the request pipeline using app.Use():

csharp
app.Use(async (context, next) =>
{
    if (!context.Request.Headers.ContainsKey("X-Required-Header"))
    {   
        context.Response.StatusCode = StatusCodes.Status400BadRequest;
        await context.Response.WriteAsync("Missing X-Required-Header");
        return;
    }
    
    await next.Invoke();
});
  • Context is the HttpContext, which represents the current HTTP request and response, providing access to request headers, query parameters, response status and body.
  • Next is a RequestDelegate that represents the next middleware in the pipeline.

In this example, our custom middleware checks if incoming HTTP requests contain the required header.

If the header is missing, the middleware will set response status code to 400, if the request includes required header, the middleware forwards the request to the next step in the pipeline.

Convention-Based Middlewares

With convention-based middleware, you can create middleware by following predefined conventions.

To create one, your class should inject a RequestDelegate and implement an InvokeAsync method that takes an HttpContext as an argument:

csharp
public class ValidateHeaderConventionMiddleware
{
    private readonly RequestDelegate _next;

    public ValidateHeaderConventionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {    
        if (!context.Request.Headers.ContainsKey("X-Required-Header"))
        {   
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
            await context.Response.WriteAsync("Missing X-Required-Header");
            return;
        }
    
        await _next.Invoke(context);
    }
}

Once the middleware is defined, you need to use the UseMiddleware method to register it with your application:

csharp
app.UseMiddleware<ValidateHeaderConventionMiddleware>();

FactoryBasedMiddlewares

Lastly, my preferred approach to creating middlewares, factory-based middlewares.

Factory-based middlewares rely on the IMiddleware interface, which includes the InvokeAsync method with HttpContext and RequestDelegate as parameters.

csharp
public class ValidateHeaderFactoryMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {    
        if (!context.Request.Headers.ContainsKey("X-Required-Header"))
        {   
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
            await context.Response.WriteAsync("Missing X-Required-Header");
            return;
        }
    
        await next.Invoke(context);
    }
}

If you choose this approach, you must first register the middleware in the DI container and then add it to your application’s pipeline:

csharp
builder.Services.AddTransient<ValidateHeaderFactoryMiddleware>();
csharp
app.UseMiddleware<ValidateHeaderFactoryMiddleware>();

Conclusion

Middleware simplifies the handling of HTTP requests by centralizing cross-cutting concerns.

The middleware pipeline processes requests in sequence, with the execution order determined by the registration sequence.

.NET provides many built-in middlewares to address common requirements. When custom middleware is needed, you can create it using one of three approaches.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.