DEV Community

Cover image for 10 ASP.NET Core Middleware Concepts Every .NET Developer Should Know
ToolBench
ToolBench

Posted on

10 ASP.NET Core Middleware Concepts Every .NET Developer Should Know

ASP.NET Core middleware is one of those concepts that every .NET developer uses, but many developers don't fully understand what happens behind the scenes.

Authentication, authorization, exception handling, logging, CORS, HTTPS redirection, rate limiting, and even your own custom logic can all be implemented through middleware.

Once you understand how middleware works, ASP.NET Core applications become much easier to design, debug, and maintain.

In this tutorial, we'll go beyond the basic definition and explore 10 important ASP.NET Core middleware concepts with practical examples, including how middleware executes, how to create custom middleware, how Use, Run, and Map differ, how ordering affects your application, and how to avoid common mistakes.


Table of Contents

  1. What Is Middleware?
  2. How the Middleware Pipeline Works
  3. Use() vs Run() vs Map()
  4. Creating Custom Middleware
  5. Middleware Ordering
  6. Exception Handling Middleware
  7. Request Logging Middleware
  8. Authentication and Authorization Middleware
  9. Conditional Middleware
  10. Short-Circuiting the Pipeline
  11. Middleware with Dependency Injection
  12. Common Middleware Mistakes
  13. Performance Best Practices
  14. Middleware Interview Questions
  15. Frequently Asked Questions
  16. Final Thoughts

1. What Is Middleware?

Middleware is a component that participates in handling an HTTP request and response.

You can think of middleware as a series of checkpoints through which every request passes.

For example, a request might travel through:

Client
  ↓
Exception Handling
  ↓
HTTPS Redirection
  ↓
Logging
  ↓
Authentication
  ↓
Authorization
  ↓
Routing
  ↓
Controller
  ↓
Response
Enter fullscreen mode Exit fullscreen mode

Each middleware component can:

  • Inspect the request
  • Modify the request
  • Perform some operation
  • Call the next middleware
  • Modify the response
  • Stop the request from continuing

This makes middleware extremely powerful.


2. How the Middleware Pipeline Works

Let's start with a simple example.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.Use(async (context, next) =>
{
    Console.WriteLine("Before next middleware");

    await next();

    Console.WriteLine("After next middleware");
});

app.Run(async context =>
{
    Console.WriteLine("Endpoint");

    await context.Response.WriteAsync("Hello World!");
});

app.Run();
Enter fullscreen mode Exit fullscreen mode

When a request arrives, the output is:

Before next middleware
Endpoint
After next middleware
Enter fullscreen mode Exit fullscreen mode

Notice something important.

The middleware doesn't simply execute from top to bottom.

The call to:

await next();
Enter fullscreen mode Exit fullscreen mode

passes execution to the next middleware.

When that middleware finishes, execution returns to the previous middleware.

You can visualize it like this:

Middleware A
    ↓
Middleware B
    ↓
Endpoint
    ↑
Middleware B
    ↑
Middleware A
Enter fullscreen mode Exit fullscreen mode

This is sometimes called the middleware pipeline or onion model.

Understanding this behavior is essential when working with logging, authentication, exception handling, and response manipulation.


3. Use() vs Run() vs Map()

One of the most common ASP.NET Core interview questions is the difference between Use(), Run(), and Map().

Use()

Use() adds middleware that can call the next component.

app.Use(async (context, next) =>
{
    Console.WriteLine("Before");

    await next();

    Console.WriteLine("After");
});
Enter fullscreen mode Exit fullscreen mode

Because it receives next, it can continue the pipeline.


Run()

Run() adds terminal middleware.

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello");
});
Enter fullscreen mode Exit fullscreen mode

There is no next parameter.

Once this middleware executes, the pipeline ends.

For example:

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 1");

    await next();
});

app.Run(async context =>
{
    Console.WriteLine("Terminal middleware");
});
Enter fullscreen mode Exit fullscreen mode

Anything registered after Run() won't execute.


Map()

Map() allows you to create a separate pipeline based on the request path.

app.Map("/admin", adminApp =>
{
    adminApp.Run(async context =>
    {
        await context.Response.WriteAsync("Admin Area");
    });
});
Enter fullscreen mode Exit fullscreen mode

Now:

/admin
Enter fullscreen mode Exit fullscreen mode

will enter the mapped pipeline.

This is useful when different URL paths require completely different processing.


4. Creating Custom Middleware

