I’ve read a number of articles and watched the MVA on custom middleware so I thought it would be time to explore a bit more. The one part which I’ve just taken for granted but been curious about is when there should be an Action passed as a parameter and when an instance of the options for the middleware is passed in. Hopefully this post will add some clarity or at least spark a discussion.

What is Middleware?

To me a way to describe middleware is it defines steps data can progress through, each step leading into the next. On the way in you can check values, map values, log calls, determine logic etc and on the way out do any out bound mapping, logging etc. It can be used for a wide variety of reasons from Authentication to Logging to Routing in MVC etc. Andrew Lock has a good post going over the basics and describing how the HttpContext fits and is used.

The basic premise is that you can specify some sort of action to be done to or on the HttpContext on the way into the pipeline and then subsequently the same can be done on the way out. Additional logging or further processing can be done at various points as well. Very flexible!

Define a Basic Custom Middleware

To describe the techniques I am going to work with in this post I am going to use a very simple example middleware which outputs some text before and after the call to the next step in the pipeline. This is the easiest way to see an output as to what is happening.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("------- Before ------ \n\r");

            await next();

            await context.Response.WriteAsync("\n\r------- After ------");
        });

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

Componentising Custom Middleware

As your build up your request pipeline the above will not be extensible or clean. It will get in the way of other middleware you want to add to the pipeline and we generally want to tidy it, or abstract it, away into it’s own definition. Doing this keeps things simple and means the Configure method and the Middleware class have their own Single Reponsibilities.

public class MyCustomMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        await context.Response.WriteAsync("------- Before ------ \n\r");

        await _next(context);

        await context.Response.WriteAsync("\n\r------- After ------");
    }
}

If we compare the original with the updated class definition it has changed slightly but the common parts are all there. The class definition gets the next RequestDelegate as part of the constructor through DI, the HttpContext gets passed into the executing method and by convention in the Middlware pipeline this is called InvokeAsync. Notice we have to pass the HttpContext through to the _next delegate call.

We can use this directly in the Configure method in Startup however it can be cleaner. We can achieve this through defining an extension method which acts on IApplicationBuilder.

public static class MyCustomMiddlewareExtensions
{
    public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyCustomMiddleware>();
    }
}

Other libraries and middleware authors do the same to make consumption of the pipeline task easy.

Our Startup class is now a lot cleaner.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMyCustomMiddleware();

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

Adding Custom options to Middleware

Writing custom middleware is great but you will come to a point where it needs to be made configurable. But how do we do this? There are a couple of options to adding custom options to your middleware. You can create an options/configuration class for your middleware or inject IConfiguration directly. I personally would lean to the former to allow for less dependency on an external construct to help with unit testing etc.

But when should the middlware options be instantiated and passed into the Middleware?

For me it comes down to the question.

When are you going to use it?

There maybe more but for me there are two options for middleware; single setup configuration with options specified in the ConfigureServices method of the Startup class and multi usage middleware which is configured per usage in the Configure when added to the pipeline.

Let’s explore some examples.

Single Configuration Setup

The single configuration setup is performed in the ConfigureServices method of the Startup and is done through, usually, an extension method which follows the convention of AddX where X describes the the type of middleware that is being configured.

Working with the previous example I want to demonstrate what I am trying to explain with a very simple example.

Let’s define a configuration options class which defines some options which the middleware can use.

public class MyCustomMiddlewareOptions
{
    public bool DisplayBefore { get; set; } = true;

    public bool DisplayAfter { get; set; } = true;
}

So how does an instance of this get into the custom middleware to be used?

Well there are two options; either passing in an instance of the options class or passing in an Action<T>. It seems that passing in the Action<T> option is preferred when setup in ConfigureServices but I imagine it’s down the library author. Let’s implement the pattern with an Action<T>.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMyCustomMiddlewareWithOptions(options => options.DisplayAfter = false);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMyCustomMiddlewareWithOptions();

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

