Problem
How to run code before and after MVC request pipeline in ASP.NET Core.
Solution
In an empty project, update Startup
class to add services and middleware for MVC:
public void ConfigureServices
(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Add the class to implement filter:
public class ParseParameterActionFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
object param;
if (context.ActionArguments.TryGetValue("param", out param))
context.ActionArguments["param"] = param.ToString().ToUpper();
else
context.ActionArguments.Add("param", "I come from action filter");
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
In the Home controller, add an action method that uses Action filter:
[ParseParameterActionFilter]
public IActionResult ParseParameter(string param)
{
return Content($"Hello ParseParameter. Parameter: {param}");
}
Browse to /Home/ParseParameter, you’ll see:
Discussion
Filter runs after an action method has been selected to execute. MVC provides built-in filters for things like authorization and caching. Custom filters are very useful to encapsulate reusable code that you want to run before or after action methods.
Filters can short-circuit the result, i.e., stops the code in your action from running and return a result to the client. They can also have services injected into them via service container, which makes them very flexible.
Filter Interfaces
Creating a custom filter requires implementing an interface
for the type of filter you require. There are two flavours of interface
s for most filter types, synchronous and asynchronous:
public class HelloActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
public class HelloAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
await next();
}
}
You can short-circuit the filter pipeline by setting the Result
(of type IActionResult
) property on context parameter (for Async filters, don’t call the next
delegate):
public class SkipActionFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
context.Result = new ContentResult
{
Content = "I'll skip the action execution"
};
}
public void OnActionExecuted(ActionExecutedContext context)
{ }
}
[SkipActionFilter]
public IActionResult SkipAction()
{
return Content("Hello SkipAction");
}
For Result
filters, you could short-circuit by setting the Cancel
property on context parameter and sending a response:
public void OnResultExecuting(ResultExecutingContext context)
{
context.Cancel = true;
context.HttpContext.Response.WriteAsync("I'll skip the result execution");
}
[SkipResultFilter]
public IActionResult SkipResult()
{
return Content("Hello SkipResult");
}
Filter Attributes
MVC provides abstract
base classes that you can inherit from to create custom filters. These abstract
classes inherit from Attribute
class and therefore can be used to decorate controllers and action methods:
ActionFilterAttribute
ResultFilterAttribute
ExceptionFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute
Filter Types
There are various type of filters that run at different stages of the filter pipeline. Below, a figure from the official documentation illustrates the sequence:
Authorization
This is the first filter to run and short circuits request for unauthorized users. They only have one method (unlike most other filters that have Executing
and Executed
methods). Normally, you won’t write your own Authorization filters, the built-in filter calls into framework’s authorization mechanism.
Resource
They run before model binding and can be used for changing how it behaves. Also, they run after the result has been generated and can be used for caching, etc.
Action
They run before and after the action method, thus are useful to manipulate action parameters or its result. The context supplied to these filters let you manipulate the action parameters, controller and result.
Exception
They can be used for unhandled exception before they’re written to the response. Exception handling middleware works for most scenarios, however this filter can be used if you want to handle errors differently based on the invoked action.
Result
They run before and after the execution of action method’s result, if the result was successful. They can be used to manipulate the formatting of result.
Filter Scope
Filters can be added at different levels of scope: Action, Controller and Global. Attributes are used for action and controller level scope. For globally scoped filters, you need to add them to filter collection of MvcOptions
when configuring services in Startup
:
services.AddMvc(options =>
{
options.Filters.Add(new AddDeveloperResultFilter("Tahir Naushad"));
options.Filters.Add(typeof(GreetDeveloperResultFilter));
});
Filters are executed in a sequence:
- The
Executing
methods are called first for Global > Controller > Action filters.
- Then
Executed
methods are called for Action > Controller > Global filters.
Filter Dependency Injection
In order to use filters that require dependencies injected at runtime, you need to add them by Type. You can add them globally (as illustrated above), however, if you want to apply them to action or controller (as attributes), then you have two options:
ServiceFilterAttribute
This attributes retrieves the filter using service container. To use it:
Create a filter that uses dependency injection:
public class GreetingServiceFilter : IActionFilter
{
private readonly IGreetingService greetingService;
public GreetingServiceFilter(IGreetingService greetingService)
{
this.greetingService = greetingService;
}
public void OnActionExecuting(ActionExecutingContext context)
{
context.ActionArguments["param"] =
this.greetingService.Greet("James Bond");
}
public void OnActionExecuted(ActionExecutedContext context)
{ }
}
Add filter to service container:
services.AddScoped<GreetingServiceFilter>();
Apply it using ServiceFilterAttribute
:
[ServiceFilter(typeof(GreetingServiceFilter))]
public IActionResult GreetService(string param)
TypeFilterAttribute
This attribute doesn’t need registering the filter in service container and initiates the type using ObjectFactory
delegate. To use it:
Create a filter that uses dependency injection:
public class GreetingTypeFilter : IActionFilter
{
private readonly IGreetingService greetingService;
public GreetingTypeFilter(IGreetingService greetingService)
{
this.greetingService = greetingService;
}
public void OnActionExecuting(ActionExecutingContext context)
{
context.ActionArguments["param"] = this.greetingService.Greet("Dr. No");
}
public void OnActionExecuted(ActionExecutedContext context)
{ }
}
Apply it using TypeFilterAttribute
:
[TypeFilter(typeof(GreetingTypeFilter))]
public IActionResult GreetType1(string param)
You could also inherit from TypeFilterAttribute
and then use without TypeFilter
:
public class GreetingTypeFilterWrapper : TypeFilterAttribute
{
public GreetingTypeFilterWrapper() : base(typeof(GreetingTypeFilter))
{ }
}
[GreetingTypeFilterWrapper]
public IActionResult GreetType2(string param)