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.
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 AuthorizationFilter
s 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 MiddlewareFilter
s 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!