The middleware now can be added into the pipeline and be setup using a lambda expression. It can take the Options in through constructor through the DI mechanism. But it’s an Action<T> so how I am going to get that? Well it comes from the IOptions<T> functionality.

public class MyCustomMiddlewareWithOptions
{
    private readonly RequestDelegate _next;
    private readonly MyCustomMiddlewareOptions _options;

    public MyCustomMiddlewareWithOptions(RequestDelegate next, IOptions<MyCustomMiddlewareOptions> options)
    {
        _next = next;
        _options = options.Value;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (_options.DisplayBefore)
        {
            await context.Response.WriteAsync("------- Before ------ \n\r");
        }

        await _next(context);

        if (_options.DisplayAfter)
        {
            await context.Response.WriteAsync("\n\r------- After ------");
        }
    }
}

So what happens under the hood to get a Action<T> converted to be an IOptions<T>? This is where the extension method to wrap up the middleware addition comes in useful as it keeps it all in one place and makes sure that when the middleware is added to the builder it’s configuration is setup as well.

public static class MyCustomMiddlewareWithOptionsExtensions
{
    public static IServiceCollection AddMyCustomMiddlewareWithOptions(this IServiceCollection service, Action<MyCustomMiddlewareOptions> options = default)
    {
        options = options ?? (opts => { });

        service.Configure(options);
        return service;
    }

    public static IApplicationBuilder UseMyCustomMiddlewareWithOptions(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyCustomMiddlewareWithOptions>();
    }
}

As you can see we are checking that there is actually an instance of Action<MyCustomMiddlewareOptions> whether passed in or if a default value is specified then an empty action is created. Now we have an instance of the action we can call the service.Configure method. Calling the service.Configure method with the action allows the details to be registered under the hood and can then be referenced by IOptions<MyCustomMiddlewareOptions> when wanting DI system to provide the details.

Multiple Configuration Setup

The other option is the multiple configuration setup option where you want to be able to use the same type of middleware more than once in your application pipeline but want to specify configuration per usage. For this option you specify that you require a configuration option instance directly and it’s not hidden behind IOptions<>.

public class MyCustomMiddlewareWithOptionsMultiUse
{
    private readonly RequestDelegate _next;
    private readonly MyCustomMiddlewareOptions _options;

    public MyCustomMiddlewareWithOptionsMultiUse(RequestDelegate next, MyCustomMiddlewareOptions options)
    {
        _next = next;
        _options = options;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (_options.DisplayBefore)
        {
            await context.Response.WriteAsync("------- Before ------ \n\r");
        }

        await _next(context);

        if (_options.DisplayAfter)
        {
            await context.Response.WriteAsync("\n\r------- After ------");
        }
    }
}

Adding this version into the middleware pipeline is much straight forward however using an extension method makes it cleaner from a consumers point of view.

public static class MyCustomMiddlewareWithOptionsMultiUseExtensions
{
    public static IApplicationBuilder UseMyCustomMiddlewareWithOptionsMultiUse(this IApplicationBuilder builder, MyCustomMiddlewareOptions options = default)
    {
        options = options ?? new MyCustomMiddlewareOptions();
        return builder.UseMiddleware<MyCustomMiddlewareWithOptionsMultiUse>(options);
    }
}

Passing in the options instance into the UseMiddleware method call allows for the DI system inject it into the middleware constructor directly.

Using this extension method we can then add in multiple instances into the pipeline with different configuration options specified.

public class Startup
{
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMyCustomMiddlewareWithOptionsMultiUse(new MyCustomMiddlewareOptions {DisplayBefore = true});
        app.UseMyCustomMiddlewareWithOptionsMultiUse(new MyCustomMiddlewareOptions {DisplayAfter = false});

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

Conclusion

In this post I have run through a simple custom middleware example to demonstrate the different ways of passing in custom middleware options POCO to allow for the middleware to be customised but not rely on IConfiguration which allows for improved unit testing and keeps the code clean.

Any questions/comments then please contact me on Twitter @WestDiscGolf