Middleware
1. What Is Middleware (Core Concept)
In ASP.NET Core, middleware is a series of components that form a request–response pipeline. Every HTTP request must pass through this pipeline, and every response flows back through it in reverse order.
Each middleware component can:
- Read the incoming request
- Modify the request or response
- Call the next middleware
- Stop the pipeline early and return a response
Mental model: Middleware is like checkpoints a request passes before reaching your app logic.
2. Middleware Pipeline Execution Model
Request Flow
Client → Middleware 1 → Middleware 2 → Middleware 3 → App
Response Flow
App → Middleware 3 → Middleware 2 → Middleware 1 → Client
- Middleware executes top → bottom on the request
- Then bottom → top on the response
- Code before
next()runs on the way in - Code after
next()runs on the way out
This is why logging, timing, and error handling work so well in middleware.
3. app.Use vs app.Run (Critical Difference)
app.Use(...)
- Non-terminal middleware
- Receives
next - Must call
await next()to continue the pipeline - Can run logic before and after the next middleware
Typical use cases
- Authentication
- Logging
- Adding headers
- Metrics
app.Use(async (context, next) => {
// before
await next();
// after
});
app.Run(...)
- Terminal middleware
- Does NOT call
next - Ends the pipeline
- Generates the final response
Typical use cases
- Simple endpoints
- Fallback responses
app.Run(async (context) => {
await context.Response.WriteAsync("Done");
});
Important Rule
- Only the first
app.Runwill execute - Anything after it is unreachable
4. Chaining Middleware (Execution Example)
app.Use(async (ctx, next) => {
await ctx.Response.WriteAsync("A ");
await next();
});
app.Use(async (ctx, next) => {
await ctx.Response.WriteAsync("B ");
await next();
});
app.Run(async (ctx) => {
await ctx.Response.WriteAsync("C ");
});
Output
A B C
Why?
- A runs → calls next
- B runs → calls next
- C runs → pipeline ends
- No middleware runs after Run
5. Short-Circuiting the Pipeline
A middleware can stop the pipeline by not calling next().
Common scenarios:
- Authentication failure
- Authorization failure
- Validation errors
- Caching
if (!isAuthorized) {
context.Response.StatusCode = 401;
return; // pipeline stops
}
This is how security middleware works.
6. Custom Middleware — Why & When
Why create custom middleware?
- Reusable logic
- Centralized behavior
- Cleaner controllers
- Cross-cutting concerns
Examples:
- Request logging
- Feature flags
- Custom headers
- Rate limiting
7. Two Ways to Write Custom Middleware
Conventional Middleware (Most Common)
Key features
- Class-based
- Constructor injects
RequestDelegate - Uses
Invokemethod
public class HelloMiddleware
{
private readonly RequestDelegate _next;
public HelloMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await context.Response.WriteAsync("Before\n");
await _next(context);
await context.Response.WriteAsync("After\n");
}
}
Best when:
- You want simplicity
- You don't need DI lifetime control
IMiddleware-Based Middleware
Key features
- Implements
IMiddleware - Registered via DI
- Uses
InvokeAsync
public class MyMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
await next(context);
}
}
Best when:
- You want DI-managed lifetimes
- You need scoped services
8. Extension Methods (Clean Registration)
Always wrap middleware registration in extension methods:
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseHelloMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<HelloMiddleware>();
}
}
This keeps
Program.csclean and readable.
9. Recommended Middleware Order (Very Important)
Typical Order
- Exception handling
- HTTPS redirection
- Static files
- Routing
- CORS
- Authentication
- Authorization
- Endpoints (controllers / minimal APIs)
Why this order?
- Errors should be caught early
- Security before business logic
- Static files should bypass auth
- Authorization needs routing info
Order ≠ random. Each layer depends on the previous one.
10. UseWhen() — Conditional Middleware
UseWhen() allows branching pipelines based on a condition.
app.UseWhen(
(ctx) => ctx.Request.Query.ContainsKey("debug"),
(branch) => {
branch.Use(async (ctx, next) => {
await ctx.Response.WriteAsync("Debug Mode\n");
await next();
});
},
);
- Condition is checked per request
- Branch runs only when condition is true
- After branch, request rejoins main pipeline
Use cases
- Debug-only logic
- Feature toggles
- A/B testing
- Conditional logging
11. Interview-Ready Summary (Say This)
"Middleware in ASP.NET Core is a request–response pipeline. Each middleware can inspect, modify, or short-circuit requests.
Usecontinues the pipeline,Runterminates it. Order matters, and custom middleware helps encapsulate cross-cutting concerns like logging, authentication, and error handling."