← Back to list

Middleware Development in ASP.NET Core Web API

A Complete Guide to Usage, Benefits, and Best Practices

Jathurshan Santhirasekaram | C#.NET | JS | SQL ✨ in .NET|C# Hub · 2026-06-11 14:06 · 0 claps · 6.7 min read paywalled
#csharp #middleware #middleware-pipeline #dotnet-middleware #programming
Open on Medium ↗
Wiki topics: 💻 · Programming

Middleware Development in ASP.NET Core Web API

A Complete Guide to Usage, Benefits, and Best Practices

What is middleware in ASP.NET Core Web API, and why does every modern .NET developer need to understand it? In this comprehensive guide, we’ll break down middleware development, explain the ASP.NET Core request pipeline, and show you real-world usage examples with code.

Photo by Christopher Gower on Unsplash

Photo by Christopher Gower on Unsplash

What Is Middleware in ASP.NET Core?

Middleware in ASP.NET Core is software assembled into an application pipeline to handle HTTP requests and responses. Each middleware component in ASP.NET Core Web API can perform operations before and after the next component in the pipeline — making it one of the most powerful architectural features in .NET development.

Think of middleware as a series of “checkpoints” that every HTTP request passes through. Each checkpoint can inspect, modify, short-circuit, or pass along the request to the next component.

The ASP.NET Core middleware pipeline is built on the concept of request delegates, and it follows a specific pattern often called the Russian Doll Model or Onion Architecture — where each layer wraps around the next.

How the ASP.NET Core Request Pipeline Works

When an HTTP request hits your ASP.NET Core Web API, it flows through a pipeline of middleware components in the order they are registered. Understanding this pipeline is critical for building scalable, maintainable .NET applications.

Request → Middleware 1 → Middleware 2 → Middleware 3 → Endpoint
Response ← Middleware 1 ← Middleware 2 ← Middleware 3 ← Endpoint

Each middleware component:

  • Receives an HttpContext object
  • Decides whether to call the next middleware (next()) or short-circuit the pipeline
  • Can execute logic before and/or after the next component runs

Why Use Middleware in ASP.NET Core Web API?

Middleware is the backbone of cross-cutting concerns in ASP.NET Core. Here are the primary reasons developers use it:

1. Separation of Concerns — Move authentication, logging, and error handling out of your controllers and into reusable pipeline components.

2. Reusability — Write once, apply globally across all routes and endpoints.

3. Composability — Layer middleware in any order to achieve complex behavior.

4. Performance — Short-circuit requests early (e.g., block unauthorized users before hitting the database).

5. Testability — Middleware can be unit-tested independently from your business logic.

Built-in Middleware in ASP.NET Core

ASP.NET Core ships with a rich library of built-in middleware components you can plug directly into your Web API pipeline:

Middleware Purpose UseRouting() Matches request URLs to route endpoints UseAuthentication() Validates user identity via tokens or cookies UseAuthorization() Enforces access policies and roles UseHttpsRedirection() Redirects HTTP requests to HTTPS UseStaticFiles() Serves static assets (CSS, JS, images) UseExceptionHandler() Global error handling and logging UseCors() Cross-Origin Resource Sharing configuration UseResponseCaching() Caches responses to improve performance UseRateLimiter() Protects APIs from abuse with request throttling

How to Register Middleware in ASP.NET Core

Middleware is registered in the Program.cs file (or Startup.cs in older versions) using the IApplicationBuilder interface. The order of registration matters — middleware executes in the exact order it is added.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Order matters! Register middleware in the correct sequence
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

⚠️ Important: Always register UseAuthentication() before UseAuthorization(). Placing them out of order is one of the most common bugs in ASP.NET Core Web API projects.

The Three Ways to Add Middleware in ASP.NET Core

1. Inline Middleware with app.Use()

The quickest way to add middleware logic without creating a separate class:

