Introduction to ASP.NET Core part 4: middleware and the component pipeline

Introduction

In the previous post we saw how to add a configuration file to an ASP.NET Core application and how to extract the various settings from it. We now know that the traditional web.config file has been replaced by appsettings.json and JSON is used in favour of XML. We also demonstrated that retrieving the settings values is achieved in a very different way compared to how it was done before. The ConfigurationBuilder object with its various extension methods help us load and read the configuration settings. In addition, various techniques such as using the generic IOptions of T interface let us process the different sections in appsettings.json so that we only distribute those settings that are relevant to the consuming classes that they really need. This is in contrast to passing the full list of configuration key-value pairs everywhere. The consuming class can then read the settings in an object oriented way from specialised options objects.

In this post we’ll start looking at middleware and the component pipeline in .NET Core. We’ll also write a simple custom middleware.

Middleware and the component pipeline play a very important role in .NET Core, even more so than in traditional ASP.NET. In .NET 4.5 and above we still had shared startup responsibility between Global.asax.cs and Startup.cs – in fact Startup.cs was divided into two partial classes – and we might have easily built a web application without worrying about what middleware was all about. However, in .NET Core middleware is the ultimate way to build an application pipeline, i.e. the chain that each HTTP request will go through.

There’s some good documentation on this topic on the relevant Microsoft page here.

OWIN and Katana

Two terms that you may come across while working with middleware in the full ASP.NET world are OWIN and Katana. We briefly mentioned OWIN in the second part of this series when we looked at the IApplicationBuilder object injected into the Configure method of Startup.cs. Here’s a short recap on what those terms mean:

Katana is a light-weight web framework built by Microsoft. It is based on a specification called OWIN – Open Web Interface for .NET. The default MVC5 template in Visual Studio 2013 have elements of Katana and OWIN.

Think of Katana as a web framework such as ASP.NET which interacts with IIS as the hosting environment. IIS provides a rich set of features for logging, tracing, monitoring etc. These features are not always needed. With the advent of HTML5 and some advanced JavaScript libraries like jQuery, Knockout.js, Angular.js etc. a lot of work has been delegated to the client. It is often no longer the server that produces HTML – it only responds to Ajax calls with JSON and it is up to the client to process it. This applies especially to mobile applications that consume cloud based web services which only respond with JSON. The goal is to simplify web hosts to make them more responsive, more scalable and cheaper to maintain as the processing time of a request will be a lot faster. Node.js is a JavaScript-based framework that you can use to build a very lean and efficient web server which only has a limited number of features compared to IIS but suits the needs of a large number of web service consuming applications.

Katana allows you to build web based applications, such as MVC or Web API where you can decide which web features to include in the project. Katana features are highly modularised. As mentioned before .NET Core takes modularity more seriously than previous versions of .NET and middleware and startup components are concrete manifestations of this new direction. Instead of including the entire System.Web library even in the smallest web applications, you can decide which features to include and which ones to ignore. As mentioned above, Katana builds on OWIN to achieve these goals. OWIN is a specification which defines an an easy and decoupled form of communication between frameworks and servers. With Katana you can build applications that only indirectly use the features in System.Web through OWIN interfaces. Such an application can be hosted on any server that supports OWIN so it is not tightly coupled to IIS.

Middleware in .NET Core

