Table of Contents
- What is a Middleware?
- How does a Middleware work?
- What is the difference between a Middleware and a HTTP Module?
- How to create a Middleware?
- Different ways you can create a Middleware
- Difference between Run and Use methods
- How to create a custom Middleware?
- Shorthand notation to add a Middleware
- How to inject Dependencies into Middleware
- How to unit test a Middleware?
- Conclusion
What is a Middleware?
Although the concept of a Middleware based design was introduced in the last version of .NET Framework itself, it was fully integrated in .NET
A Middleware is a small block of independent code that runs during a request pipeline. A Request pipeline is a series of steps that happen before a request reaches its intended endpoint.
A Middleware can read an input request and can modify the request or decide if it can go further or needs to be responded back within itself. With a Middleware, you can transform, modify, log or even forbid a request even before it is executed.
In .NET, you will add all default functionality to your application such as Authentication, Authorization, Endpoints, Response Caching, Exception Handling etc. as Middleware to be added to the request pipeline!
For example – Authenticating and Authorizing incoming requests for a controller. An Authorization Middleware executes before a controller and validates the Authorization header value passed before letting it pass through it.
You can also write a custom Middleware which asynchronously logs all the input requests and responses that are bound to the application.
How does a Middleware work?
A typical Middleware component has two parameters, the HttpContext which contains the current Request context and a RequestDelegate which chains to the next Middleware to be executed in the pipeline.
Every Middleware must call the next() method at the end of its implementation. Otherwise the next Middleware is not called. This scenario is called Short-circuiting.
These Middleware components are executed one after the other in the same sequence in which they are added to the pipeline. Once the request passes through all the Middleware in the pipeline and a response is generated by the application, it passes through all these Middleware components once again before being sent out to the network.
You can notice that everything in an .NET is a Middleware under the hood and work similar to a set of components in a Chain of Responsibility.
What is the difference between a Middleware and a HTTP Module?
HTTP Module | Middleware |
A HTTP Module is core to the ASP.NET MVC framework | a Middleware is native to the ASP.NET Core |
pieces of code which lets two or more components interact together during a request execution | pieces of code which lets two or more components interact together during a request execution |
HTTP Module is registered in the web.config file of the ASP.NET framework | a Middleware is registered via code inside an ASP.NET Core application |
The order of execution of HTTP Modules can’t be changed | Middleware order or execution can be altered or skipped when needed |
How to create a Middleware?
A Middleware can be defined in the Startup class as below:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// services definition
services.AddSingleton<MyInjectableDependency>();
services.AddTransient<MyLocalInjectableDependency>();
}
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<MyMiddlewareClass>();
}
}
Or in the latest versions of .NET, within the Program class as below
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
// add services from lower layers
services.AddCore();
services.AddInfrastructure();
// Add services to the container.
services.AddControllersWithViews();
var app = builder.Build();
// Add middlewares to the pipeline
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseMiddleware<MyMiddlewareClass>();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
The above code snippet registers the class MyMiddlewareClass as a Middleware and adds it to the request pipeline.
All the requests bound to the application first pass through the class MyMiddlewareClass before reaching a controller. The class can contain any logic for handling requests or responses.
Different ways you can create a Middleware
In .NET, you can create and add a Middleware to the request pipeline in more than one ways. All the Middleware are added to the IApplicationBuilder object, which is a part of the WebApplication built using the WebApplication.Build() method.
.NET provides us with extension methods on top of this interface to add any default or custom Middleware components.
The below are the ways you can do it –
- Use method –
Use() method is available in the IApplicationBuilder interface. This method takes a Function delegate, with two parameters – HttpContext and RequestDelegate. You can just call this method and within the Function delegate write your code inside it.
- UseWhen method –
UseWhen() method is a logical extension of the Use() method. This method takes two Function parameters – a boolean condition and a Function delegate that branches out into a new Application builder. You use this variation to execute a Middleware component only when a specified condition is met, such as a Request containing some path parameters or any query string conditions.
- Run method –
Run() method also runs a Middleware code over the request pipeline. But the difference is that the request stops moving forward after Run() method is executed. This method is specifically used for “Short Circuiting” scenarios where the request need not be processed after a step.
- UseMiddleware method –
UseMiddleware() method doesn’t take any parameters. Instead it takes a type parameter of a class that runs a Middleware code. You use this variant when you need to create a Middleware component which is large enough to be separated and encapsulated into its own method.
Difference between Run and Use methods
Use | Run |
---|---|
Creates a simple Middleware which can be “chained” to other functions over the pipeline | short circuits the request pipeline for a given condition |
Takes two arguments: RequestDelegate and HttpContext | Takes a single argument of HttpContext |
It is chained by calling the next() method on the RequestDelegate | Not possible to chain as no further middlewares are called after this point. HttpContext can be used to write on the Response |
app.Use(async (context, next) => | app.Run(async context => "Hello from Run"); |
How to create a custom Middleware?
public class MyMiddlewareClass
{
private RequestDelegate _next;
public MyMiddlewareClass(
RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync() {
Console.WriteLine("MyMiddlewareClass is now Invoked");
_next.Invoke();
}
}
In the above code snippet, which defines a sample structure of a Middleware implementation, contains a constructor which takes one or more parameters.
The RequestDelegate variable “next” is an ActionDelegate that represents the next Task that needs to be invoked once the current Middleware completes its execution. It can be used to explicitly pass on the control to the next Middleware in the pipeline on any required condition.
When the Middleware is invoked the InvokeAsync (or Invoke for synchronous calls) method is called and the incoming request object is available under the HttpContext property of the class (which can be injected via the Invoke method).
Shorthand notation to add a Middleware
Instead of creating a separate class for a Middleware and configuring it using the UseMiddleware<>()
method inside the Configure()
method, we can also define a simple Middleware class with app.Use() method available in the IApplicationBuilder class.
app.use((context, next) => {
// Request context is available for access
// via the HttpContext parameter object
--- do some logic for this Middleware
next.invoke();
});
Keep in mind that unless we call the next.invoke() inside our Middleware, the next component in the application pipeline is never called. If we add a return in the Middleware without calling the next.invoke() method, the request won’t move forward and instead is returned back with the current state.
This provides a Middleware the power to control the flow of request in the application pipeline.
Example
app.use(async (context, next) => {
// Request context is available for access
// via the HttpContext parameter object
--- do some logic for this Middleware
if(someFailedBooleanCondition)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Your journey ends here!");
return;
}
next.invoke(context);
});
Adding the above middleware in our application pipeline makes our application always reject any request based on some failing condition with a 401 status code, and response body would be “Your journey ends here!”
How to inject Dependencies into Middleware
We might require injecting dependencies into a Middleware (say for example a database context, or a Logger singleton). For that we can have two options: The constructor or the Invoke method. The usage depends on the scope of the dependency within the class.
If the dependencies are scoped life-time services or transient, they can only be injected via the Invoke method.
Since each time a new request comes in, the InvokeAsync()
method is called inside the Middleware, it provides a great scope for the Scoped services to be injected in this because Scoped services are created for every new scope and in this case its a new “HttpRequest Scope”.
For the transient, since they require to be always instantiated to be stateful, they too need to be injected with the InvokeAsync() method.
A Singleton service can be injected into the constructor of the Middleware.
Since the Middleware is built only once when the startup class is invoked (at the time of app bootstrap) and not re-created for every request.
public class MyMiddlewareClass {
private RequestDelegate _next;
private MySingletonDependency _mIdp;
// Any dependencies which might span across
// the class scope can be injected through the constructor
// SINGLETON services
public MyMiddlewareClass (
RequestDelegate next,
MySingletonDependency mIdp)
{
_next = next;
_mIdp = mIdp;
}
// Any dependencies which are scoped just under
// Invoke method are injected into Invoke directly
public async Task InvokeAsync(
MyScopedInjectableDependency mLiDp,
HttpContext context) {
Console.WriteLine("MyMiddlewareClass is now Invoked");
mLiDp.SomeMethodToInvoke("This is MyMiddlewareClass");
this.LogicalMethod();
if (context.User.Identity.IsAuthenticated)
{
// explicitly call
// the next Middleware in the pipeline.
_next.Invoke();
}
else
{
//return back the response to the user.
context.Response.WriteAsync("Invalid User. 401");
return;
}
}
private void LogicalMethod()
{
_mIdp.SomeMethodToInvoke(
"This is LogicalMethod of the middleware");
}
}
How to unit test a Middleware?
Since Middleware components assume a responsibility for some business or validation logic for every request, they’re also prone to go wrong.
For such cases, we require these components to be unit tested. While unit testing a Middleware is not impossible, its a bit difficult because of the constant parameters of HttpContext and RequestDelegate in the code.
For this, we can make use of a Mocking framework to mock these dependencies and use them in isolating the component for testing. You can check how we can unit test a middleware component in my article here.
Conclusion
Middleware are one of the most powerful components you can develop in a .NET application. They have the power to observe and influence the incoming request as well as the response, which makes them ideal for functionalities such as authenticating user requests, or logging request and response.