Middleware

By Steve Smith

Small application components that can be incorporated into an HTTP request pipeline are known collectively as middleware. ASP.NET 5 has integrated support for middleware, which are wired up in an application’s Configure method during Application Startup.

In this article:

Download sample from GitHub.

What is middleware

Middleware are components that are assembled into an application pipeline to handle requests and responses. Each component can choose whether to pass the request on to the next component in the pipeline, and can perform certain actions before and after the next component in the pipeline. Request delegates are used to build this request pipeline, which are then used to handle each HTTP request to your application.

Request delegates are configured using Run, Map, and Use extension methods on the IApplicationBuilder type that is passed into the Configure method in the Startup class. An individual request delegate can be specified in-line as an anonymous method, or it can be defined in a reusable class. These reusable classes are middleware, or middleware components. Each middleware component in the request pipeline is responsible for invoking the next component in the chain, or choosing to short-circuit the chain if appropriate.

Creating a middleware pipeline with IApplicationBuilder

The ASP.NET request pipeline consists of a sequence of request delegates, called one after the next, as this diagram shows (the thread of execution follows the black arrows):

../_images/request-delegate-pipeline.png

Each delegate has the opportunity to perform operations before and after the next delegate. Any delegate can choose to stop passing the request on to the next delegate, and instead handle the request itself. This is referred to as short-circuiting the request pipeline, and is desirable because it allows unnecessary work to be avoided. For example, an authorization middleware function might only call the next delegate in the pipeline if the request is authenticated, otherwise it could short-circuit the pipeline and simply return some form of “Not Authorized” response. Exception handling delegates need to be called early on in the pipeline, so they are able to catch exceptions that occur in later calls within the call chain.

You can see an example of setting up a request pipeline, using a variety of request delegates, in the default web site template that ships with Visual Studio 2015. Its Configure method, shown below, first wires up error pages (in development) or the site’s production error handler, then builds out the pipeline with support for static files, ASP.NET Identity authentication, and finally, ASP.NET MVC.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseBrowserLink();
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());

    app.UseStaticFiles();

    app.UseIdentity();

    // To configure external authentication please see http://go.microsoft.com/fwlink/?LinkID=532715

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Because of the order in which this pipeline was constructed, the middleware configured by the UseExceptionHandler method will catch any exceptions that occur in later calls (in non-development environments). Also, in this example a design decision has been made that static files will not be protected by any authentication. This is a tradeoff that improves performance when handling static files since no other middleware (such as authentication middleware) needs to be called when handling these requests (ASP.NET 5 uses a specific wwwroot folder for all files that should be accessible by default, so there is typically no need to perform authentication before sending these files). If the request is not for a static file, it will flow to the next piece of middleware defined in the pipeline (in this case, Identity). Learn more about Working with Static Files.

Note

Remember: the order in which you arrange your Use[Middleware] statements in your application’s Configure method is very important. Be sure you have a good understanding of how your application’s request pipeline will behave in various scenarios.

The simplest possible ASP.NET application sets up a single request delegate that handles all requests. In this case, there isn’t really a request “pipeline”, so much as a single anonymous function that is called in response to every HTTP request.

1
2
3
4
app.Run(async context =>
{
    await context.Response.WriteAsync("Hello, World!");
});

It’s important to realize that request delegate, as written here, will terminate the pipeline, regardless of other calls to App.Run that you may include. In the following example, only the first delegate (“Hello, World!”) will be executed and displayed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void Configure(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World!");
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World, Again!");
    });