OWIN and Katana turn up in .NET Core in a different way than before. We can still construct our own middleware, it’s really not difficult. However, the code implementation is different from how it was done in MVC 5. There are some important things to keep in mind:

  • The middleware components are chained together within the Configure method of Startup.cs using the IApplicationBuilder object
  • This chain makes up a pipeline for the HTTP request
  • Each HTTP request goes through this pipeline from the first element in the chain to the last and back
  • There are so-called terminal components which terminate the pipeline. If a component is registered to be called after a terminal component then it won’t be executed. We’ll see an example for that later on
  • An important recurring parameter in all middleware is usually called “next” and is of the delegate type RequestDelegate in the Microsoft.AspNetCore.Http namespace. It doesn’t have to be called “next”, you can call it “mickeyMouse”, but it’s customary to go with “next”. This parameter represents the next element in the pipeline that a component will call. However, a single isolated component will never be aware of the next element in the chain, we don’t need to worry about that. There’s complete separation of middleware components in the chain which is really great. Each component will call the next one in line in a generic way using the request delegate “next”. Keep in mind that a delegate represents a function which can be called exactly like we call a function in C# code.
  • A custom middleware component must adhere to certain conventions in order to function well in the pipeline. We’ll soon see what those conventions are.
  • Middleware is very often wrapped in an extension method whose name starts with “Use”, “Run” or “Map”, with “Use” being the most frequent. These verbs are then followed by the name of the component, e.g. “UseCustomLogger”. That’s how other developers can most easily find these components as they browse the available function names with IntelliSense. We’ll see how to that as well, I promise. Again, this is not a compulsory but a recommended and customary convention.

Existing middleware in the demo

Before we create our own middleware let’s see if there are any available by default in our demo application DotNetCoreBookstore. It turns out that there are indeed a couple. We cannot look at their exact implementation in code but we can see how they are invoked.

Here’s the first example:

app.UseDeveloperExceptionPage();

We add the DeveloperExceptionPage middleware to the pipeline through the UseDeveloperExceptionPage extension method. This piece of middleware ensures that we get to see the error page that describes the exception that occurred during the web request execution. We saw that page before in this series.

Another example is the following:

app.Run(async (context) =>
{
	await context.Response.WriteAsync(stringFormatter.FormatMe(new { Message = "Hello World!" }));
});

“Run” is a terminal middleware, i.e. anything registered after that piece of code won’t be called during the pipeline execution. The Run method accepts a RequestDelegate object that we mentioned above. It can be expressed in a lambda expression like in the example above. The delegate function must have a parameter of type HttpContext which resides in the Microsoft.AspNetCore.Http namespace. The “context” parameter in the above code is exactly of type HttpContext. The delegate returns a Task, hence we can use the async-await keywords to increase the responsiveness of the pipeline. All middleware have this behaviour in fact.

We know that this is a terminal middleware because the body of the lambda expression never calls upon the next item in the pipeline. It simply writes a string to the response stream and that’s it. Let’s alter the behaviour a little and add throw an exception in the lambda:

app.Run(async (context) =>
{
        throw new ArgumentNullException("NONO!!!");
	await context.Response.WriteAsync(stringFormatter.FormatMe(new { Message = "Hello World!" }));
});

Run the demo application and you’ll see the error page with “An unhandled exception occurred while processing the request.” on top. If we comment out the line…

app.UseDeveloperExceptionPage();

…then the error page is not shown. I get a grey screen with the following printed on it:

The localhost page isn’t working

localhost is currently unable to handle this request.
HTTP ERROR 500

Placing…

if (env.IsDevelopment())
{
	app.UseDeveloperExceptionPage();
}

…after…

app.Run(async (context) =>
{
        throw new ArgumentNullException("NONO!!!");
	await context.Response.WriteAsync(stringFormatter.FormatMe(new { Message = "Hello World!" }));
});

…will have the same effect. This proves once again that Run is a terminal middleware. We can remove the exception throwing code now.

The fact that the exception page could show the source of the error proves that the pipeline is two-way. The request goes to the end of the pipeline and then back. The DeveloperExceptionPage middleware extracted the exception information populated further down in the pipeline.

Custom middleware

A custom-made middleware must adhere to the following rules:

  • It must have constructor whose first parameter is of type RequestDelegate. Recall that this is represents the next element in the pipeline.
  • It must have a function called Invoke that accepts an object of type HttpContext and returns a Task for an easy application of the async-await pattern.

That’s it really. Add a new folder to the solution called Middleware and put the following class into it:

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreBookstore.Middleware
{
    public class LoggerComponent
    {
		private readonly RequestDelegate _next;

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

		public async Task Invoke(HttpContext context)
		{
			Debug.WriteLine("Hello from the logger component!");
			await _next(context);			
		}
	}
}

