Middleware is the backbone of the ASP.NET Core request pipeline. Understanding middleware deeply is essential for building robust, secure, and high-performance web applications — and it's one of the most asked topics in .NET interviews.
📌 What Exactly is Middleware?
Middleware is software that is assembled into an application pipeline to handle HTTP requests and responses. Each middleware component can:
- ✅ Execute logic before the next middleware.
- ✅ Pass the request to the next middleware (or short-circuit).
- ✅ Execute logic after the next middleware (on the way back).
- ✅ Short-circuit the pipeline to prevent further processing.
🔁 How the Pipeline Works
- Request flows from left (first middleware) to right (last middleware).
- Response flows back from right to left.
- Each middleware can decide whether to call the next middleware using
next()or return early.
🧩 Types of Middleware (Interview Ready)
- Built-in Middleware – Provided by ASP.NET Core.
Example:UseAuthentication,UseAuthorization,UseStaticFiles,UseExceptionHandler,UseRouting. - Custom Middleware – Developer-created for specific cross-cutting concerns.
Use cases: Logging, request/response modification, tenant resolution, IP whitelisting, custom headers. - Third-party Middleware – From external NuGet packages.
Examples: Serilog (logging), Swagger (API docs), Health Checks, Rate limiting. - Terminal Middleware – The last middleware that produces a response and doesn't call
next.
Examples:Run(), MVC endpoints, static files when found.
🛠️ Use, Map, and Run Methods – Explained
- Use() – Adds middleware that can call the next delegate. Most common.
- Run() – Adds terminal middleware (no
nextparameter). - Map() / MapWhen() – Branches the pipeline based on request path or condition.
Code Example – All Three
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Use - can call next
app.Use(async (context, next) => {
Console.WriteLine("Before next");
await next();
Console.WriteLine("After next");
});
// Map - branch for specific path
app.Map("/admin", adminApp => {
adminApp.Run(async context => {
await context.Response.WriteAsync("Admin area");
});
});
// Run - terminal middleware
app.Run(async context => {
await context.Response.WriteAsync("Hello from terminal");
});
📋 Common Built-in Middlewares (Purpose & Order)
| Middleware | Purpose | When to place |
|---|---|---|
| UseExceptionHandler | Global exception handling & fallback | First (early capture) |
| UseHsts | Enforce HTTPS Strict Transport Security | Early (security) |
| UseHttpsRedirection | Redirect HTTP → HTTPS | Early |
| UseStaticFiles | Serve static files (CSS, JS, images) | Before auth (performance) |
| UseRouting | Match request to endpoint | After static files, before auth |
| UseAuthentication | Identify user | After routing, before authz |
| UseAuthorization | Check permissions | After authentication |
| UseResponseCaching | Cache responses | Before auth if public cache |
| UseCors | Cross-Origin Resource Sharing | Before auth & routing |
| MapControllers / Endpoints | Execute MVC/API actions | Last |
⚠️ Why Order Matters – Critical for Interviews
✅ Correct Order (Recommended)
ExceptionHandler → HSTS → HTTPS Redirection → StaticFiles → Routing → CORS → Authentication → Authorization → Endpoints
❌ Incorrect Order (Problems you will face)
- UseStaticFiles after UseAuthorization → Secure files might be blocked incorrectly.
- UseExceptionHandler at the end → Exceptions won't be caught.
- UseAuthentication after UseAuthorization → User not identified when authorizing (throws).
- UseRouting after UseAuthentication → Authentication might not have route data.
🎯 Interview Question: "What happens if I put UseAuthentication after UseAuthorization?"
Answer: The authorization middleware will execute before authentication, meaning the user identity will not be established. This typically throws an InvalidOperationException because authorization depends on authentication results. Always place UseAuthentication before UseAuthorization.
📦 Real Example: Complete Program.cs (Modern .NET 8/9)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles(); // Serve wwwroot files
app.UseRouting();
app.UseCors("AllowSpecific"); // After routing, before auth
app.UseAuthentication(); // Who are you?
app.UseAuthorization(); // Are you allowed?
app.MapControllers(); // Terminal middleware
app.Run();
🛡️ Creating Custom Middleware – Step by Step
Custom middleware is great for logging, request validation, tenant detection, etc.
Approach 1: Inline Middleware (for simple cases)
app.Use(async (context, next) => {
Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
await next();
Console.WriteLine($"Response: {context.Response.StatusCode}");
});
Approach 2: Dedicated Middleware Class (recommended)
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation("Handling request: {Path}", context.Request.Path);
await _next(context); // Call next middleware
_logger.LogInformation("Finished request with status: {StatusCode}", context.Response.StatusCode);
}
}
// Register in Program.cs
app.UseMiddleware<RequestLoggingMiddleware>();
🔥 Short-circuiting Example (IP Blocking)
app.Use(async (context, next) => {
var blockedIp = "192.168.1.100";
if (context.Connection.RemoteIpAddress?.ToString() == blockedIp)
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Forbidden");
return; // short-circuit – does NOT call next()
}
await next();
});
🧠 Interview Questions & Answers (Must Know)
Q1: What is the difference between Use() and Run()?
A: Use() expects a next delegate and can pass control to the next middleware. Run() does not have a next parameter and terminates the pipeline (terminal middleware).
Q2: How do you conditionally branch the pipeline?
A: Using Map() (based on path) or MapWhen() (based on predicate). Example: app.Map("/api", apiApp => { ... });
Q3: Can middleware run after the response has started?
A: Once the response has started (headers sent), modifying response body may throw. Always check context.Response.HasStarted before writing.
Q4: How to handle exceptions globally?
A: Use UseExceptionHandler as the first middleware. It catches exceptions from subsequent middleware and can re-execute the pipeline on an error route.
Q5: What is the difference between app.UseAuthorization() and adding [Authorize] attributes?
A: UseAuthorization adds the authorization middleware to the pipeline. [Authorize] attributes are policy/requirements evaluated by that middleware. Both are required – middleware enables the feature, attributes define rules.
✅ Best Practices (For Production & Interviews)
- Order matters – exceptions first, security early, endpoints last.
- Single responsibility – each middleware does one thing.
- Don't block async – always use
await next()and async signatures. - Handle exceptions early –
UseExceptionHandlershould be first. - Use built-in middleware instead of reinventing (CORS, caching, static files).
- Document custom middleware order in large teams.
- Be careful with short-circuiting – ensure you don't accidentally skip required middleware (e.g., auth).
🎯 Final Key Takeaway
Middleware is the heart of ASP.NET Core request processing. Mastering Use, Map, Run and the correct middleware order is critical for building secure, maintainable, and high-performance applications. In interviews, expect scenario-based questions like "Arrange these middlewares in correct order" or "Write a custom logging middleware".
✅ Bookmark this guide for your next .NET interview or real-world project!