Custom Middleware in ASP.NET Core
The Security Guard Story
Imagine you’re at a big amusement park. Before you can go on any ride, you have to pass through security guards standing at different checkpoints.
Each guard has ONE special job:
- 🎫 First guard checks your ticket
- 📏 Second guard checks your height
- 🎒 Third guard checks your bag
Only after ALL guards say “OK!” can you enjoy the rides!
This is exactly how middleware works in ASP.NET Core!
What is Custom Middleware?
Think of it like this:
Every web request is like a person walking through a hallway. Middleware is like the doors in that hallway. Each door can:
- Check something about you
- Let you pass to the next door
- Or stop you completely!
Custom Middleware = You build your OWN special door with YOUR rules!
graph TD A["Request Arrives"] --> B["Middleware 1"] B --> C["Middleware 2"] C --> D["Your Custom Middleware"] D --> E["Your App"] E --> F["Response Goes Back"]
Two Ways to Create Custom Middleware
Just like there are two ways to make a sandwich (quick way or chef way), there are two ways to create middleware:
| Method | Best For | Difficulty |
|---|---|---|
| Convention-Based | Simple, quick tasks | Easy |
| IMiddleware Interface | Complex, DI-heavy tasks | Medium |
Method 1: Convention-Based Middleware (The Quick Way)
This is like writing a quick note. You just need a class with a special method!
The Recipe:
- Create a class
- Accept
RequestDelegatein constructor - Write an
InvokeorInvokeAsyncmethod
Simple Example:
public class HelloMiddleware
{
private readonly RequestDelegate _next;
public HelloMiddleware(
RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(
HttpContext context)
{
// Do something BEFORE
Console.WriteLine("Hello!");
await _next(context);
// Do something AFTER
Console.WriteLine("Goodbye!");
}
}
What’s Happening Here?
Think of _next as the “next door” in our hallway:
| Part | What It Does |
|---|---|
_next |
Points to next middleware |
InvokeAsync |
Runs when request arrives |
await _next(context) |
Opens the next door |
Registering Your Middleware
You built the door. Now you need to put it in the hallway!
// In Program.cs
app.UseMiddleware<HelloMiddleware>();
Or create a nice extension method:
public static class HelloExtensions
{
public static IApplicationBuilder
UseHello(
this IApplicationBuilder app)
{
return app.UseMiddleware
<HelloMiddleware>();
}
}
// Now use it like this:
app.UseHello();
Method 2: IMiddleware Interface (The Pro Way)
This is like following a strict recipe book. You implement a special interface!
Why Use IMiddleware?
| Feature | Convention-Based | IMiddleware |
|---|---|---|
| Lifetime | Singleton (one instance) | Per-request |
| DI in method | Manual injection | Automatic |
| Best for | Simple tasks | Complex services |
The Recipe:
public class LoggingMiddleware
: IMiddleware
{
private readonly ILogger _logger;
public LoggingMiddleware(
ILogger<LoggingMiddleware> logger)
{
_logger = logger;
}
public async Task InvokeAsync(
HttpContext context,
RequestDelegate next)
{
_logger.LogInformation(
"Request: {Path}",
context.Request.Path);
await next(context);
_logger.LogInformation(
"Response: {Code}",
context.Response.StatusCode);
}
}
IMiddleware Registration (Important!)
With IMiddleware, you MUST register it as a service:
// Step 1: Register as service
builder.Services
.AddTransient<LoggingMiddleware>();
// Step 2: Use in pipeline
app.UseMiddleware<LoggingMiddleware>();
Forget Step 1? You’ll get an error!
graph TD A["Create IMiddleware Class"] --> B["Register in Services"] B --> C["Add to Pipeline"] C --> D["Works!"] A --> E["Skip Registration"] E --> F["Error!"]
Real-World Example: Request Timer
Let’s build something useful! A middleware that tracks how long each request takes:
public class TimerMiddleware
: IMiddleware
{
private readonly ILogger _log;
public TimerMiddleware(
ILogger<TimerMiddleware> log)
{
_log = log;
}
public async Task InvokeAsync(
HttpContext ctx,
RequestDelegate next)
{
var watch = Stopwatch
.StartNew();
await next(ctx);
watch.Stop();
_log.LogInformation(
"{Path} took {Time}ms",
ctx.Request.Path,
watch.ElapsedMilliseconds);
}
}
The Middleware Order Matters!
Remember our security guards? Order matters!
If the height-checker comes before the ticket-checker, things get weird!
// This order...
app.UseAuthentication();
app.UseAuthorization();
app.UseMyCustomMiddleware();
// ...is different from this!
app.UseMyCustomMiddleware();
app.UseAuthentication();
app.UseAuthorization();
graph TD A["Request"] --> B["First Middleware"] B --> C["Second Middleware"] C --> D["Third Middleware"] D --> E["Your Controller"] E --> D D --> C C --> B B --> F["Response"]
Short-Circuiting: Stopping Early
Sometimes a guard says “STOP! You can’t enter!”
Your middleware can do this too:
public async Task InvokeAsync(
HttpContext context)
{
if (context.Request.Path == "/blocked")
{
context.Response.StatusCode = 403;
await context.Response
.WriteAsync("Not allowed!");
return; // Don't call _next!
}
await _next(context);
}
Notice: We don’t call _next. The request stops here!
Quick Comparison Table
| Feature | Convention-Based | IMiddleware |
|---|---|---|
| Signature | InvokeAsync(HttpContext) |
InvokeAsync(HttpContext, RequestDelegate) |
| Constructor | Gets RequestDelegate |
Gets DI services |
| Lifetime | Singleton | Per-request |
| DI Support | Method injection | Constructor injection |
| Registration | Just UseMiddleware |
Service + UseMiddleware |
Common Mistakes to Avoid
| Mistake | Problem | Fix |
|---|---|---|
Forgetting await _next() |
Request never completes | Always await next |
| Wrong order | Auth fails | Check order carefully |
| No service registration | Runtime error | Add AddTransient for IMiddleware |
Key Takeaways
- Middleware = Doors in your request hallway
- Convention-based = Quick and easy
- IMiddleware = Better for complex DI
- Order matters = First registered, first executed
- Short-circuit = Return early to stop the request
You Did It!
Now you understand custom middleware! You can:
- Create your own security guards for requests
- Choose between convention-based and IMiddleware
- Register and order them correctly
- Stop requests when needed
Go build something awesome!
