The concept of Middleware based design was introduced in the last version of dotnet framework, while it’s been fully integrated in dotnetcore.
A middleware can be termed as a handler module that runs before a request reaching a controller.
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.
Typical example of built-in middleware components is the Authorization module used for authenticating and authorizing incoming requests for a controller.
An Authorization middleware, when configured in the application, acts before a controller and validates the Authorization header value passed before letting it pass through it.
Possible usecase for a custom Middleware can be a Logger which asynchronously logs all the input requests and responses that are bound to the application.
How to configure a Middleware?
A middleware is 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>();
}
}
The above code snippet registers the class MyMiddlewareClass as a Middleware and adds it to the request pipeline. So 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.
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!”
Injecting Dependencies into Middlewares
Now that a middleware contains logic for handling requests, we might require injecting dependencies into it (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.
Obvious right? 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.
Example –
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.
Found this article helpful? Please consider supporting!
Conclusion
Middlewares are one of the most powerful components you can develop in an aspnetcore 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.
As you start working with aspnetcore, you’ll understand that everything that works in an aspnetcore is a middleware under the hood and is work similar to a set of components in a Chain of Responsibility. When used properly, one can leverage the true potential of these components based on the requirements they’re created for.
[…] We can also define a series of components which are to be executed in order while processing an incoming request. The resultant is called as a request pipeline and every single request passes through each and every component registered in the request pipeline before transforming into a response. These components which are tied to the request pipeline are called as "Middlewares". […]
[…] of the most interesting things I feel dotnet core has been provided with is the concept of Middlewares. Each middleware component when hooked up to the request pipeline, can have access to the context […]
[…] Middlewares are one of the most powerful components in the ASP.NET Core framework, using which we can perform a particular functionality over an incoming request or an outgoing response. We can use them for practical purposes such as Logging, Authorization, Routing and for more complex scenarios that suit our requirements. Why we’re talking all this now? Just to understand that a custom Middleware class can hold some business logic which does require to testing for any errors. And how can we do this better? by writing Unit Tests for the logic that happens in a Middleware just to ensure that everything happens as expected when an actual request passes by. […]