Card image cap

Exploring ASP.NET Core Fundamentals - Understanding Middlewares

ASP.NET Core  • Posted one month ago

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.

Configuring 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. And 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.

Creating a Middleware class:

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 for Adding 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.

For 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 a Middleware:

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". And 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");
	}
}

Unit Testing 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.

Final Thoughts:

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.

What is the difference between Response.Redirect() and Server.Transfer() ?
How do you handle errors Globally in ASP.NET Core?
How do you design a strongly-typed class for a configuration?
How can you bind a configuration section to an object?
When to use IOptionsMonitor?
We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept