ASP.NET Core Middleware

Understand the middleware pipeline in ASP.NET Core and learn to build custom middleware components

ASP.NET Core
Web
Middleware
HTTP
15 min read

Overview

The middleware pipeline is central to ASP.NET Core architecture. Understanding how to build, order, and optimize middleware is crucial for building robust web applications and APIs.

Core Concepts

Middleware Pipeline Flow

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// 1. Exception handling (first - catches all)
app.UseExceptionHandler("/error");

// 2. HTTPS redirection
app.UseHttpsRedirection();

// 3. Static files (short-circuits for static files)
app.UseStaticFiles();

// 4. Routing (matches endpoints)
app.UseRouting();

// 5. CORS (after routing, before auth)
app.UseCors();

// 6. Authentication (who are you?)
app.UseAuthentication();

// 7. Authorization (what can you do?)
app.UseAuthorization();

// 8. Endpoints (terminal - handles request)
app.MapControllers();

app.Run();

Custom Middleware as Method

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

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

    public async Task InvokeAsync(HttpContext context)
    {
        var start = DateTime.UtcNow;

        // Before next middleware
        _logger.LogInformation(
            "Request {Method} {Path} started",
            context.Request.Method,
            context.Request.Path);

        await _next(context);  // Call next middleware

        // After next middleware (response)
        var duration = DateTime.UtcNow - start;
        _logger.LogInformation(
            "Request {Method} {Path} completed in {Duration}ms with status {StatusCode}",
            context.Request.Method,
            context.Request.Path,
            duration.TotalMilliseconds,
            context.Response.StatusCode);
    }
}

// Extension method for easy registration
public static class RequestLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogging(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestLoggingMiddleware>();
    }
}

// Usage
app.UseRequestLogging();

Conditional Middleware

// Branch pipeline for specific paths
app.Map("/api", apiApp =>
{
    apiApp.UseMiddleware<ApiKeyMiddleware>();
    apiApp.MapControllers();
});

// Conditional middleware
app.UseWhen(
    context => context.Request.Path.StartsWithSegments("/admin"),
    adminApp =>
    {
        adminApp.UseMiddleware<AdminAuthMiddleware>();
    });

// MapWhen for more complex conditions
app.MapWhen(
    context => context.Request.Query.ContainsKey("debug"),
    debugApp =>
    {
        debugApp.Use(async (context, next) =>
        {
            context.Response.Headers["X-Debug"] = "true";
            await next();
        });
    });

Bad vs Good Examples

Bad: Incorrect Order

// Bad - auth before routing means routing info not available
app.UseAuthentication();
app.UseAuthorization();
app.UseRouting();
app.MapControllers();

Good: Correct Order

// Good - routing before auth allows endpoint-specific auth
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

Bad: Not Calling Next

// Bad - breaks pipeline, nothing after executes
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello");
    // Forgot to call next() - pipeline stops here!
});

Good: Proper Flow Control

// Good - conditionally short-circuit or continue
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/health")
    {
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync("OK");
        return;  // Short-circuit for health checks
    }

    await next();  // Continue for other requests
});

Interview Tips

Tip 1: Remember the correct middleware order - it's a common interview question. Exception handling first, endpoints last.

Tip 2: Explain that middleware can handle both request (before next()) and response (after next()).

Tip 3: Know the difference between Use (calls next), Run (terminal), and Map (branches).

Common Interview Questions

  1. What is middleware in ASP.NET Core?

    • Components that form a pipeline to process HTTP requests/responses. Each can process request, call next, and process response.
  2. Why does middleware order matter?

    • Each middleware processes request in order, then response in reverse. Auth before routing breaks endpoint-based auth. Exception handler must be first to catch all errors.
  3. What's the difference between Use and Run?

    • Use can call next middleware, Run is terminal (doesn't call next). Use for pipeline components, Run for final handler.
  4. How do you short-circuit the middleware pipeline?

    • Don't call next(). Return immediately after setting response. Useful for health checks, static files, auth failures.
  5. Can middleware be conditionally applied?

    • Yes, using UseWhen, MapWhen, or Map for path-based branching.
  6. What's the purpose of UseRouting and UseEndpoints?

    • UseRouting matches request to endpoint, UseEndpoints executes matched endpoint. Middleware between them can use endpoint metadata (like [Authorize]).
  7. How do you share data between middleware?

    • Use HttpContext.Items dictionary or custom features via HttpContext.Features.