app.Use(async (context, next) =>
{
    // Logic BEFORE the next middleware
    Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
await next.Invoke(); // Call the next middleware
    // Logic AFTER the next middleware
    Console.WriteLine($"Response Status: {context.Response.StatusCode}");
});

2. Terminal Middleware with app.Run()

Use app.Run() to add a terminal middleware that does not call the next component. It ends the pipeline:

app.Run(async context =>
{
    await context.Response.WriteAsync("Pipeline terminated here.");
});

3. Custom Middleware Class (Recommended for Production)

For clean, reusable, and testable middleware, create a dedicated class:

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation(
            "Incoming Request: {Method} {Path} at {Time}",
            context.Request.Method,
            context.Request.Path,
            DateTime.UtcNow
        );
        await _next(context); // Pass to next middleware
        _logger.LogInformation(
            "Response: {StatusCode}",
            context.Response.StatusCode
        );
    }
}

Register it in Program.cs using an extension method:

// Extension method for clean registration
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder app)
    {
        return app.UseMiddleware<RequestLoggingMiddleware>();
    }
}

// In Program.cs
app.UseRequestLogging();

Real-World Use Cases for ASP.NET Core Middleware

Use Case 1: Global Exception Handling Middleware

One of the most valuable uses of custom middleware is a global error handler that catches unhandled exceptions and returns consistent error responses:

public class GlobalExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger;
public GlobalExceptionHandlerMiddleware(RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred.");
            await HandleExceptionAsync(context, ex);
        }
    }
    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        var response = new
        {
            StatusCode = context.Response.StatusCode,
            Message = "An internal server error occurred.",
            Detail = exception.Message
        };
        return context.Response.WriteAsJsonAsync(response);
    }
}

Use Case 2: API Key Authentication Middleware

Protect your Web API endpoints with a lightweight API key validation middleware:

public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private const string ApiKeyHeaderName = "X-Api-Key";
public ApiKeyMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue(ApiKeyHeaderName, out var extractedApiKey))
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await context.Response.WriteAsync("API Key is missing.");
            return;
        }
        var configuration = context.RequestServices.GetRequiredService<IConfiguration>();
        var apiKey = configuration["ApiSettings:ApiKey"];
        if (!apiKey.Equals(extractedApiKey))
        {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            await context.Response.WriteAsync("Invalid API Key.");
            return;
        }
        await _next(context);
    }
}

Use Case 3: Request/Response Logging Middleware

Log every incoming request and outgoing response for debugging and auditing:

public class AuditLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<AuditLoggingMiddleware> _logger;
public AuditLoggingMiddleware(RequestDelegate next, ILogger<AuditLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        await _next(context);
        stopwatch.Stop();
        _logger.LogInformation(
            "[AUDIT] {Method} {Path} responded {StatusCode} in {Elapsed}ms",
            context.Request.Method,
            context.Request.Path,
            context.Response.StatusCode,
            stopwatch.ElapsedMilliseconds
        );
    }
}

Use Case 4: Correlation ID Middleware

Attach a unique ID to every request for distributed tracing across microservices:

public class CorrelationIdMiddleware
{
    private readonly RequestDelegate _next;
    private const string CorrelationIdHeader = "X-Correlation-Id";
    public CorrelationIdMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        var correlationId = context.Request.Headers[CorrelationIdHeader].FirstOrDefault()
                            ?? Guid.NewGuid().ToString();
        context.Items["CorrelationId"] = correlationId;
        context.Response.Headers[CorrelationIdHeader] = correlationId;
        await _next(context);
    }
}

Middleware vs. Filters in ASP.NET Core Web API

A common question among .NET developers: When should I use middleware vs. action filters?

Rule of thumb: Use middleware for infrastructure-level concerns that apply to all requests. Use filters for MVC-specific behavior tied to controllers and actions.

Conditional Middleware: Applying Middleware Selectively

You don’t always want middleware to run on every request. ASP.NET Core provides UseWhen() and MapWhen() for conditional branching:

// Run middleware only for API routes
app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), appBuilder =>
{
    appBuilder.UseMiddleware<ApiKeyMiddleware>();
});

