blog post image
Andrew Lock avatar

Andrew Lock

~7 min read

Exploring Middleware as MVC Filters in ASP.NET Core 1.1

Adding a URL culture provider using middleware as filters - Part 1

One of the new features released in ASP.NET Core 1.1 is the ability to use middleware as an MVC Filter. In this post I'll take a look at how the feature is implemented by peering into the source code, rather than focusing on how you can use it. In the next post I'll look at how you can use the feature to allow greater code reuse.

Middleware vs Filters

The first step is to consider why you would choose to use middleware over filters, or vice versa. Both are designed to handle cross-cutting concerns of your application and both are used in a 'pipeline', so in some cases you could choose either successfully.

The main difference between them is their scope. Filters are a part of MVC, so they are scoped entirely to the MVC middleware. Middleware only has access to the HttpContext and anything added by preceding middleware. In contrast, filters have access to the wider MVC context, so can access routing data and model binding information for example.

Generally speaking, if you have a cross cutting concern that is independent of MVC then using middleware makes sense, if your cross cutting concern relies on MVC concepts, or must run midway through the MVC pipeline, then filters make sense.

Middleware and filters

So why would you want to use middleware as filters then? A couple of reasons come to mind for me.

First, you have some middleware that already does what you want, but you now need the behaviour to occur midway through the MVC middleware. You could rewrite your middleware as a filter, but it would be nicer to just be able to plug it in as-is. This is especially true if you are using a piece of third-party middleware and you don't have access to the source code.

Second, you have functionality that needs to logically run as both middleware and a filter. In that case you can just have the one implementation that is used in both places.

Using the MiddlewareFilterAttribute

On the announcement post, you will find an example of how to use Middleware as filters. Here I'll show a cut down example, in which I want to run MyCustomMiddleware when a specific MVC action is called.

There are two parts to the process, the first is to create a middleware pipeline object:

public class MyPipeline 
{
    public void Configure(IApplicationBuilder applicationBuilder) 
    {
        var options = // any additional configuration

        applicationBuilder.UseMyCustomMiddleware(options);
    }
}

and the second is to use an instance of the MiddlewareFilterAttribute on an action or a controller, wherever it is needed.

[MiddlewareFilter(typeof(MyPipeline))]
public IActionResult ActionThatNeedsCustomfilter() 
{
    return View();
}

With this setup, MyCustomMiddleware will run each time the action method ActionThatNeedsCustomfilter is called.

It's worth noting that the MiddlewareFilterAttribute on the action method does not take a type of the middleware component itself (MyCustomMiddleware), it actually takes a pipeline object which configures the middleware itself. Don't worry about this too much as we'll come back to it again later.

For the rest of this post, I'll dip into the MVC repository and show how the feature is implemented.

The MiddlewareFilterAttribute

As we've already seen, the middleware filter feature starts with the MiddlewareFilterAttribute applied to a controller or method. This attribute implements the IFilterFactory interface which is useful for injecting services into MVC filters. The implementation of this interface just requires one method, CreateInstance(IServiceProvider provider):

public class MiddlewareFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{
    public MiddlewareFilterAttribute(Type configurationType)
    {
        ConfigurationType = configurationType;
    }

    public Type ConfigurationType { get; }

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        var middlewarePipelineService = serviceProvider.GetRequiredService<MiddlewareFilterBuilder>();
        var pipeline = middlewarePipelineService.GetPipeline(ConfigurationType);

        return new MiddlewareFilter(pipeline);
    }
}

The implementation of the attribute is fairly self explanatory. First a MiddlewareFilterBuilder object is obtained from the dependency injection container. Next, GetPipeline is called on the builder, passing in the ConfigurationType that was supplied when creating the attribute (MyPipeline in the previous example).

GetPipeline returns a RequestDelegate which represents a middleware pipeline which takes in an HttpContext and returns a Task:

public delegate Task RequestDelegate(HttpContext context);

Finally, the delegate is used to create a new MiddlewareFilter, which is returned by the method. This pattern of using an IFilterFactory attribute to create an actual filter instance is very common in the MVC code base, and works around the problems of service injection into attributes, as well as ensuring each component sticks to the single responsibility principle.

Building the pipeline with the MiddlewareFilterBuilder

In the last snippet we saw the MiddlewareFilterBuilder being used to turn our MyPipeline type into an actual, runnable piece of middleware. Taking a look inside the MiddlewareFilterBuilder, you will see an interesting use case of a Lazy<> with a ConcurrentDictionary, to ensure that each pipeline Type passed in to the service is only ever created once. This was the usage I wrote about in my last post.

The call to GetPipeline initialises a pipeline for the provided type using the BuildPipeline method, shown below in abbreviated form:

private RequestDelegate BuildPipeline(Type middlewarePipelineProviderType)
{
    var nestedAppBuilder = ApplicationBuilder.New();

    // Get the 'Configure' method from the user provided type.
    var configureDelegate = _configurationProvider.CreateConfigureDelegate(middlewarePipelineProviderType);
    configureDelegate(nestedAppBuilder);

    nestedAppBuilder.Run(async (httpContext) =>
    {
        // additional end-middleware, covered later
    });

    return nestedAppBuilder.Build();
}

