ASP.NET 5 Middleware, Or Where Has My HttpModule Gone?

4.73 (30 votes)

ASP.NET 5 has been largely rewritten from the ground up, and incorporates some radical changes when compared with previous versions of ASP.NET. One of the biggest changes is in the HTTP Pipeline. This article looks at how those changes impact the design and registration of plug and play components that used to be represented by HttpModules.

HttpModules act as request filters in ASP.NET versions prior to 5. They are reusable units of code that can be plugged into the request pipeline, and tasked with responding to events defined in the HttpApplication class as they are fired. The kind of things that modules are used for include managing authentication, global error handling and logging. Traditional forms authentication is implemented as a module, and I have written about using ELMAH, a component that logs details of unhandled exceptions, which is impemented as an HttpModule. Modules can also be used to intercept and modify the outbound response to remove white space or compress it, for example. Modules implement the IHttpModule interface, which is defined in System.Web, and that library is not part of the new ASP.NET.

HttpModule code can either be added to the relevant event handler in Global.asax, or more usually, created as class libraries and registered with the application in the web.config file.

What Is Middleware?

The definition of "Middleware" varies widely depending on its context, but in relation to ASP.NET 5, the definition provided by the OWIN specification is probably closest:

Pass through components that form a pipeline between a server and application to inspect, route, or modify request and response messages for a specific purpose.

That's also pretty much the description of traditional HttpModules and their kin, HttpHandlers.

Access to the request pipeline in an ASP.NET 5 application is provided in the Startup class (Startup.cs file), which is the entry point to the application itself. The Startup class includes a Configure method which takes an IApplicationBuilder type as a parameter. The IApplicationBuilder interface, an instance of which is represented by the parameter app, provides a number of extension methods by which components can be plugged in to the request pipeline. The major difference between this and the previous implementation of the HTTP pipeline is that the event-driven approach has been replaced with a composable model. In the new ASP.NET, the order in which components are added is important. The Beta 3.0 MVC Starter site template has some components added with comments that explain their purpose:

// Add static files to the request pipeline.
app.UseStaticFiles();

// Add cookie-based authentication to the request pipeline.
app.UseIdentity();

// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action}/{id?}",
        defaults: new { controller = "Home", action = "Index" });

    // Uncomment the following line to add a route for porting Web API 2 controllers.
    // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});

The methods by which Identity, MVC and static files management are added to the pipeline are extension methods on the IApplicationBuilder type. When adding the middleware that will be built in this article, I will also create an extension method for adding it to the pipeline.

The Example Middleware

The example middleware that I shall create will do two things: it will measure the time taken to process the request and it will add the value to both the outgoing html and as a custom response header. That might be three things. Anyhoo, the example will feature two of the most common scenarios illustrated in the plethora of existing "Build your own HttpModule" tutorials on the Internet - modifying the response output and adding headers. The code for the middleware class is as follows:

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

namespace WebApplication1.MiddleWare
{
    public class MyMiddleware
    {
        RequestDelegate _next;

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

        public async Task Invoke(HttpContext context)
        {

            var sw = new Stopwatch();
            sw.Start();

            using (var memoryStream = new MemoryStream())
            {
                var bodyStream = context.Response.Body;
                context.Response.Body = memoryStream;
  
                await _next(context);

                var isHtml = context.Response.ContentType?.ToLower().Contains("text/html");
                if (context.Response.StatusCode == 200 && isHtml.GetValueOrDefault())
                {
                    {
                        memoryStream.Seek(0, SeekOrigin.Begin);
                        using (var streamReader = new StreamReader(memoryStream))
                        {
                            var responseBody = await streamReader.ReadToEndAsync();
                            var newFooter = @"<footer><div id=""process"">Page processed in {0} milliseconds.</div>";
                            responseBody = responseBody.Replace("<footer>", string.Format(newFooter, sw.ElapsedMilliseconds));
                            context.Response.Headers.Add("X-ElapsedTime", new[] { sw.ElapsedMilliseconds.ToString() });
                            using (var amendedBody = new MemoryStream())
                            using (var streamWriter = new StreamWriter(amendedBody))
                            {
                                streamWriter.Write(responseBody);
                                amendedBody.Seek(0, SeekOrigin.Begin);
                                await amendedBody.CopyToAsync(bodyStream);
                            }
                        }
                    }
                }
            }
        }
    }
}

A private field of type RequestDelegate is declared. This is used to store the RequestDelegate that is passed in to the constructor that takes a RequestDelegate. A RequestDelegate is a function that takes an HttpContext instance and returns a Task:

public delegate Task RequestDelegate(HttpContext context);

The HttpContext object is similar to the one found in previous versions of ASP.NET in that it provdes access to the request and response etc, but it is a different animal altogether and much, much slimmer.

In ASP.NET 5, middleware is an instance of Func<RequestDelegate, RequestDelegate> - a delegate that takes a RequestDelegate as a parameter and returns a RequestDelegate. And as described above, the RequestDelegate is a function that returns a function, and it is in this way that the pipeline is built, with each piece of middleware being chained to the next and responsible for passing the processing on to the next in line (if appropriate).

The Invoke method is a convention-based method in that the runtime looks for a method with that name and will call it. It is where the action happens within your middleware, and it is where you pass control on to the next component in the pipeline if appropriate. Sometimes you won't want to pass control on - you will want to halt execution of the pipeline altogether, if, for example, you are writing a custom authentication middleware that determines that the current user should proceed no further. Control is passed to the next component by calling await _next(context). Code that you place before this call is executed, and in this example that code creates a Stopwatch instance and starts it. The code also gets a reference to the response content, which is implemented as a stream. Then the next component is called, which in turn calls the next component and so on. Then control is passed back up the chain of components and any code that has been added after the await _next(context) call is executed. It is in that block of code in this example that the response body is modified to include some HTML to display the elapsed time in milliseconds somewhere in the footer region of the page. At the same time, a custom header is added with the elapsed time as its value.

