Filters¶
By Steve Smith
Filters in ASP.NET MVC allow you to run code before or after a particular stage in the execution pipeline. Filters can be configured globally, per-controller, or per-action.
Sections
View or download sample from GitHub.
How do filters work?¶
Each filter type is executed at a different stage in the pipeline, and thus has its own set of intended scenarios. Choose what type of filter to create based on the task you need it to perform, and where in the request pipeline it executes. Filters run within the MVC Action Invocation Pipeline, sometimes referred to as the Filter Pipeline, which runs after MVC selects the action to execute.
Different filter types run at different points within the pipeline. Some filters, like authorization filters, only run before the next stage in the pipeline, and take no action afterward. Other filters, like action filters, can execute both before and after other parts of the pipeline execute, as shown below.
Selecting a Filter¶
Authorization filters are used to determine whether the current user is authorized for the request being made.
Resource filters are the first filter to handle a request after authorization, and the last one to touch the request as it is leaving the filter pipeline. They’re especially useful to implement caching or otherwise short-circuit the filter pipeline for performance reasons.
Action filters wrap calls to individual action method calls, and can manipulate the arguments passed into an action as well as the action result returned from it.
Exception filters are used to apply global policies to unhandled exceptions in the MVC app.
Result filters wrap the execution of individual action results, and only run when the action method has executed successfully. They are ideal for logic that must surround view execution or formatter execution.
Implementation¶
All filters support both synchronous and asynchronous implementations through different interface definitions. Choose the sync or async variant depending on the kind of task you need to perform. They are interchangeable from the framework’s perspective.
Synchronous filters define both an OnStageExecuting and OnStageExecuted method (with noted exceptions). The OnStageExecuting method will be called before the event pipeline stage by the Stage name, and the OnStageExecuted method will be called after the pipeline stage named by the Stage name.
using FiltersSample.Helper;
using Microsoft.AspNet.Mvc.Filters;
namespace FiltersSample.Filters
{
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
}
}
}
Asynchronous filters define a single OnStageExecutionAsync method that will surround execution of the pipeline stage named by Stage. The OnStageExecutionAsync method is provided a StageExecutionDelegate delegate which will execute the pipeline stage named by Stage when invoked and awaited.
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Filters;
namespace FiltersSample.Filters
{
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
// do something before the action executes
await next();
// do something after the action executes
}
}
}
Note
You should only implement either the synchronous or the async version of a filter interface, not both. If you need to perform async work in the filter, implement the async interface. Otherwise, implement the synchronous interface. The framework will check to see if the filter implements the async interface first, and if so, it will call it. If not, it will call the synchronous interface’s method(s). If you were to implement both interfaces on one class, only the async method would be called by the framework. Also, it doesn’t matter whether your action is async or not, your filters can be synchronous or async independent of the action.
Filter Scopes¶
Filters can be scoped at three different levels. You can add a particular filter to a particular action as an attribute. You can add a filter to all actions within a controller by applying an attribute at the controller level. Or you can register a filter globally, to be run with every MVC action.
Global filters are added in the ConfigureServices
method in Startup
, when configuring MVC:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
});
services.AddScoped<AddHeaderFilterWithDi>();
}
Filters can be added by type, or an instance can be added. If you add an instance, that instance will be used for every request. If you add a type, it will be type-activated, meaning an instance will be created for each request and any constructor dependencies will be populated by DI. Adding a filter by type is equivalent to filters.Add(new TypeFilterAttribute(typeof(MyFilter)))
.
It’s often convenient to implement filter interfaces as Attributes. Filter attributes are applied to controllers and action methods. The framework includes built-in attribute-based filters that you can subclass and customize. For example, the following filter inherits from ResultFilterAttribute, and overrides its OnResultExecuting
method to add a header to the response.
using Microsoft.AspNet.Mvc.Filters;
namespace FiltersSample.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;
public AddHeaderAttribute(string name, string value)
{
_name = name;
_value = value;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
_name, new string[] { _value });
base.OnResultExecuting(context);
}
}
}
Attributes allow filters to accept arguments, as shown in the example above. You would add this attribute to a controller or action method and specify the name and value of the HTTP header you wished to add to the response:
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}
}
The result of the Index
action is shown below - the response headers are displayed on the bottom right.
Several of the filter interfaces have corresponding attributes that can be used as base classes for custom implementations.
Filter attributes:
- ActionFilterAttribute
- AuthorizationFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
Cancellation and Short Circuiting¶
You can short-circuit the filter pipeline at any point by setting the Result
property on the context parameter provided to the filter method. For instance, the following ShortCircuitingResourceFilter
will prevent any other filters from running later in the pipeline, including any action filters.
using System;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Filters;
namespace FiltersSample.Filters
{
public class ShortCircuitingResourceFilterAttribute : Attribute,
IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult()
{
Content = "Resource unavailable - header should not be set"
};
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}
In the following code, both the ShortCircuitingResourceFilter
and the AddHeader
filter target the SomeResource
action method. However, because the ShortCircuitingResourceFilter
runs first and short-circuits the rest of the pipeline, the AddHeader
filter never runs for the SomeResource
action. This behavior would be the same if both filters were applied at the action method level, provided the ShortCircuitingResourceFilter
ran first (see Ordering).
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}
Configuring Filters¶
Global filters are configured within Startup.cs
. Attribute-based filters that do not require any dependencies can simply inherit from an existing attribute of the appropriate type for the filter in question. To create a filter without global scope that requires dependencies from DI, apply the ServiceFilterAttribute
or TypeFilterAttribute
attribute to the controller or action.
Dependency Injection¶
Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). This is because attributes must have their constructor parameters supplied where they are applied. This is a limitation of how attributes work.
However, if your filters have dependencies you need to access from DI, there are several supported approaches. You can apply your filter to a class or action method using
- The ServiceFilterAttribute
- The TypeFilterAttribute attribute
- IFilterFactory implemented on your attribute
A TypeFilter
will instantiate an instance, using services from DI for its dependencies. A ServiceFilter
retrieves an instance of the filter from DI. The following example demonstrates using a ServiceFilter
:
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}
Using ServiceFilter
without registering the filter type in ConfigureServices
, throws the following exception:
System.InvalidOperationException: No service for type
'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.
To avoid this exception, you must register the AddHeaderFilterWithDI
type in ConfigureServices
:
services.AddScoped<AddHeaderFilterWithDi>();
ServiceFilterAttribute
implements IFilterFactory
, which exposes a single method for creating an IFilter
instance. In the case of ServiceFilterAttribute
, the IFilterFactory
interface’s CreateInstance
method is implemented to load the specified type from the services container (DI).
TypeFilterAttribute
is very similar to ServiceFilterAttribute
(and also implements IFilterFactory
), but its type is not resolved directly from the DI container. Instead, it instantiates the type using an ObjectFactory delegate. Because of this difference, types that are referenced using the TypeFilterAttribute
do not need to be registered with the container first (but they will still have their dependencies fulfilled by the container). Also, TypeFilterAttribute
can optionally accept constructor arguments for the type in question. The following example demonstrates how to pass arguments to a type using TypeFilterAttribute
:
[TypeFilter(typeof(AddHeaderAttribute),
Arguments =new object[] { "Author", "Steve Smith (@ardalis)"})]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
If you have a simple filter that doesn’t require any arguments, but which has constructor dependencies that need to be filled by DI, you can inherit from TypeFilterAttribute
, allowing you to use your own named attribute on classes and methods (instead of [TypeFilter(typeof(FilterType))]
). The following filter shows how this can be implemented:
public class SampleActionFilterAttribute : TypeFilterAttribute
{
public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
{
}
private class SampleActionFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public SampleActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("Business action starting...");
// perform some business logic work
}
public void OnActionExecuted(ActionExecutedContext context)
{
// perform some business logic work
_logger.LogInformation("Business action completed.");
}
}
}
This filter can be applied to classes or methods using the [SampleActionFilter]
syntax, instead of having to use [TypeFilter]
or [ServiceFilter]
.
Note
Avoid creating and using filters purely for logging purposes, since the built-in framework logging features should already provide what you need for logging. If you’re going to add logging to your filters, it should focus on business domain concerns or behavior specific to your filter, rather than MVC actions or other framework events.
IFilterFactory
implements IFilter
. Therefore, an IFilterFactory
instance can be used as an IFilter
instance anywhere in the filter pipeline. When the framework prepares to invoke the filter, attempts to cast it to an IFilterFactory
. If that cast succeeds, the CreateInstance
method is called to create the IFilter
instance that will be invoked. This provides a very flexible design, since the precise filter pipeline does not need to be set explicitly when the application starts.
You can implement IFilterFactory
on your own attribute implementations as another approach to creating filters:
public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new InternalAddHeaderFilter();
}
private class InternalAddHeaderFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"Internal", new string[] { "Header Added" });
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
}
Ordering¶
Filters can be applied to action methods or controllers (via attribute) or added to the global filters collection. Scope also generally determines ordering. The filter closest to the action runs first; generally you get overriding behavior without having to explicitly set ordering. This is sometimes referred to as “Russian doll” nesting, as each increase in scope is wrapped around the previous scope, like a nesting doll.
In addition to scope, filters can override their sequence of execution by implementing IOrderedFilter. This interface simply exposes an int
Order
property, and filters execute in ascending numeric order based on this property. All of the built-in filters, including TypeFilterAttribute
and ServiceFilterAttribute
, implement IOrderedFilter
, so you can specify the order of filters when you apply the attribute to a class or method. By default, the Order
property is 0 for all of the built-in filters, so scope is used as a tie-breaker and (unless Order
is set to a non-zero value) is the determining factor.
Every controller that inherits from the Controller
base class includes OnActionExecuting
and OnActionExecuted
methods. These methods wrap the filters that run for a given action, running first and last. The scope-based order, assuming no Order
has been set for any filter, is:
- The Controller
OnActionExecuting
- The Global filter
OnActionExecuting
- The Class filter
OnActionExecuting
- The Method filter
OnActionExecuting
- The Method filter
OnActionExecuted
- The Class filter
OnActionExecuted
- The Global filter
OnActionExecuted
- The Controller
OnActionExecuted
Note
IOrderedFilter
trumps scope when determining the order in which filters will run. Filters are sorted first by order, then scope is used to break ties. Order defaults to 0 if not set.
To modify the default, scope-based order, you could explicitly set the Order
property of a class-level or method-level filter. For example, adding Order=-1
to a method level attribute:
[MyFilter(Name = "Method Level Attribute", Order=-1)]
In this case, a value of less than zero would ensure this filter ran before both the Global and Class level filters (assuming their Order
property was not set).
The new order would be:
- The Controller
OnActionExecuting
- The Method filter
OnActionExecuting
- The Global filter
OnActionExecuting
- The Class filter
OnActionExecuting
- The Class filter
OnActionExecuted
- The Global filter
OnActionExecuted
- The Method filter
OnActionExecuted
- The Controller
OnActionExecuted
Note
The Controller
class’s methods always run before and after all filters. These methods are not implemented as IFilter
instances and do not participate in the IFilter
ordering algorithm.
Authorization Filters¶
Authorization Filters control access to action methods, and are the first filters to be executed within the filter pipeline. They have only a before stage, unlike most filters that support before and after methods. You should only write a custom authorization filter if you are writing your own authorization framework. Note that you should not throw exceptions within authorization filters, since nothing will handle the exception (exception filters won’t handle them). Instead, issue a challenge or find another way.
Learn more about Authorization.
Resource Filters¶
Resource Filters implement either the IResourceFilter
or IAsyncResourceFilter
interface, and their execution wraps most of the filter pipeline (only Authorization Filters run before them - all other filters and action processing happens between their OnResourceExecuting
and OnResourceExecuted
methods). Resource filters are especially useful if you need to short-circuit most of the work a request is doing. Caching would be one example use case for a resource filter, since if the response is already in the cache, the filter can immediately set a result and avoid the rest of the processing for the action.
The short circuiting resource filter shown above is one example of a resource filter. A very naive cache implementation (do not use this in production) that only works with ContentResult
action results is shown below:
public class NaiveCacheResourceFilterAttribute : Attribute,
IResourceFilter
{
private static readonly Dictionary<string, object> _cache
= new Dictionary<string, object>();
private string _cacheKey;
public void OnResourceExecuting(ResourceExecutingContext context)
{
_cacheKey = context.HttpContext.Request.Path.ToString();
if (_cache.ContainsKey(_cacheKey))
{
var cachedValue = _cache[_cacheKey] as string;
if (cachedValue != null)
{
context.Result = new ContentResult()
{ Content= cachedValue };
}
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
if (!String.IsNullOrEmpty(_cacheKey) &&
!_cache.ContainsKey(_cacheKey))
{
var result = context.Result as ContentResult;
if (result != null)
{
_cache.Add(_cacheKey, result.Content);
}
}
}
}
In OnResourceExecuting
, if the result is already in the static dictionary cache, the Result
property is set on context
, and the action short-circuits and returns with the cached result. In the OnResourceExecuted
method, if the current request’s key isn’t already in use, the current Result
is stored in the cache, to be used by future requests.
Adding this filter to a class or method is shown here:
[TypeFilter(typeof(NaiveCacheResourceFilterAttribute))]
public class CachedController : Controller
{
public IActionResult Index()
{
return Content("This content was generated at " + DateTime.Now);
}
}
Action Filters¶
Action Filters implement either the IActionFilter
or IAsyncActionFilter
interface and their execution surrounds the execution of action methods. Action filters are ideal for any logic that needs to see the results of model binding, or modify the controller or inputs to an action method. Additionally, action filters can view and directly modify the result of an action method.
The OnActionExecuting
method runs before the action method, so it can manipulate the inputs to the action by changing ActionExecutingContext.ActionArguments
or manipulate the controller through ActionExecutingContext.Controller
. An OnActionExecuting
method can short-circuit execution of the action method and subsequent action filters by setting ActionExecutingContext.Result
. Throwing an exception in an OnActionExecuting
method will also prevent execution of the action method and subsequent filters, but will be treated as a failure instead of successful result.
The OnActionExecuted
method runs after the action method and can see and manipulate the results of the action through the ActionExecutedContext.Result
property. ActionExecutedContext.Canceled
will be set to true if the action execution was short-circuited by another filter. ActionExecutedContext.Exception
will be set to a non-null value if the action or a subsequent action filter threw an exception. Setting ActionExecutedContext.Exception
to null effectively ‘handles’ an exception, and ActionExectedContext.Result
will then be executed as if it were returned from the action method normally.
For an IAsyncActionFilter
the OnActionExecutionAsync
combines all the possibilities of OnActionExecuting
and OnActionExecuted
. A call to await next()
on the ActionExecutionDelegate
will execute any subsequent action filters and the action method, returning an ActionExecutedContext
. To short-circuit inside of an OnActionExecutionAsync
, assign ActionExecutingContext.Result
to some result instance and do not call the ActionExectionDelegate
.
Exception Filters¶
Exception Filters implement either the IExceptionFilter
or IAsyncExceptionFilter
interface.
Exception filters handle unhandled exceptions, including those that occur during controller creation and model binding. They are only called when an exception occurs in the pipeline. They can provide a single location to implement common error handling policies within an app. The framework provides an abstract ExceptionFilterAttribute that you should be able to subclass for your needs. Exception filters are good for trapping exceptions that occur within MVC actions, but they’re not as flexible as error handling middleware. Prefer middleware for the general case, and use filters only where you need to do error handling differently based on which MVC action was chosen.
Tip
One example where you might need a different form of error handling for different actions would be in an app that exposes both API endpoints and actions that return views/HTML. The API endpoints could return error information as JSON, while the view-based actions could return an error page as HTML.
Exception filters do not have two events (for before and after) - they only implement OnException
(or OnExceptionAsync
). The ExceptionContext
provided in the OnException
parameter includes the Exception
that occurred. If you set context.Exception
to null, the effect is that you’ve handled the exception, so the request will proceed as if it hadn’t occurred (generally returning a 200 OK status). The following filter uses a custom developer error view to display details about exceptions that occur when the application is in development:
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ViewFeatures;
namespace FiltersSample.Filters
{
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
return;
}
var result = new ViewResult {ViewName = "CustomError"};
result.ViewData = new ViewDataDictionary(_modelMetadataProvider,context.ModelState);
result.ViewData.Add("Exception", context.Exception);
// TODO: Pass additional detailed data via ViewData
context.Exception = null; // mark exception as handled
context.Result = result;
}
}
}
Result Filters¶
Result Filters implement either the IResultFilter
or IAsyncResultFilter
interface and their execution surrounds the execution of action results. Result filters are only executed for successful results - when the action or action filters produce an action result. Result filters are not executed when exception filters handle an exception, unless the exception filter sets Exception = null
.
Note
The kind of result being executed depends on the action in question. An MVC action returning a view would include all razor processing as part of the ViewResult
being executed. An API method might perform some serialization as part of the execution of the result. Learn more about action results
Result filters are ideal for any logic that needs to directly surround view execution or formatter execution. Result filters can replace or modify the action result that’s responsible for producing the response.
The OnResultExecuting
method runs before the action result is executed, so it can manipulate the action result through ResultExecutingContext.Result
. An OnResultExecuting
method can short-circuit execution of the action result and subsequent result filters by setting ResultExecutingContext.Cancel
to true. If short-circuited, MVC will not modify the response; you should generally write to the response object directly when short-circuiting to avoid generating an empty response. Throwing an exception in an OnResultExecuting
method will also prevent execution of the action result and subsequent filters, but will be treated as a failure instead of a successful result.
The OnResultExecuted
method runs after the action result has executed. At this point if no exception was thrown, the response has likely been sent to the client and cannot be changed further. ResultExecutedContext.Canceled
will be set to true if the action result execution was short-circuited by another filter. ResultExecutedContext.Exception
will be set to a non-null value if the action result or a subsequent result filter threw an exception. Setting ResultExecutedContext.Exception
to null effectively ‘handles’ an exception and will prevent the exeception from being rethrown by MVC later in the pipeline. If handling an exception in a result filter, consider whether or not it’s appropriate to write any data to the response. If the action result throws partway through its execution, and the headers have already been flushed to the client, there’s no reliable mechanism to send a failure code.
For an IAsyncResultFilter
the OnResultExecutionAsync
combines all the possibilities of OnResultExecuting
and OnResultExecuted
. A call to await next()
on the ResultExecutionDelegate
will execute any subsequent result filters and the action result, returning a ResultExecutedContext
. To short-circuit inside of an OnResultExecutionAsync
, set ResultExecutingContext.Cancel
to true and do not call the ResultExectionDelegate
.
You can override the built-in ResultFilterAttribute
to create result filters. The AddHeaderAttribute class shown above is an example of a result filter.
Tip
If you need to add headers to the response, do so before the action result executes. Otherwise, the response may have been sent to the client, and it will be too late to modify it. For a result filter, this means adding the header in OnResultExecuting
rather than OnResultExecuted
.
Filters vs. Middleware¶
In general, filters are meant to handle cross-cutting business and application concerns. This is often the same use case for middleware. Filters are very similar to middleware in capability, but let you scope that behavior and insert it into a location in your app where it makes sense, such as before a view, or after model binding. Filters are a part of MVC, and have access to its context and constructs. For instance, middleware can’t easily detect whether model validation on a request has generated errors, and respond accordingly, but a filter can easily do so.
To experiment with filters, download, test and modify the sample.