Filters & Middleware
Run code before and after every request. DRY principle for APIs.
The Rule: If you're writing the same code in 10 controllers, you're doing it wrong. Use Middleware or Filters.
1. The Zomato Problem - Why You Need This
Without Middleware: Log request time in 50 controllers. Change log format? Edit 50 files. Miss 1? Bug in prod.
With Middleware: Write logging once in Program.cs. Runs for every request. Change once. Done.
๐ฏ Real Example
Every Zomato API call logs: UserId, Endpoint, Time, StatusCode. 1 Middleware handles it for all 500 APIs. That's why their debugging is fast.
2. Middleware vs Filters - The 10 Second Decision
| Middleware | Filters | |
|---|---|---|
| Runs When | Every HTTP request. Even if no controller. | Only when request hits MVC/API controller. |
| Order | Configured in Program.cs. You control exact order. | Fixed pipeline: Auth โ Action โ Result. |
| Access To | HttpContext. Raw request/response. | ActionContext. Knows controller, action, model. |
| Use For | Logging, Exceptions, Auth, CORS, Static Files | Validation, Caching, Custom Auth per action |
Rule: App-wide concerns = Middleware. Controller-specific = Filter.
3. Code It: Global Exception Middleware
Goal: If any controller throws exception, return clean JSON {"error":"Server failed"} instead of HTML crash page.
// Middleware/GlobalExceptionMiddleware.cs
public class GlobalExceptionMiddleware {
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger logger) {
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context) {
try {
await _next(context); // Call next middleware/controller
}
catch (Exception ex) {
_logger.LogError(ex, "Unhandled exception");
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"error\":\"An internal server error occurred\"}");
}
}
}
// Program.cs - Order matters!
var app = builder.Build();
app.UseMiddleware(); // 1st - catches all errors
app.UseAuthentication(); // 2nd
app.UseAuthorization(); // 3rd
app.MapControllers(); // Last
app.Run();
Test: Throw new Exception() in any controller. API returns 500 JSON, not HTML. Logs error.
4. Code It: Action Filter for Logging
Goal: Log execution time for specific slow actions only.
// Filters/LogTimeAttribute.cs
public class LogTimeAttribute : ActionFilterAttribute {
private Stopwatch _timer;
public override void OnActionExecuting(ActionExecutingContext context) {
_timer = Stopwatch.StartNew();
}
public override void OnActionExecuted(ActionExecutedContext context) {
_timer.Stop();
var logger = context.HttpContext.RequestServices.GetService>();
logger?.LogInformation($"Action {context.ActionDescriptor.DisplayName} took {_timer.ElapsedMilliseconds}ms");
}
}
// Controller usage
[HttpGet("slow-report")]
[LogTime] // Only this action gets timed
public IActionResult GetSlowReport() {...}
Career-Killer Mistake:
Result: Exception handler never runs. Controllers throw โ crash โ HTML error page.
Fix: Middleware is a pipeline. Order is critical. Exception handler must be FIRST. Auth must be before Controllers. Request flows down, response flows up.
app.UseExceptionHandler() AFTER app.MapControllers() Result: Exception handler never runs. Controllers throw โ crash โ HTML error page.
Fix: Middleware is a pipeline. Order is critical. Exception handler must be FIRST. Auth must be before Controllers. Request flows down, response flows up.
Quick Check ๐ง
Middleware mastered. Next: CORS & Security Headers - Fix the "CORS error" every React dev hits, and add headers that get you A+ on security scans. Continue โ
No comments yet. Be the first to share your thoughts!