🔗 The Request Pipeline Mystery
Your API is slow. Users complain. You add logging… but where? You need auth checks, rate limiting, error handling. They’re scattered everywhere. Sound chaotic?
Understanding the Middleware Pipeline
🎯 What Middleware Actually Does
Request comes in → ├─ Logging Middleware (start timer) │ ├─ Auth Middleware (check JWT) │ │ ├─ Rate Limiting Middleware (check quota) │ │ │ ├─ Exception Handling Middleware │ │ │ │ ├─ Your Controller Action │ │ │ │ └─ Response generated │ │ │ └─ Log errors if any │ │ └─ Check rate limit │ └─ Validate auth └─ Log request duration Response goes out ← Each middleware can: 1. Do something BEFORE next middleware 2. Call next middleware (or short-circuit) 3. Do something AFTER next middleware
🛠️ Custom Middleware Example: Request Timing
// RequestTimingMiddleware.cs
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public RequestTimingMiddleware(
RequestDelegate next,
ILogger logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
var requestPath = context.Request.Path;
try
{
// BEFORE: Log request start
_logger.LogInformation("Request started: {Method} {Path}",
context.Request.Method, requestPath);
// Call next middleware
await _next(context);
// AFTER: Log success
stopwatch.Stop();
_logger.LogInformation(
"Request completed: {Method} {Path} - {StatusCode} in {Duration}ms",
context.Request.Method,
requestPath,
context.Response.StatusCode,
stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex,
"Request failed: {Method} {Path} after {Duration}ms",
context.Request.Method,
requestPath,
stopwatch.ElapsedMilliseconds);
throw;
}
}
}
// Extension method for clean registration
public static class RequestTimingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestTiming(
this IApplicationBuilder builder)
{
return builder.UseMiddleware();
}
}
// Program.cs
app.UseRequestTiming();
🔐 Production Middleware: API Key Validation
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _config;
public ApiKeyMiddleware(RequestDelegate next, IConfiguration config)
{
_next = next;
_config = config;
}
public async Task InvokeAsync(HttpContext context)
{
// Skip auth for health checks
if (context.Request.Path.StartsWithSegments("/health"))
{
await _next(context);
return;
}
// Check for API key in header
if (!context.Request.Headers.TryGetValue("X-API-Key", out var apiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new
{
error = "API Key required",
hint = "Add X-API-Key header"
});
return; // Short-circuit! Don't call next middleware
}
// Validate API key
var validKey = _config["ApiKey"];
if (apiKey != validKey)
{
context.Response.StatusCode = 403;
await context.Response.WriteAsJsonAsync(new { error = "Invalid API Key" });
return;
}
// API key valid, continue pipeline
await _next(context);
}
}
🎨 The Perfect Middleware Order (Critical!)
// Program.cs
var app = builder.Build();
// 1. FIRST: Exception handling (catch everything)
app.UseExceptionHandler("/error");
// 2. HTTPS redirect
app.UseHttpsRedirection();
// 3. Static files (before auth - public content)
app.UseStaticFiles();
// 4. Routing (determines which endpoint)
app.UseRouting();
// 5. CORS (before auth!)
app.UseCors();
// 6. Authentication (who are you?)
app.UseAuthentication();
// 7. Authorization (what can you do?)
app.UseAuthorization();
// 8. Custom middleware
app.UseRequestTiming();
app.UseMiddleware();
// 9. Rate limiting
app.UseRateLimiter();
// 10. LAST: Endpoints
app.MapControllers();
app.Run();
// Order matters! Auth before authorization, timing before everything
| Middleware | Purpose | Order Priority |
|---|---|---|
| Exception Handler | Catch all unhandled errors | 1st (Top) |
| Static Files | Serve JS, CSS, images | Early |
| CORS | Handle cross-origin requests | Before Auth |
| Authentication | Validate JWT, cookies | Middle |
| Authorization | Check permissions | After Auth |
| Endpoints | Your controllers | Last (Bottom) |
💡 Advanced Pattern: Conditional Middleware
// Only apply middleware to specific paths
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/api"),
appBuilder =>
{
appBuilder.UseMiddleware();
appBuilder.UseRateLimiter();
}
);
// Apply middleware to everything EXCEPT certain paths
app.UseWhen(
context => !context.Request.Path.StartsWithSegments("/public"),
appBuilder => appBuilder.UseAuthentication()
);
⚠️ Common Mistakes
- Wrong order: Auth after endpoints = auth never runs
- Forgetting await _next(): Pipeline stops, request hangs
- Exception in middleware: Use try-catch or exception handler
- Heavy operations: Middleware runs on EVERY request – keep it fast
- Modifying response after next(): Response may be committed already
“We moved auth, logging, and rate limiting into middleware. Removed 2000+ lines of duplicate code from controllers. Request pipeline went from spaghetti to surgical.”
🎯 The Power of Middleware
1
Place
Not 50 controllers
✓
Every Request
Consistent behavior
0
Duplication
DRY principle
