A request does not proceed straight to your controller or endpoint when it reaches an ASP.NET Core application. The request first goes through a number of components that have the ability to examine, alter, log, validate, or even halt it entirely.
We refer to those parts as middleware.
Even if you weren’t aware of it, you have already utilized middleware if you have previously worked with ASP.NET Core APIs.
Things like:
- authentication
- authorization
- exception handling
- CORS
- request logging
- static files
- rate limiting
are all implemented using middleware internally.
Understanding middleware is important because once you understand how the request pipeline actually works, ASP.NET Core starts making much more sense.
Instead of feeling like “framework magic”, you start seeing how requests are flowing through the application step by step.
The Request Pipeline
The easiest way to think about middleware is as a chain.
A request enters the application and moves through middleware one by one until it finally reaches the controller.
Then the response travels back through the same middleware in reverse order.
That reverse flow is the important part that many developers initially miss.
Middleware doesn’t just run once.
It runs:
- before the next middleware
- and again after the response comes back
That’s what makes middleware powerful.
You can:
- inspect requests
- inspect responses
- measure execution time
- handle exceptions
- add headers
- terminate requests
- apply cross-cutting concerns globally
A typical middleware pipeline in ASP.NET Core looks something like this:
The order here matters a lot.
Middleware executes in the same order it gets registered.
If you accidentally place authentication after authorization, things break.
If exception middleware is added too late, exceptions won’t be caught properly.
Middleware order is one of the most important parts of the ASP.NET Core pipeline.
Understanding How Middleware Actually Executes
Let’s take a simple request to:
Internally, the flow looks something like this:
Notice how the request enters from the top and then comes back upward again after the controller finishes.
That’s because every middleware decides when to pass execution to the next middleware and when execution returns back.
This is why middleware is so useful for logging and tracing.
You can see the entire request lifecycle without touching controller code.
Creating Inline Middleware with app.Use()
The simplest way to create middleware is directly inside Program.cs using app.Use().
This middleware runs for every request.
The important thing here is:
That line passes execution to the next middleware in the pipeline.
Without it, the request stops there.
A lot of middleware behavior becomes easy to understand once you realize that middleware is basically:
“do something before next(), then optionally do something after next()”
Code before await next() runs before the controller.
Code after await next() runs after the response comes back.
This pattern is commonly used for:
- logging
- tracing
- timing
- diagnostics
- response modification
Inline middleware is great for smaller logic.
But once the middleware becomes larger, using a dedicated class is usually cleaner.
Creating Custom Middleware Classes
For reusable middleware, ASP.NET Core typically uses middleware classes.
Here’s a simple request logging middleware.
A conventional middleware class usually contains:
- a constructor
- RequestDelegate
- an InvokeAsync() method
The important line again is:
That’s what continues the pipeline.
Without it:
- controller never executes
- next middleware never executes
- request ends immediately
And that behavior is actually useful in some scenarios.
Middleware gets registered like this:
What Exactly is RequestDelegate?
You’ll see RequestDelegate everywhere in middleware.
Internally it’s basically this:
It represents:
“the next middleware in the pipeline”
So when you call:
you’re telling ASP.NET Core:
“continue processing the request”
If you don’t call it, the request pipeline stops there.
This is how terminating middleware works.
Terminating Middleware
Some middleware intentionally stops the pipeline and returns a response directly.
A maintenance middleware is a good example.
Notice something important here.
There’s no:
So the request never continues.
The controller is never reached.
This middleware directly returns a response and ends the request pipeline.
It can be mapped only to specific routes.
So only /maintenance requests get terminated.
Other requests continue normally.
The IMiddleware Pattern
ASP.NET Core also provides another middleware pattern using IMiddleware.
This approach is slightly different from conventional middleware.
With IMiddleware:
- middleware gets activated from DI
- RequestDelegate comes directly in InvokeAsync
- middleware feels more service-oriented
Registration happens through dependency injection.
You won’t always need IMiddleware, but it’s useful when middleware depends heavily on dependency injection or scoped services.
Dependency Injection Inside Middleware
Middleware supports dependency injection just like controllers.
Constructor injection works normally.
You can also inject services directly into InvokeAsync().
This is especially important for scoped services.
Scoped Services in Middleware
This is one of the most common middleware mistakes developers run into.
Scoped services should generally be injected into InvokeAsync() instead of the middleware constructor.
Example scoped service registration:
Scoped service:
Using it inside middleware:
Each request gets its own scoped instance.
That means every request receives a different request ID.
Middleware itself behaves more like a singleton because it’s created once for the pipeline.
That’s why scoped services inside constructors can create lifetime problems.
This is one of those things that feels confusing initially until you actually debug request lifetimes.
Global Exception Handling Middleware
Exception handling is one of the most common real-world middleware use cases.
A global exception middleware usually wraps the request pipeline in a try-catch block.
This middleware should usually be registered early in the pipeline.
That way it wraps everything below it.
If a controller or downstream middleware throws an exception, this middleware catches it and returns a clean standardized response.
Without centralized exception middleware, exception handling becomes repetitive very quickly.
Conditional Middleware with UseWhen()
Sometimes middleware should run only for specific requests.
ASP.NET Core provides UseWhen() for this.
In this example:
- middleware only runs for /api/secure
- other requests skip it entirely
This is useful for:
- API key validation
- feature-specific middleware
- route-specific authentication
- admin-only middleware
- specialized logging
Use vs Run vs Map
Three middleware registration methods are important to understand.
app.Use()
Adds middleware into the pipeline.
Pipeline continues only if next() is called.
app.Run()
Terminates the pipeline.
No middleware executes after this.
app.Map()
Creates a branch pipeline.
Only matching routes enter this branch.
This is commonly used for:
- admin routes
- health checks
- feature-specific pipelines
- versioned APIs
Middleware Extension Methods
Most middleware in ASP.NET Core gets registered using extension methods.
Instead of writing:
you can create cleaner registration methods.
Then registration becomes cleaner.
This is the same pattern used throughout ASP.NET Core itself.
Understanding a Real Request Flow
Once you understand middleware individually, the interesting part is seeing how everything works together.
Here’s the flow for a typical request:
Now imagine a request where authentication fails.
The controller never executes because middleware terminated the request.
This is middleware behavior in action.
Best Practices
A few middleware practices become important once applications grow.
Keep middleware focused.
Logging middleware should log. Validation middleware should validate. Exception middleware should handle exceptions.
Avoid mixing too many responsibilities into one middleware.
Middleware order also matters a lot.
Exception middleware should usually come early. Authentication should run before authorization.
Another important thing: middleware is best for cross-cutting concerns.
Heavy business logic usually belongs inside services or application layers, not middleware.
And finally: trace requests while learning middleware.
Once you start observing request flow through logs, middleware becomes much easier to understand.
Conclusion
Middleware is one of the core building blocks of ASP.NET Core.
Once you understand how the request pipeline works, a lot of ASP.NET Core architecture suddenly becomes easier to reason about.
The important things to remember are:
- middleware executes in registration order
- responses travel back in reverse order
- next() continues the pipeline
- omitting next() terminates the request
- middleware fully supports dependency injection
- most ASP.NET Core features internally rely on middleware
The best way to truly understand middleware is to build some yourself, trace requests, and observe how execution flows through the pipeline.
That’s usually the point where middleware stops feeling abstract and starts becoming practical.
And if you want to explore the complete implementation, you can find the full source code on my GitHub repository.
Best ASP.NET 10.0 Hosting
A solid base for developing online services and applications is ASP.NET. Before creating an ASP.NET web application, you must be proficient in JavaScript, HTML, CSS, and C#. There are thousands of web hosting providers offering ASP.NET hosting on the market. However, there are relatively few web hosting providers that offer top-notch ASP.NET hosting.
ASP.NET is the best development language in Windows platform, which is released by Microsoft and widely used to build all types of dynamic Web sites and XML Web services. With this article, we’re going to help you to find the best ASP.NET Hosting solution in Europe based on reliability, features, price, performance and technical support. After we reviewed about 30+ ASP.NET hosting providers in Europe, our Best ASP.NET Hosting Award in Europe goes to HostForLIFE.eu, one of the fastest growing private companies and one of the most reliable hosting providers in Europe.
