Middleware in .NET?
In ASP.NET Core, a middleware is a component in the request/response pipeline that handles:
- HTTP requests before they reach the endpoint
- HTTP responses after they leave the endpoint
Middlewares can:
- Process the request
- Short-circuit the pipeline
- Call the next middleware
- Modify the response
Middleware Execution Flow:
Request -> Middleware A -> Middleware B -> Middleware C -> Controller
<- Post-C -> Post-B -> Post-A -> Response
Code before _next() runs on the way in
Code after _next() runs on the way out
If any middleware short-circuits, downstream + post logic won’t run
Middleware Example
public class MyCustomMiddleware
{
private readonly RequestDelegate _next;
public MyCustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
Console.WriteLine("Before Request");
await _next(context); // Call the next middleware
Console.WriteLine("After Response");
}
}
// Register middleware
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<MyCustomMiddleware>();
}
Built-in Middlewares in ASP.NET Core
| Middleware | Purpose |
|---|---|
UseRouting() | Matches route patterns |
UseAuthentication() | Validates user credentials |
UseAuthorization() | Checks if user has permission |
UseStaticFiles() | Serves static files (CSS, JS) |
UseExceptionHandler() | Global error handling |
When to use :
| Use Case | Why Middleware is Good |
|---|---|
| Logging requests/responses | Captures input/output for debugging |
| Global error handling | Centralizes exception processing |
| Authentication and authorization | Controls access before reaching controller |
| Request timing | Track how long a request took |
| IP blocking / rate limiting | Validate client info early |
When not to use:
| Use Case | Why Not Middleware |
|---|---|
| Data validation (model validation) | Use filters or model binding instead |
| Business logic processing | Use services, controllers |
| Dependency-heavy logic | Middleware is initialized once, avoid complex logic requiring scoped dependencies |
| Returning UI responses | Views are better handled by controllers or Razor pages |
Scenarios:
- Short Circuiting Stop the pipeline and return a custom response
if (!context.Request.Headers.ContainsKey("X-API-KEY"))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("API Key Missing");
return; // Don't call next middleware
}
- What happens to the code after
await _next(context)?
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
public LoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
Console.WriteLine("Before next()");
await _next(context); // Passes control to next middleware
Console.WriteLine("After next()"); // Executes after the response is sent
}
}
Answer: YES, the code after await _next(context) will execute, only if the next middleware and the rest of the pipeline completes successfully (i.e., no short-circuit or unhandled exception).
Before next()
[other middleware / controller executes]
After next()
- What if the next middleware short-circuits the pipeline?
// Middleware A
public class FirstMiddleware
{
private readonly RequestDelegate _next;
public FirstMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
Console.WriteLine("Middleware A - Before");
await _next(context);
Console.WriteLine("Middleware A - After"); // Will NOT run if B short-circuits
}
}
// Middleware B
public class SecondMiddleware
{
public async Task InvokeAsync(HttpContext context)
{
Console.WriteLine("Middleware B - Short-circuiting");
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Access Denied");
// _next not called — pipeline ends here
}
}
Answer: Then, the code after _next() in previous middleware will NOT execute.
Middleware A - Before
Middleware B - Short-circuiting
- Can you modify the response after
_next(context)?
public class TimerMiddleware
{
private readonly RequestDelegate _next;
public TimerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
await _next(context);
sw.Stop();
Console.WriteLine($"Request took {sw.ElapsedMilliseconds} ms");
context.Response.Headers.Add("X-Elapsed-Time", sw.ElapsedMilliseconds.ToString());
}
}
Answer: Yes. After await _next(context), you can modify the response headers, log status codes, etc.
- Can middleware be conditional (execute only on certain routes)?
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/api/secure"))
{
// Do special logging or validation
}
await _next(context);
}
Yes, by checking context.Request.Path
Unit testing the middle ware
public class CustomHeaderMiddleware
{
private readonly RequestDelegate _next;
public CustomHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("X-Test-Header", "HelloUnitTest");
return Task.CompletedTask;
});
await _next(context);
}
}
public class CustomHeaderMiddlewareTests
{
[Fact]
public async Task Middleware_Should_Add_Custom_Header()
{
// Arrange
var context = new DefaultHttpContext(); // fake HttpContext
var middleware = new CustomHeaderMiddleware((innerHttpContext) =>
{
// Simulate next middleware
innerHttpContext.Response.StatusCode = 200;
return Task.CompletedTask;
});
// Act
await middleware.InvokeAsync(context);
// Assert
Assert.True(context.Response.Headers.ContainsKey("X-Test-Header"));
Assert.Equal("HelloUnitTest", context.Response.Headers["X-Test-Header"]);
}
}
Terminal Middleware: What Is It?
Terminal Middleware is the last piece of middleware in the request-response pipeline in ASP.NET Core that terminates the request. Once a terminal middleware is reached, it does not call any subsequent middleware in the pipeline. This is typically where the response is generated and sent back to the client.
Types of Terminal Middleware
- Static File Middleware:
- Serves static files such as HTML, CSS, JavaScript, and images directly to the client.
- Example:
app.UseStaticFiles();
- Routing Middleware:
- Matches incoming requests to route templates defined in your application.
- Example:
app.UseRouting();
- End-point Middleware:
- Executes the final handler for the matched route, often a controller method in an MVC application or a Razor page.
- Example:
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
- Exception Handling Middleware:
- Catches exceptions that occur during the request processing and can provide a response to the client.
- Example:
app.UseExceptionHandler("/Home/Error");
Usage of Terminal Middleware
- Centralized Handling:
Terminal middleware can centralize certain types of request handling – for instance, handling static file requests allows efficient serving of content from the server. - Performance Optimization:
By placing terminal middleware near the end of the pipeline, applications avoid overhead from processing unnecessary middlewares after the response has been generated. - Error Management:
Terminal middleware allows for graceful error handling without propagating failures throughout the middleware pipeline. - Security:
Implements security measures like authentication and authorization at the routing level to ensure terminal endpoints are protected.
Example of Terminal Middleware in ASP.NET Core
public void Configure(IApplicationBuilder app)
{
app.UseRouting(); // Match incoming requests to defined routes
app.UseAuthorization(); // Check for user authorization
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
}); // Terminal Middleware to send response
}
In this example, MapGet is a terminal middleware that handles GET requests to the root URL and sends a simple response.