Plugging into the pipeline

The next step is to plug the component into the pipeline. As I mentioned earlier, this is achieved via an extension method on the IApplicationBuilder type:

using Microsoft.AspNet.Builder;

namespace ASPNET5Test.MiddleWare
{
    public static class BuilderExtensions
    {
        public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware<MyMiddleware>();
        }
    }
}

The result is a wrapper around the existing UseMiddleware<T> extension method (currently found in the Microsoft.AspNet.Builder namespace - hence the using directive) into something a little prettier.

Finally, the extension method is called in the Configure method of the Startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
{
    app.UseMyMiddleware();

    // etc

When the site is run for the first time, the processing time takes a while - just under two seconds in this image - and is printed to the bottom of the screen (with a little CSS assistance):

ASP.NET 5 Middleware

And the developer tools in Chrome provide an easy way to confirm that the custom header was added:

ASP.NET 5 Middleware

Summary

In this article, I introduced the ASP.NET5 replacement to traditional HttpModules and showed how to create one and plug it in to the application pipeline. This article is based on Beta 3 of ASP.NET 5 and the concepts illustrated here are subject to change. I will endeavour to keep the article current in line with future releases.

You might also like...

Date Posted:
Last Updated:
Posted by:
Total Views to date: 43836

11 Comments

- Jesse Foster

Thanks for the article and all the details.

- Roby

Well done!

- Simon

Great article, thanks for writing this up.

- Vinz

Great post Mike! One thing, you have a minor typo under The Example Middleware section. The word "Anyhoo"

- Mike

@Vinz,

That's not actually a typo.

- Vinz

@Mike

My bad. It was my first time to encounter that word, I thought you meant "anyhow" or "anyway". XD

Again. Great post! :)

- abedon

I feel the title of this article is not that felicitous.

ASP.NET 5 is built of top of the concept of OWIN, whose pipeline is totally different from that of the traditional ASP.NET for IIS.

If you run ASP.NET 5 in IIS, the AspNet.Loader.IIS.dll will register an ASP.NET HttpModule and HttpHandler to intercept requests as they flow through the HTTP pipeline and send them through the user-specified OWIN pipeline. Hence the integrated HttpModule such as authentication/authorization is still able to kick in and provides a rich set of functionalities.

So ASP.NET 5's middleware and HttpModule/HttpHandler are not equivalent or comparable because they are not in the same level although they can both operate on request/response with the pipeline pattern. And HttpModule/HttpHandler has not gone.

- Corstian Boerman

It looks as if this technique doesn't work anymore. (As of the VS 2015 RC release)

When adding headers before the `await _next(context);` call the headers are set correctly. Within the Middleware pipeline it looks like the response headers are sent by mvc without finishing this Middleware queue or whatever I can call it.

- Brian

I'm having a hard time with IApplicationBuilder when it comes to the setting the pipeline stage. With IAppBuilder we use something like:
app.UseStageMarker(PipelineStage.PostAuthenticate);

Is this just no longer applicable, or is there a different way of going about it?

- Mike

@Brian

The stage markers belong to the OWIN pipeline model. That doesn't seem to have been adopted in ASP.NET 5. Middleware is executed in the order in which it is added.

- sumalatha

Great article. I have gone through hundreds of articles, finally this gives the comprehensive knowledge of Owin middleware. Thanks.

Recent Comments

Gfw 03/02/2017 09:48
In response to Free SSL Certificates On IIS With LetsEncrypt
I have used WinSimple for about the last 9 months - works great. One thing that you want to make of...

Ted Driver 02/02/2017 13:24
In response to Free SSL Certificates On IIS With LetsEncrypt
This looks great is you have command line access to your web server - what about those of us on Is...

Carl T. 06/11/2016 05:43
In response to Server.MapPath Equivalent in ASP.NET Core
Very succinct and easy to follow. Worked perfectly the first time for me. Thanks!!...

Manoj Kulkarni 04/11/2016 05:47
In response to Entity Framework Core DbContext Updated
Great post....

Sivu 19/10/2016 08:21
In response to Entity Framework Core TrackGraph For Disconnected Data
Oh that's very very very nice ! Thanks for the write up Mike, much appreciated for the taking the to...

Mark 12/10/2016 16:42
In response to ASP.NET Web Pages vNext or Razor Pages
Although "Web Pages" was removed from the roadmap, has it just been renamed to "Razor Pages"?...

Satyabrata 12/10/2016 09:20
In response to Entity Framework Core TrackGraph For Disconnected Data
Nice article. Please write more articles featuring ASP.Net web pages. Thank you...

Julian 26/09/2016 14:27
In response to Loading ASP.NET Core MVC Views From A Database Or Other Location
Fantastic, many thanks Mike! Had got half way down this road before finding your article - saved...

Abolfazl Roshanzamir 14/09/2016 05:36
In response to Loading ASP.NET Core MVC Views From A Database Or Other Location
Nice article. Thanke you so much ....

cyrus 02/09/2016 15:12
In response to ASP.NET Web Pages vNext or Razor Pages
I've got some news. As Damian stated in this link: https://github.com/aspnet/Mvc/issues/5208 “We...