This method creates a new IApplicationBuilder, and uses it to configure a middleware pipeline, using the custom pipeline supplied earlier (MyPipeline'). It then adds an additional piece of 'end-middleware' at the end of the pipeline which I'll come back to later, and builds the pipeline into a RequestDelegate.

Creating the pipeline from MyPipeline is performed by a MiddlewareFilterConfigurationProvider, which attempts to find an appropriate Configure method on it.

You can think of the MyPipeline class as a mini-Startup class. Just like the Startup class you need a Configure method to add middleware to an IApplicationBuilder, and just like in Startup, you can inject additional services into the method. One of the big differences is that you can't have environment-specific Configure methods like ConfigureDevelopment here - your class must have one, and only one, configuration method called Configure.

The MiddlewareFilter

So just to recap, you add a MiddlewareFilterAttribute to one of your action methods or controllers, passing in a pipeline to use as a filter, e.g. MyPipeline. This uses a MiddlewareFilterBuilder to create a RequestDelegate, which in turn is used to create a MiddlewareFilter. This is the object actually added to the MVC filter pipeline.

The MiddlewareFilter implements IAsyncResourceFilter, so it runs early in the filter pipeline - after AuthorizationFilters have run, but before Model Binding and Action filters. This allows you to potentially short-circuit requests completely should you need to.

The MiddlewareFilter implements the single required method OnResourceExecutionAsync. The execution is very simple. First it records the MVC ResourceExecutingContext context of the filter, as well as the next filter to execute ResourceExecutionDelegate, as a new MiddlewareFilterFeature. This feature is then stored against the HttpContext itself, so it can be accessed elsewhere. The middleware pipeline we created previously is then invoked using the HttpContext.

public class MiddlewareFilter : IAsyncResourceFilter
{
    private readonly RequestDelegate _middlewarePipeline;
    public MiddlewareFilter(RequestDelegate middlewarePipeline)
    {
        _middlewarePipeline = middlewarePipeline;
    }

    public Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        var httpContext = context.HttpContext;

        var feature = new MiddlewareFilterFeature()
        {
            ResourceExecutionDelegate = next,
            ResourceExecutingContext = context
        };
        httpContext.Features.Set<IMiddlewareFilterFeature>(feature);

        return _middlewarePipeline(httpContext);
    }

From the point of view of the middleware pipeline we created, it is as though it was called as part of the normal pipline; it just receives an HttpContext to work with. If needs be though, it can access the MVC context by accessing the MiddlewareFilterFeature.

If you have written any filters previously, something may seem a bit off with this code. Normally, you would call await next() to execute the next filter in the pipeline before returning, but we are just returning the Task from our RequestDelegate invocation. How does the pipeline continue? To see how, we'll skip back to the 'end-middleware' I glossed over in BuildPipeline

Using the end-middleware to continue the filter pipeline

The middleware added at the end of the BuildPipeline method is responsible for continuing the execution of the filter pipeline. An abbreviated form looks like this:

nestedAppBuilder.Run(async (httpContext) =>
{
    var feature = httpContext.Features.Get<IMiddlewareFilterFeature>();

    var resourceExecutionDelegate = feature.ResourceExecutionDelegate;
    var resourceExecutedContext = await resourceExecutionDelegate();

    if (!resourceExecutedContext.ExceptionHandled && resourceExecutedContext.Exception != null)
    {
        throw resourceExecutedContext.Exception;
    }
});

There are two main functions of this middleware. The primary goal is ensuring the filter pipeline is continued after the MiddlewareFilter has executed. This is achieved by loading the IMiddlewareFeatureFeature which was saved to the HttpContext when the filter began executing. It can then access the next filter via the ResourceExecutionDelegate and await its execution as usual.

The second goal, is to behave like a middleware pipeline rather than a filter pipeline when exceptions are thrown. That is, if a later filter or action method throws an exception, and no filter handles the exception, then the end-middleware re-throws it, so that the middleware pipeline used in the filter can handle it as middleware normally would (with a try-catch).

Note that Get<IMiddlewareFilterFeature>() will be called before the end of each MiddlewareFilter. If you have multiple MiddlewareFilters in the pipeline, each one will set a new instance of IMiddlewareFilterFeature, overwriting the values saved earlier. I haven't dug into it, but that could potentially cause an issue if you have middleware in your MyCustomMiddleware that both operates on the response being sent back through the pipeline after other middleware has executed, and also tries to load the IMiddlewareFilterFeature. In that case, it will get the IMiddlewareFilterFeature associated with a different MiddlewareFilter. It's a pretty unlikely scenario I suspect, but still, just watch out for it.

Wrapping up

That brings us to the end of this look under the covers of middleware filters. hopefully you found it interesting, personally, I just enjoy looking at the repos as a source of inspiration should I ever need to implement something similar in the future. Hope you enjoyed it!

Andrew Lock | .Net Escapades
Want an email when
there's new posts?