One of the most useful skills for an ASP.NET Core developer is creating custom middleware.

Suppose you want to measure how long every request takes.

You could create:

public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestTimingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();

        await _next(context);

        stopwatch.Stop();

        Console.WriteLine(
            $"{context.Request.Path} took {stopwatch.ElapsedMilliseconds} ms");
    }
}
Enter fullscreen mode Exit fullscreen mode

You can register it with:

app.UseMiddleware<RequestTimingMiddleware>();
Enter fullscreen mode Exit fullscreen mode

Now every request passes through your middleware.


Why Use Custom Middleware?

Custom middleware is useful for cross-cutting concerns such as:

  • Request logging
  • Correlation IDs
  • Exception handling
  • Performance measurement
  • Security headers
  • Request validation
  • Custom authentication
  • Tenant identification
  • Audit logging

The important principle is:

Middleware is best suited for logic that applies across many requests.

Don't put business-specific logic into middleware when it belongs inside a service or controller.


5. Middleware Ordering Matters

This is one of the most important concepts to understand.

Consider:

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();
Enter fullscreen mode Exit fullscreen mode

The order is intentional.

Authentication establishes who the user is.

Authorization then determines whether that user is allowed to access the resource.

Finally, the request reaches the endpoint.

Conceptually:

Request
  ↓
Authentication
  ↓
Authorization
  ↓
Controller
Enter fullscreen mode Exit fullscreen mode

If you change the order incorrectly, your application may behave unexpectedly.

For example:

app.UseAuthorization();

app.UseAuthentication();
Enter fullscreen mode Exit fullscreen mode

This is generally incorrect because authorization may execute before the user has been authenticated.

General rule

When working with middleware, always ask:

"Does this middleware depend on something that runs before it?"

That question helps prevent many pipeline-related bugs.


6. Exception Handling Middleware

Production applications should not expose raw exception details to users.

Instead of allowing an exception to travel directly to the client, use exception-handling middleware.

ASP.NET Core provides built-in support:

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/error");
}
Enter fullscreen mode Exit fullscreen mode

You can also implement your own middleware.

public async Task InvokeAsync(HttpContext context)
{
    try
    {
        await _next(context);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);

        context.Response.StatusCode = 500;

        await context.Response.WriteAsJsonAsync(new
        {
            message = "An unexpected error occurred."
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

This gives you a central place to handle unexpected failures.

Important

Don't return sensitive exception details in production.

Avoid responses such as:

{
  "error": "SqlException: Login failed for user..."
}
Enter fullscreen mode Exit fullscreen mode

Instead, return a safe message and log the detailed exception internally.


7. Request Logging Middleware

Logging every request can be extremely useful when troubleshooting production applications.

A simple middleware might look like this:

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)
    {
        var stopwatch = Stopwatch.StartNew();

        await _next(context);

        stopwatch.Stop();

        _logger.LogInformation(
            "HTTP {Method} {Path} returned {StatusCode} in {Elapsed}ms",
            context.Request.Method,
            context.Request.Path,
            context.Response.StatusCode,
            stopwatch.ElapsedMilliseconds);
    }
}
Enter fullscreen mode Exit fullscreen mode

This can produce logs such as:

HTTP GET /api/products returned 200 in 42ms
Enter fullscreen mode Exit fullscreen mode

This is much more useful than randomly placing Console.WriteLine() statements throughout your application.


8. Authentication and Authorization Middleware

Authentication and authorization are related but different concepts.

Authentication

Authentication answers:

Who are you?

For example:

User → JWT Token → Authentication → User Identity
Enter fullscreen mode Exit fullscreen mode

Authorization

Authorization answers:

Are you allowed to access this resource?

For example:

Authenticated User
       ↓
Required Role = Admin
       ↓
Allowed / Forbidden
Enter fullscreen mode Exit fullscreen mode

In ASP.NET Core, you'll commonly see:

app.UseAuthentication();

app.UseAuthorization();
Enter fullscreen mode Exit fullscreen mode

And then protect endpoints with:

[Authorize]
public IActionResult GetOrders()
{
    return Ok();
}
Enter fullscreen mode Exit fullscreen mode

Or with roles:

[Authorize(Roles = "Admin")]
public IActionResult DeleteUser(int id)
{
    return Ok();
}
Enter fullscreen mode Exit fullscreen mode

This separation is fundamental when designing secure APIs.


9. Conditional Middleware

Sometimes you don't want middleware to execute for every request.

ASP.NET Core provides ways to conditionally add middleware.

For example:

app.UseWhen(
    context => context.Request.Path.StartsWithSegments("/api"),
    branch =>
    {
        branch.UseMiddleware<RequestTimingMiddleware>();
    });
Enter fullscreen mode Exit fullscreen mode

Now the middleware only runs for requests beginning with:

/api
Enter fullscreen mode Exit fullscreen mode

You can also use MapWhen() when you want to create a completely separate pipeline.

app.MapWhen(
    context => context.Request.Query.ContainsKey("debug"),
    branch =>
    {
        branch.Run(async context =>
        {
            await context.Response.WriteAsync("Debug pipeline");
        });
    });
Enter fullscreen mode Exit fullscreen mode

Conditional middleware can be useful when different parts of your application require different processing.


10. Short-Circuiting the Pipeline

Middleware doesn't always have to call next().

It can stop the pipeline completely.

For example:

app.Use(async (context, next) =>
{
    if (!context.Request.Headers.ContainsKey("X-API-KEY"))
    {
        context.Response.StatusCode = 401;

        await context.Response.WriteAsync("API key required.");

        return;
    }

    await next();
});
Enter fullscreen mode Exit fullscreen mode

If the API key doesn't exist, the request ends immediately.

The controller will never execute.

This is called short-circuiting.

Short-circuiting is useful for:

  • Authentication checks
  • API key validation
  • IP restrictions
  • Maintenance mode
  • Rate limiting
  • Request validation

However, don't overuse it.

If the logic belongs to authorization, validation, or business logic, use the appropriate ASP.NET Core mechanism instead of creating unnecessary custom middleware.


11. Middleware with Dependency Injection

Middleware can also use services registered with ASP.NET Core's dependency injection container.

For example:

public class AuditMiddleware
{
    private readonly RequestDelegate _next;

    public AuditMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(
        HttpContext context,
        IAuditService auditService)
    {
        await _next(context);

        await auditService.RecordAsync(
            context.Request.Path,
            context.Response.StatusCode);
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice that IAuditService is injected into InvokeAsync().

This is useful when middleware needs application services.

For example:

builder.Services.AddScoped<IAuditService, AuditService>();
Enter fullscreen mode Exit fullscreen mode

Then the middleware can use it.

Important Lifetime Consideration

Be careful when injecting scoped services into middleware.

Conventional middleware instances can live longer than a single request, so injecting scoped services into the middleware constructor can create lifetime problems.

Prefer injecting request-scoped services into InvokeAsync() when appropriate.


12. Common Middleware Mistakes

Let's look at mistakes that frequently appear in real applications.

Mistake 1: Forgetting await next()

Consider:

app.Use(async (context, next) =>
{
    Console.WriteLine("Logging");

    // next() is never called
});
Enter fullscreen mode Exit fullscreen mode

The pipeline stops here.

Unless the middleware intentionally short-circuits the request, this is a bug.


Mistake 2: Incorrect Middleware Order

For example:

app.UseAuthorization();

app.UseAuthentication();
Enter fullscreen mode Exit fullscreen mode

Authentication should normally happen before authorization.

Always understand the dependencies between middleware components.


Mistake 3: Putting Business Logic in Middleware

Avoid doing this:

if (order.Total > 10000)
{
    // complex business rules
}
Enter fullscreen mode Exit fullscreen mode

Middleware should generally handle cross-cutting concerns.

Business rules belong in appropriate application/domain services.


Mistake 4: Logging Sensitive Information

Don't blindly log:

  • Passwords
  • Access tokens
  • Refresh tokens
  • Credit card information
  • Sensitive personal information

Logging should help diagnose problems without creating a security problem.


Mistake 5: Doing Expensive Work on Every Request

Middleware runs frequently.

If your application receives 10,000 requests, middleware may execute 10,000 times.

Avoid unnecessary database calls, network calls, or expensive calculations in globally registered middleware.


13. Middleware Performance Best Practices

Performance matters because middleware can affect every request.

Keep Middleware Lightweight

Good middleware:

Request
 ↓
Small operation
 ↓
next()
Enter fullscreen mode Exit fullscreen mode

Avoid unnecessarily expensive processing.

Avoid Blocking Calls

Prefer:

await service.ProcessAsync();
Enter fullscreen mode Exit fullscreen mode

instead of synchronous blocking operations.

Avoid patterns such as:

service.ProcessAsync().Result;
Enter fullscreen mode Exit fullscreen mode

or:

service.ProcessAsync().Wait();
Enter fullscreen mode Exit fullscreen mode

These can cause thread-pool starvation and performance problems.

Use Structured Logging

Instead of:

_logger.LogInformation(
    $"Request {path} took {time}ms");
Enter fullscreen mode Exit fullscreen mode

Prefer structured logging:

_logger.LogInformation(
    "Request {Path} took {Elapsed}ms",
    path,
    time);
Enter fullscreen mode Exit fullscreen mode

Structured logs are easier to search and analyze in logging platforms.


14. Middleware vs Filters

A common interview question is:

What's the difference between middleware and MVC filters?

Middleware operates at the HTTP pipeline level.

Filters are more closely associated with MVC/controller or endpoint execution.

Think of it like:

HTTP Request
     ↓
Middleware
     ↓
Routing
     ↓
Endpoint
     ↓
MVC Filters
     ↓
Controller
Enter fullscreen mode Exit fullscreen mode

Use middleware for application-wide concerns.

Use filters when the behavior is specifically related to MVC/controller or action execution.


15. Middleware Interview Questions

Here are some questions worth knowing for .NET interviews.

1. What is middleware in ASP.NET Core?

Middleware is software that participates in processing HTTP requests and responses within the ASP.NET Core pipeline.

2. What is next()?

next() passes control to the next middleware component.

3. What happens if next() isn't called?

The pipeline is short-circuited unless the middleware intentionally handles the response itself.

4. What is the difference between Use() and Run()?

Use() can call the next middleware.

Run() is terminal middleware and doesn't continue the pipeline.

5. Why does middleware order matter?

Because middleware executes in the order it is registered, and some components depend on others running first.

6. How do you create custom middleware?

Create a class that accepts RequestDelegate and exposes an Invoke or InvokeAsync method.

7. What is short-circuiting?

Stopping the request pipeline before subsequent middleware executes.

8. When should middleware be used?

For cross-cutting concerns such as logging, exception handling, authentication, security headers, and request processing.


Frequently Asked Questions

Is middleware executed for every request?

Middleware registered globally generally participates in every request unless the pipeline is branched or the middleware itself chooses to short-circuit or skip processing.

Can middleware modify the response?

Yes.

Middleware can inspect or modify the response before or after calling the next component.

Can middleware access services?

Yes. Middleware can use ASP.NET Core dependency injection.

Should business logic be placed in middleware?

Generally, no. Middleware should focus on cross-cutting concerns rather than application-specific business rules.

Can middleware stop a request?

Yes. Middleware can short-circuit the pipeline by not calling next() and returning a response directly.


Final Thoughts

Middleware is one of the foundations of ASP.NET Core.

Once you understand how the pipeline works, many ASP.NET Core features become easier to understand.

The most important concepts to remember are:

  • Middleware forms a request-processing pipeline.
  • Use() can continue the pipeline.
  • Run() is terminal.
  • Map() can create branches.
  • Middleware order matters.
  • Middleware can short-circuit requests.
  • Custom middleware is useful for cross-cutting concerns.
  • Authentication should generally run before authorization.
  • Middleware should remain lightweight.
  • Business logic should live in appropriate services.

If you're preparing for a .NET interview, don't just memorize these definitions. Build a small ASP.NET Core API and implement your own logging, exception handling, API-key validation, and request-timing middleware.

That's when the pipeline really starts to make sense.


🚀 Explore More Free Developer Tools

As developers, we often switch between different tools while building and debugging applications. Formatting API responses, decoding JWTs, comparing configuration files, generating UUIDs, or converting data shouldn't require opening several different websites.

That's one reason I built ToolBenchApp.

👉 https://toolbenchapp.com/

ToolBenchApp is a growing collection of free, browser-based developer tools designed to make everyday development tasks faster and simpler.

Some of the available tools include:

  • JSON Formatter & Validator
  • JWT Decoder
  • JSON ↔ XML Converter
  • JSON ↔ CSV Converter
  • Base64 Encoder/Decoder
  • UUID Generator
  • URL Encoder/Decoder
  • SQL Formatter
  • YAML Formatter
  • Text Difference Checker
  • HTML Formatter
  • Regex Tester

If you're working with ASP.NET Core APIs, tools like the JSON Formatter, JWT Decoder, and Text Diff Checker can be particularly useful during development and debugging.

I'm continuously adding new utilities based on what developers actually need, so if there's a tool you'd like to see on ToolBenchApp, feel free to share your idea.

Happy coding! 🚀

Top comments (0)