You chain multiple request delegates together making a different call, with a next parameter representing the next delegate in the pipeline. Note that just because you’re calling it “next” doesn’t mean you can’t perform actions both before and after the next delegate, as this example demonstrates:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
    loggerfactory.AddConsole(minLevel: LogLevel.Information);
    var logger = loggerfactory.CreateLogger(_environment);
    app.Use(async (context, next) =>
    {
        logger.LogInformation("Handling request.");
        await next.Invoke();
        logger.LogInformation("Finished handling request.");
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

Warning

Be wary of modifying HttpResponse after invoking next, since one of the components further down the pipeline may have written to the response, causing it to be sent to the client.

Note

This ConfigureLogInline method is called when the application is run with an environment set to LogInline. Learn more about Working with Multiple Environments. We will be using variations of Configure[Environment] to show different options in the rest of this article. The easiest way to run the samples in Visual Studio is with the web command, which is configured in project.json. See also Application Startup.

In the above example, the call to await next.Invoke() will call into the delegate on line 14. The client will receive the expected response (“Hello from LogInline”), and the server’s console output includes both the before and after messages, as you can see here:

../_images/console-loginline.png

Run, Map, and Use

You configure the HTTP pipeline using the extensions Run, Map, and Use. By convention, the Run method is simply a shorthand way of adding middleware to the pipeline that doesn’t call any other middleware (that is, it will not call a next request delegate). Thus, Run should only be called at the end of your pipeline. Run is a convention, and some middleware components may expose their own Run[Middleware] methods that should only run at the end of the pipeline. The following two examples (one using Run and the other Use) are equivalent to one another, since the second one doesn’t use its next parameter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public void ConfigureEnvironmentOne(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

public void ConfigureEnvironmentTwo(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

Note

The IApplicationBuilder interface itself exposes a single Use method, so technically they’re not all extension methods.

We’ve already seen several examples of how to build a request pipeline with Use. Map* extensions are used as a convention for branching the pipeline. The current implementation supports branching based on the request’s path, or using a predicate. The Map extension method is used to match request delegates based on a request’s path. Map simply accepts a path and a function that configures a separate middleware pipeline. In the following example, any request with the base path of /maptest will be handled by the pipeline configured in the HandleMapTest method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private static void HandleMapTest(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test Successful");
    });
}

public void ConfigureMapping(IApplicationBuilder app)
{
    app.Map("/maptest", HandleMapTest);

}

Note

When Map is used, the matched path segment(s) are removed from HttpRequest.Path and appended to HttpRequest.PathBase for each request.

In addition to path-based mapping, the MapWhen method supports predicate-based middleware branching, allowing separate pipelines to be constructed in a very flexible fashion. Any predicate of type Func<HttpContext, bool> can be used to map requests to a new branch of the pipeline. In the following example, a simple predicate is used to detect the presence of a querystring variable branch:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Branch used.");
    });
}

public void ConfigureMapWhen(IApplicationBuilder app)
{
    app.MapWhen(context => {
        return context.Request.Query.ContainsKey("branch");
    }, HandleBranch);

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

Using the configuration shown above, any request that includes a querystring value for branch will use the pipeline defined in the HandleBranch method (in this case, a response of “Branch used.”). All other requests (that do not define a querystring value for branch) will be handled by the delegate defined on line 17.

Built-in middleware

ASP.NET ships with the following middleware components:

Middleware
Middleware Description
Authentication Provides authentication support.
CORS Configures Cross-Origin Resource Sharing.
Diagnostics Includes support for error pages and runtime information.
🔧 Routing Define and constrain request routes.
Session Provides support for managing user sessions.
Static Files Provides support for serving static files, and directory browsing.

Writing middleware

For more complex request handling functionality, the ASP.NET team recommends implementing the middleware in its own class, and exposing an IApplicationBuilder extension method that can be called from the Configure method. The simple logging middleware shown in the previous example can be converted into a middleware class that takes in the next RequestDelegate in its constructor and supports an Invoke method as shown:

RequestLoggerMiddleware.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Logging;
using System.Threading.Tasks;

namespace MiddlewareSample
{
    public class RequestLoggerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public RequestLoggerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
        {
            _next = next;
            _logger = loggerFactory.CreateLogger<RequestLoggerMiddleware>();
        }

        public async Task Invoke(HttpContext context)
        {
            _logger.LogInformation("Handling request: " + context.Request.Path);
            await _next.Invoke(context);
            _logger.LogInformation("Finished handling request.");
        }
    }
}

The middleware follows the Explicit Dependencies Principle and exposes all of its dependencies in its constructor. Middleware can take advantage of the UseMiddleware<T> extension to inject services directly into their constructors, as shown in the example below. Dependency injected services are automatically filled, and the extension takes a params array of arguments to be used for non-injected parameters.

RequestLoggerExtensions.cs
1
2
3
4
5
6
7
public static class RequestLoggerExtensions
{
    public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestLoggerMiddleware>();
    }
}

Using the extension method and associated middleware class, the Configure method becomes very simple and readable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public void ConfigureLogMiddleware(IApplicationBuilder app,
    ILoggerFactory loggerfactory)
{
    loggerfactory.AddConsole(minLevel: LogLevel.Information);

    app.UseRequestLogger();

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

Although RequestLoggerMiddleware requires an ILoggerFactory parameter in its constructor, neither the Startup class nor the UseRequestLogger extension method need to explicitly supply it. Instead, it is automatically provided through dependency injection performed within UseMiddleware<T>.

Testing the middleware (by setting the ASPNET_ENV environment variable to LogMiddleware) should result in output like the following (when using WebListener):

../_images/console-logmiddleware.png

Note

You can see another example of UseMiddleware<T> in action in the UseStaticFiles extension method, which is used to create the StaticFileMiddleware with its required constructor parameters. In this case, the StaticFileOptions parameter is passed in, but other constructor parameters are supplied by UseMiddleware<T> and dependency injection.

Summary

Middleware provide simple components for adding features to individual web requests. Applications configure their request pipelines in accordance with the features they need to support, and thus have fine-grained control over the functionality each request uses. Developers can easily create their own middleware to provide additional functionality to ASP.NET applications.