This article shows how the ActionFilterAttribute class can be used in an ASP.NET Core 3.0 MVC application.
The ActionFilterAttribute class is an implementation of the IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, and the IOrderedFilter interfaces. This filter can be used as a method filter, controller filter, or global filter for all MVC HTTP requests, or more precisely an ActionFilterAttribute can be used directly, as a ServiceFilter, as a TypeFilter, or in the Startup class. The ServiceFilter class can be used to apply the different custom attribute implementations in the controller classes. By using the ServiceFilter, it is possible to use constructor injection using the application IoC. This is great improvement compared to the the previous version which forced us the use property injection for child dependencies or to add the dependencies directly.
Code: https://github.com/damienbod/AspNetCoreFilters
2019-07-27: Updated to ASP.NET Core 3.0
2019-01-30: Updated to ASP.NET Core 2.2
2017-08-18: Updated to ASP.NET Core 2.0
2017-07-01: Updated to VS2017 and csproj
2017-07-01: Updated to ASP.NET Core 1.0 RTM
2016-05-17: Updated to ASP.NET Core 1.0 RC2 dotnet
2015-11-18: Updated to ASP.NET Core 1.0 RC1
2015-10-16: Updated to ASP.NET Core 1.0 beta8
Using the filter as a ServiceFilter
In the following examples, custom implementations of the ActionFilterAttribute are used which implement the four synchronous method which can be overridden. The ILoggerFactory is used to log the method calls which is added to the custom action filter using constructor injection.
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; namespace AspNetCore.Filters.ActionFilters { public class ClassConsoleLogActionOneFilter : ActionFilterAttribute { private readonly ILogger _logger; public ClassConsoleLogActionOneFilter(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger("ClassConsoleLogActionOneFilter"); } public override void OnActionExecuting(ActionExecutingContext context) { _logger.LogWarning("ClassFilter OnActionExecuting"); base.OnActionExecuting(context); } public override void OnActionExecuted(ActionExecutedContext context) { _logger.LogWarning("ClassFilter OnActionExecuted"); base.OnActionExecuted(context); } public override void OnResultExecuting(ResultExecutingContext context) { _logger.LogWarning("ClassFilter OnResultExecuting"); base.OnResultExecuting(context); } public override void OnResultExecuted(ResultExecutedContext context) { _logger.LogWarning("ClassFilter OnResultExecuted"); base.OnResultExecuted(context); } } }
Because the filters will be used as a ServiceType, the different custom filters need to be registered with the framework IoC. If the action filters were used directly, this would not be required.
public void ConfigureServices(IServiceCollection services) { services.AddScoped<ConsoleLogActionOneFilter>(); services.AddScoped<ConsoleLogActionTwoFilter>(); services.AddScoped<ClassConsoleLogActionOneFilter>(); services.AddControllers().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); }
The different custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute.
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using AspNetCore.Filters.ActionFilters; using Microsoft.Extensions.Logging; namespace AspNetCore.Controllers { [ServiceFilter(typeof(ClassConsoleLogActionOneFilter))] [Route("api/[controller]")] public class TestController : ControllerBase { private readonly ILogger _logger; public TestController(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger("TestController"); } // GET: api/test [HttpGet] [ServiceFilter(typeof(ConsoleLogActionOneFilter))] [ServiceFilter(typeof(ConsoleLogActionTwoFilter))] public IEnumerable<string> Get() { _logger.LogInformation("Executing Http Get all"); return new string[] { "test data one", "test data two" }; } } }
When the application is started (dotnet run), the HTTP request runs as following:
The action overrides are executed first, then the result overrides. The class filter wraps the method filters.
Using the filter as a global filter
The custom implementations of the action filter can also be added globally in the ConfigureServices method in the startup class. The is not added using the framework IoC here, so the loggerFactory is created and added manually in the AddControllers method via the config options.
public void ConfigureServices(IServiceCollection services) { services.AddScoped<ConsoleLogActionOneFilter>(); services.AddScoped<ConsoleLogActionTwoFilter>(); services.AddScoped<ClassConsoleLogActionBaseFilter>(); services.AddScoped<ClassConsoleLogActionOneFilter>(); services.AddScoped<CustomOneLoggingExceptionFilter>(); services.AddScoped<CustomTwoLoggingExceptionFilter>(); services.AddScoped<GlobalFilter>(); services.AddScoped<GlobalLoggingExceptionFilter>(); services.AddScoped<CustomOneResourceFilter>(); services.AddControllers(config => { config.Filters.Add<GlobalFilter>(); config.Filters.Add<GlobalLoggingExceptionFilter>(); }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0); }
The global filter is wrapped outside of the controller class filters per default.
Using the filter with base controllers
The action filter can also be applied to child and parent MVC controllers. The action filter on the child controller is wrapped around the base controller.
[ServiceFilter(typeof(ClassConsoleLogActionOneFilter))] [Route("api/[controller]")] public class TestWithBaseController : BaseController { private readonly ILogger _logger; public TestWithBaseController(ILoggerFactory loggerFactory): base(loggerFactory) { _logger = loggerFactory.CreateLogger("TestWithBaseController"); }
The base controller implementation:
[ServiceFilter(typeof(ClassConsoleLogActionBaseFilter))] [Route("api/[controller]")] public class BaseController : ControllerBase { private readonly ILogger _logger; public BaseController(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger("BaseController"); } [HttpGet] [HttpGet("getall")] [ServiceFilter(typeof(ConsoleLogActionOneFilter))] [ServiceFilter(typeof(ConsoleLogActionTwoFilter))] public IEnumerable<string> GetAll() { _logger.LogInformation("Executing Http Get all"); return new string[] { "test data one", "test data two" }; } }
A HTTP request is executed as follows:
Using the filter with an order
The execution order in the HTTP request can also be set when using an action filter which is a great improvement compared to Web API. The action filters with the highest order value will be executed last. It doesn’t matter if the filter is defined on a class or on a method, if the order properties are different, this property will be used. By using the order property on a filter, you have total control. This is great to have, but I would avoid using this if possible and stick to the conventions. I would try to design the application so that the usage of the order is not required.
[ServiceFilter(typeof(ClassConsoleLogActionOneFilter), Order=3)] [Route("api/[controller]")] public class TestWithOrderedFiltersController : Controller { private readonly ILogger _logger; public TestWithOrderedFiltersController(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger("TestWithOrderedFiltersController"); } // GET: api/test [HttpGet] [ServiceFilter(typeof(ConsoleLogActionOneFilter), Order = 5)] [ServiceFilter(typeof(ConsoleLogActionTwoFilter), Order = 2)] public IEnumerable<string> Get() { _logger.LogInformation("TestWithOrderedFiltersController Http Get all"); return new string[] { "test data one", "test data two" }; } }
The execution of the HTTP request is controller by setting the order of the action filters.
Notes
You could also use the ActionFilter directly or as a TypeFilter. See Filip WOJCIESZYN’s blog for a detailed description of this.
Links:
https://github.com/aspnet/AspNetCore/tree/master/src/Mvc/Mvc.Core/src/Filters
Action filters, service filters and type filters in ASP.NET 5 and MVC 6
[…] Un résumé du fonctionnement des Action Filters avec ASP.NET 5. […]
Do you know the difference between Filter and Middleware ?
Hi Christophe
Thanks for the comment.
Here’s a link which explains this:
https://docs.asp.net/en/latest/mvc/controllers/filters.html#filters-vs-middleware
Hope this helps
Greetings Damien
All of the code executes w/o err but i never see a console window. How do I get the console to open?
You can start from the command line
> dotnet run
Greetings Damien
[…] 对此的一个很好的参考是:https://damienbod.com/2015/09/15/asp-net-5-action-filters/ […]
[…] ASP.NET Core Action Filters […]
[…] filtros de acción principales de ASP.NET […]
[…] ASP.NET Core Action Filters […]