You can see that we have implemented the class according to the guidelines above. We store a reference to the delegate in the constructor and print a small message to the Debug window and then call upon the next item in the pipeline and also pass along the HttpContext object. We don’t do much else with the HTTP context but otherwise we’re free to modify its properties if necessary. The next item in the pipeline may use that information to carry out its actions.

We can register this middleware in the Configure already now without writing the wrapper mentioned above. Put the following piece of code somewhere in the beginning of the Configure method of Startup.cs:

app.UseMiddleware<LoggerComponent>();

Run the application and you should see “Hello from the logger component!” printed in the debug window twice. Google Chrome will try to fetch the favicon icon in a second web request, hence the double output.

What if the custom component requires parameters over and above a RequestDelegate in its constructor? Like here:

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreBookstore.Middleware
{
    public class LoggerComponent
    {
		private readonly RequestDelegate _next;
		private readonly string _username;
		private readonly DateTime _when;

		public LoggerComponent(RequestDelegate next, string username, DateTime when)
		{
			_next = next;
			_username = username;
			_when = when;
		}

		public async Task Invoke(HttpContext context)
		{
			Debug.WriteLine($"Hello from the logger component: {_username}, {_when}");
			await _next(context);			
		}
	}
}

If you try to run the application now you’ll get the exception page with the following message:

InvalidOperationException: Unable to resolve service for type ‘System.String’ while attempting to activate ‘DotNetCoreBookstore.Middleware.LoggerComponent’.

It’s easy to resolve the problem. The UseMiddleware extension method accepts a params array with objects and those objects will be passed into the constructor in the same order like here:

app.UseMiddleware<LoggerComponent>("Elvis Presley", DateTime.UtcNow);

Note that this array includes all input parameters over and above the RequestDelegate which always comes first and is populated for us automatically by the runtime. Here’s what a debug output may look like:

Hello from the logger component: Elvis Presley, 2017-01-05 21:15:09

The wrapper

The wrapper extends the IApplicationBuilder through an extension method. Add a class called MiddlewareRegistrationExtension to the Middleware folder with the following content:

using DotNetCoreBookstore.Middleware;
using Microsoft.AspNetCore.Builder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreBookstore
{
    public static class MiddlewareRegistrationExtension
    {
		public static IApplicationBuilder UseLoggerComponent(this IApplicationBuilder builder, string username, DateTime when)
		{
			return builder.UseMiddleware<LoggerComponent>(username, when);
		}
    }
}

Note that the namespace is simply “DotNetCoreBookstore” so that the extension method can be easily found without adding a using statement in the consuming class. Finally replace…

app.UseMiddleware<LoggerComponent>("Elvis Presley", DateTime.UtcNow);

…with:

app.UseLoggerComponent("Elvis Presley", DateTime.UtcNow);

That’s a cleaner way to call upon the component for other developers. It also exposes the exact input parameters.

A terminal middleware

The following is an example of a terminal middleware:

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreBookstore.Middleware
{
    public class EndOfTheWorldComponent
    {
		public EndOfTheWorldComponent(RequestDelegate next)
		{}

		public async Task Invoke(HttpContext context)
		{
			await context.Response.WriteAsync("This ís the end of the world...");
		}

	}
}

Note that we don’t do anything to the request delegate but simply write to the response stream.

We can extend the wrapper as follows:

public static IApplicationBuilder UseEndOfTheWorldComponent(this IApplicationBuilder builder)
{
	return builder.UseMiddleware<EndOfTheWorldComponent>();
}

If we install the above middleware right in the beginning of the Configure method like this…:

app.UseEndOfTheWorldComponent();
app.UseLoggerComponent("Elvis Presley", DateTime.UtcNow);

…then we’ll see the “This ís the end of the world…” message in the screen and the rest of the registered middleware won’t be executed.

That was all about the basics of middleware in .NET Core. We’ll continue in the next post.

View the list of MVC and Web API related posts here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: