Middleware is the very heart of how ASP.NET Core processes HTTP requests. For an expert architect, understanding the pipeline is the difference between a secure, scalable app and a buggy, vulnerable mess.
Think of middleware as layers of an onion. A request goes through each layer to the center (the Endpoint) and then back out through the same layers as a response. This allows each component to perform logic Before and After the actual request execution.
| Order | Middleware | Expert Purpose |
|---|---|---|
| 1st | Exception Handler | Catches any crashes in the following layers. |
| 2nd | HSTS / HTTPS | Forces secure connections before any data is sent. |
| 3rd | Static Files | Instantly returns images/CSS/JS without hitting the MVC engine. |
| 4th | Routing | Determines which controller/action will handle the request. |
| 5th | CORS | Handles Cross-Origin security policies. |
| 6th | Authentication | Identifies **Who** the user is (JWT, Cookies). |
| 7th | Authorization | Determines **What** the user can do (Roles, Claims). |
| 8th | Endpoints | The final destination: Your Controller Actions. |
When you build custom middleware, performance is king. One slow line of code here affects **100%** of your application's traffic.
public class RequestDiagnosticMiddleware {
private readonly RequestDelegate _next;
public RequestDiagnosticMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context, ILogger logger) {
var sw = Stopwatch.StartNew();
// --- LOGIC BEFORE ---
var startMem = GC.GetTotalMemory(false);
await _next(context); // Pass to the next middleware!
// --- LOGIC AFTER ---
sw.Stop();
var endMem = GC.GetTotalMemory(false);
if (sw.ElapsedMilliseconds > 200) {
logger.LogWarning($"Slow Request: {context.Request.Path} took {sw.ElapsedMilliseconds}ms and used {endMem - startMem} bytes.");
}
}
}
A senior architect doesn't always want every middleware to run for every request. We use branching to keep the pipeline "Slim".
Middleware is instantiated as a **Singleton** when the app starts. This means you **CANNOT** inject a Scoped service (like a Database Context) into the Middleware constructor! If you do, you will create a Captive Dependency—a common bug that causes data corruption across multiple web requests. Instead, inject Scoped services directly into the InvokeAsync method parameters.
Instead of wrapping every single controller action in a try/catch block (a common Junior code smell), a 12-year architect uses Global Exception Handling Middleware. This ensures that any crash in your app is logged and a friendly error page is shown—all in **one single place**.
public class GlobalExceptionHandlerMiddleware {
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger;
public GlobalExceptionHandlerMiddleware(RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware> logger) {
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context) {
try {
await _next(context); // Pass the request forward!
} catch (Exception ex) {
_logger.LogError($"CRITICAL ERROR: {ex.Message} at {context.Request.Path}");
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception ex) {
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var response = new {
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error from Global Handler.",
DetailedMessage = ex.InnerException?.Message ?? ex.Message
};
await context.Response.WriteAsJsonAsync(response);
}
}