DEV Community

Cover image for The Middleware Pipeline in ASP.NET Core — How It Really Works
Libin Tom Baby
Libin Tom Baby

Posted on

The Middleware Pipeline in ASP.NET Core — How It Really Works

Use, Run, Map, short-circuit, ordering, custom middleware

Every HTTP request in ASP.NET Core passes through a pipeline of middleware components before reaching your controller.

Understanding this pipeline is essential — it's where logging, authentication, exception handling, CORS, routing, and response caching all live.


What Is Middleware?

Middleware is software assembled into a pipeline to handle requests and responses.

Each middleware component:

  1. Receives the incoming HttpContext
  2. Optionally does something with the request
  3. Calls the next middleware in the chain
  4. Optionally does something with the response on the way back
Request  → [Logging] → [Auth] → [CORS] → [Routing] → [Handler]
Response ← [Logging] ← [Auth] ← [CORS] ← [Routing] ← [Handler]
Enter fullscreen mode Exit fullscreen mode

The Three Methods: Use, Run, Map

Use — calls the next component

app.Use(async (context, next) =>
{
    Console.WriteLine("Before next middleware");
    await next(context);
    Console.WriteLine("After next middleware");
});
Enter fullscreen mode Exit fullscreen mode

Run — terminal, does NOT call next

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello — pipeline stops here");
});
Enter fullscreen mode Exit fullscreen mode

Map — branches based on path

app.Map("/health", branch =>
{
    branch.Run(async context =>
    {
        await context.Response.WriteAsync("Healthy");
    });
});
Enter fullscreen mode Exit fullscreen mode

Ordering Matters — A Lot

The order you register middleware is the order it runs.

app.UseExceptionHandler();    // 1 — catch all unhandled exceptions first
app.UseHttpsRedirection();    // 2
app.UseCors();                // 3
app.UseAuthentication();      // 4 — who are you?
app.UseAuthorization();       // 5 — what can you do?
app.UseRateLimiter();         // 6
app.MapControllers();         // 7 — route to controller
Enter fullscreen mode Exit fullscreen mode

Getting the order wrong causes real bugs. Authentication before exception handling means auth errors won't be caught cleanly.


Short-Circuiting the Pipeline

Middleware can stop the request from proceeding further.

app.Use(async (context, next) =>
{
    if (!context.Request.Headers.ContainsKey("X-API-Key"))
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Missing API key");
        return; // ← Does NOT call next — pipeline stops here
    }

    await next(context);
});
Enter fullscreen mode Exit fullscreen mode

This is how authentication middleware works — validate the token, let through or short-circuit with 401.


Building Custom Middleware

Inline (simple cases)

app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Custom-Header", "MyApp");
    await next(context);
});
Enter fullscreen mode Exit fullscreen mode

Class-based (preferred for complex middleware)

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

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

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();

        await _next(context);

        stopwatch.Stop();
        _logger.LogInformation(
            "Request {Method} {Path} completed in {ElapsedMs}ms",
            context.Request.Method,
            context.Request.Path,
            stopwatch.ElapsedMilliseconds);
    }
}

// Extension method
public static class RequestTimingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder app)
        => app.UseMiddleware<RequestTimingMiddleware>();
}

// Usage
app.UseRequestTiming();
Enter fullscreen mode Exit fullscreen mode

Real-World Scenarios

Logging every request and response

app.Use(async (context, next) =>
{
    _logger.LogInformation("Incoming: {Method} {Path}",
        context.Request.Method, context.Request.Path);

    await next(context);

    _logger.LogInformation("Outgoing: {StatusCode}",
        context.Response.StatusCode);
});
Enter fullscreen mode Exit fullscreen mode

Adding a correlation ID to every response

app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString());
    await next(context);
});
Enter fullscreen mode Exit fullscreen mode

Interview-Ready Summary

  • Middleware forms a bidirectional pipeline — request goes in, response comes back
  • Use = calls next, Run = terminal, Map = branch by path
  • Order of registration = order of execution
  • Short-circuiting stops the pipeline — used by auth, rate limiting, validation
  • Custom middleware goes in a class implementing InvokeAsync(HttpContext)
  • Exception handling middleware should be first (outermost) so it catches everything

A strong interview answer:

"ASP.NET Core middleware forms a pipeline where each component can process the request before passing it to the next, and then process the response on the way back. The order matters — exception handling goes first, then CORS, then authentication, then routing. You can short-circuit the pipeline at any point, which is how auth middleware rejects unauthorised requests."

Top comments (0)