ASP.NET Core 3.0 Action Filters

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:

aspnet5actionfilters_004_test

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.

aspnet5actionfilters_001_global

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:

aspnet5actionfilters_002_base

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.

aspnet5actionfilters_003_order

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/ActionFilterAttribute.cs

https://github.com/aspnet/AspNetCore/tree/master/src/Mvc/Mvc.Core/src/Filters

http://www.strathweb.com/2015/06/action-filters-service-filters-type-filters-asp-net-5-mvc-6/

9 comments

  1. […] Un résumé du fonctionnement des Action Filters avec ASP.NET 5. […]

  2. Do you know the difference between Filter and Middleware ?

    1. 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

  3. All of the code executes w/o err but i never see a console window. How do I get the console to open?

    1. You can start from the command line

      > dotnet run

      Greetings Damien

  4. […] 对此的一个很好的参考是:https://damienbod.com/2015/09/15/asp-net-5-action-filters/ […]

  5. […] ASP.NET Core Action Filters […]

  6. […] filtros de acción principales de ASP.NET […]

  7. […] ASP.NET Core Action Filters […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.