Explained Middleware in .Net

Middleware in .NET?

In ASP.NET Core, a middleware is a component in the request/response pipeline that handles:

Middlewares can:

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
MiddlewarePurpose
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 CaseWhy Middleware is Good
Logging requests/responsesCaptures input/output for debugging
Global error handlingCentralizes exception processing
Authentication and authorizationControls access before reaching controller
Request timingTrack how long a request took
IP blocking / rate limitingValidate client info early
When not to use:
Use CaseWhy Not Middleware
Data validation (model validation)Use filters or model binding instead
Business logic processingUse services, controllers
Dependency-heavy logicMiddleware is initialized once, avoid complex logic requiring scoped dependencies
Returning UI responsesViews are better handled by controllers or Razor pages
Scenarios:
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
}
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()
// 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
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.

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
  1. Static File Middleware:
    • Serves static files such as HTML, CSS, JavaScript, and images directly to the client.
    • Example: app.UseStaticFiles();
  2. Routing Middleware:
    • Matches incoming requests to route templates defined in your application.
    • Example: app.UseRouting();
  3. 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(); });
  4. 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
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.

Leave a comment