Middlewares

Intro

ASP.NET Core middleware are components that form the HTTP request pipeline. Each middleware wraps the next like nested layers, processing requests on the way in and responses on the way out. You reach for middleware when a concern must apply to all (or most) requests regardless of which controller or endpoint handles them — logging, authentication, CORS, compression, and exception handling are canonical examples.

Each middleware receives an HttpContext and a RequestDelegate (next). On the way in, it can inspect or modify the request before calling next. On the way out, it can inspect or modify the response. Any middleware can short-circuit by returning a response without calling next — for example, UseAuthentication can reject an unauthenticated request before it ever reaches routing.

sequenceDiagram
    participant C as Client
    participant EH as ExceptionHandler
    participant Auth as Authentication
    participant R as Routing
    participant AZ as Authorization
    participant EP as Endpoint

    C->>EH: Request
    EH->>Auth: next
    Auth->>R: next
    R->>AZ: next
    AZ->>EP: next
    EP-->>AZ: Result
    AZ-->>R: Response
    R-->>Auth: Response
    Auth-->>EH: Response
    EH-->>C: Response

    Note over C,EP: Short-circuit example
    C->>EH: Request with bad token
    EH->>Auth: next
    Auth-->>EH: 401 Unauthorized
    EH-->>C: 401 Unauthorized

Order matters: middleware registered first runs first on the way in and last on the way out. The recommended order in Program.cs is:

app.UseExceptionHandler("/error");
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

Writing Custom Middleware

The simplest form is an inline lambda:

app.Use(async (ctx, next) =>
{
    var sw = System.Diagnostics.Stopwatch.StartNew();
    try
    {
        await next(ctx);
    }
    finally
    {
        sw.Stop();
        app.Logger.LogInformation("{Method} {Path} -> {StatusCode} in {ElapsedMs} ms",
            ctx.Request.Method,
            ctx.Request.Path,
            ctx.Response.StatusCode,
            sw.ElapsedMilliseconds);
    }
});

For reusable middleware, use a class with the conventional pattern:

public sealed class CorrelationIdMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<CorrelationIdMiddleware> _logger;

    public CorrelationIdMiddleware(RequestDelegate next, ILogger<CorrelationIdMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
            ?? Guid.NewGuid().ToString("N");

        context.Response.Headers["X-Correlation-Id"] = correlationId;
        using (_logger.BeginScope(new Dictionary<string, object> { ["CorrelationId"] = correlationId }))
        {
            await _next(context);
        }
    }
}

// Registration
app.UseMiddleware<CorrelationIdMiddleware>();

Pitfalls

Wrong registration order — placing UseAuthorization before UseAuthentication means the identity is never populated, so all requests appear anonymous. The canonical order (exception handler → HSTS → static files → routing → CORS → auth → authorization → endpoints) exists for a reason.

Modifying the response after it has started — once Response.HasStarted is true, headers and status code are already sent. Writing to them throws. Check context.Response.HasStarted before any post-next response modification.

Blocking I/O in synchronous middleware — calling Thread.Sleep or synchronous file/DB operations blocks a thread-pool thread for the duration. Use async/await throughout.

Swallowing exceptions silently — a try/catch in middleware that logs and returns 200 hides failures from callers and monitoring. Either rethrow or return an appropriate error status.

Tradeoffs

Option Best for Weakness
Middleware App-wide cross-cutting concerns (logging, auth, exception handling, CORS) No direct MVC action context; runs for all requests including static files
MVC action filters Concerns tied to controllers/actions and model/action context Only applies to MVC pipeline; not available for Minimal APIs
Endpoint filters Minimal API endpoint-scoped behavior Not used by MVC controllers

Decision rule: use middleware when the concern must apply before routing or to all request types. Use filters when you need ActionExecutingContext, action arguments, or action result wrapping.

Questions


Whats next