// Branch the pipeline entirely for a specific path
app.MapWhen(context => context.Request.Path.StartsWithSegments("/admin"), adminApp =>
{
    adminApp.UseMiddleware<AdminAuthMiddleware>();
    adminApp.UseMiddleware<AuditLoggingMiddleware>();
});

Best Practices for Middleware Development in ASP.NET Core

Following these best practices will make your middleware performant, maintainable, and production-ready:

✅ Keep middleware focused — Each middleware component should do one thing well. Avoid bloated middleware that handles multiple responsibilities.

✅ Always call await _next(context) — Unless you explicitly intend to terminate the pipeline, always pass the request downstream.

✅ Handle exceptions inside middleware — Don’t let exceptions bubble out of your middleware unhandled.

✅ Be mindful of order — Authentication must precede Authorization. Logging should come first to capture all requests.

✅ Avoid storing per-request state in fields — Middleware is instantiated as a singleton. Use HttpContext.Items for request-scoped state instead.

✅ Use IMiddleware for DI-friendly middleware — Implementing IMiddleware allows full dependency injection support:

public class MyDiMiddleware : IMiddleware
{
    private readonly IMyService _service;
public MyDiMiddleware(IMyService service)
    {
        _service = service;
    }
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        // Use _service here
        await next(context);
    }
}
// Register in DI container
builder.Services.AddTransient<MyDiMiddleware>();
app.UseMiddleware<MyDiMiddleware>();

Middleware Ordering Cheat Sheet

Here is the recommended middleware ordering for a typical ASP.NET Core Web API:

app.UseExceptionHandler();        // 1. Catch all errors first
app.UseHttpsRedirection();        // 2. Enforce HTTPS
app.UseStaticFiles();             // 3. Short-circuit for static files
app.UseRouting();                 // 4. Route matching
app.UseCors();                    // 5. CORS headers
app.UseAuthentication();          // 6. Who are you?
app.UseAuthorization();           // 7. Can you do this?
app.UseRateLimiter();             // 8. Throttle requests
app.UseMiddleware<AuditLoggingMiddleware>(); // 9. Custom middleware
app.MapControllers();             // 10. Execute endpoint

Conclusion: Mastering Middleware in ASP.NET Core Web API

Middleware in ASP.NET Core Web API is one of the most essential tools in a .NET developer’s arsenal. It enables you to build clean, modular, and highly maintainable pipelines that handle cross-cutting concerns like authentication, logging, error handling, and performance monitoring — without polluting your business logic.

Whether you’re building a simple REST API or a complex microservices architecture, understanding how to create, order, and compose middleware will dramatically improve the quality of your ASP.NET Core applications.

Key takeaways from this guide:

  • Middleware forms a pipeline that processes every HTTP request and response
  • Order of registration in Program.cs directly controls execution order
  • Custom middleware classes are the recommended approach for production code
  • Use UseWhen() / MapWhen() for conditional middleware branching
  • Prefer IMiddleware when your middleware requires scoped or transient dependencies

Start small — replace your next try/catch in a controller with a global exception handler middleware — and you'll immediately see the power of building with ASP.NET Core middleware.

Did you find this guide helpful? Follow for more in-depth .NET, ASP.NET Core, and Web API tutorials. Leave a comment below with the middleware pattern you use most in your projects!

Tags: ASP.NET Core · Web API · Middleware · .NET Development · C# · Backend Development · Software Architecture · REST API · dotnet · Programming


메타데이터
post_id
51f648fbe72e
slug
middleware-development-in-asp-net-core-web-api-51f648fbe72e
url
https://medium.com/we-are-developers/middleware-development-in-asp-net-core-web-api-51f648fbe72e
canonical_url
https://medium.com/we-are-developers/middleware-development-in-asp-net-core-web-api-51f648fbe72e
author_url
https://medium.com/@code_santa
status
ok
fetched_at
2026-06-14 11:28:49