ActionFilter

We keep on customizing on the controller level in this ninth post of this series. I’ll have a look into ActionFilters and how to create your own ActionFilter to keep your Actions small and readable.

About ActionFilters

Action filters are a little bit like middlewares, but are executed immediately on a specific action or on all actions of a specific controller. If you apply an ActionFilter as a global one, it executes on all actions in your application. ActionFilters are created to execute code right before or after the action is executed. They are introduced to execute aspects that are not part of the actual action logic. Authorization is such an aspect. I’m sure you already know the AuthorizeAttribute to allow users or groups to access specific Actions or Controllers. The AuthorizeAttribute actually is an ActionFilter. It checks whether the logged-on user is authorized or not. If not it redirects to the login page.

The next sample shows the skeletons of a normal ActionFilters and an async ActionFilter:

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
    }
}

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        // do something before the action executes
        var resultContext = await next();
        // do something after the action executes; resultContext.Result will be set
    }
}

As you can see here, there are always two methods to place code to execute before and after the target action is executed. This ActionFilters cannot be used as attribute. If you want to use the ActionFilters as attribute in your Controllers, you need to derive from Attribute or from ActionFilterAttribute:

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

This code shows a simple ActionFilter which always returns a BadRequestObjectResult, if the ModelState is not valid. This may be useful within a Web API as a default check on POST, PUT and PATCH requests. This could be extended with a lot more validation logic. We’ll see how to use it later on.

Another possible use case for an ActionFilter is logging. You don’t need to log in the controller Actions directly. You can do this in an action filter to keep your actions readable with relevant code:

public class LoggingActionFilter : IActionFilter
{
    ILogger _logger;
    public LoggingActionFilter(ILoggerFactory loggerFactory)
    {

        _logger = loggerFactory.CreateLogger<LoggingActionFilter>();
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        // do something before the action executes
        _logger.LogInformation($"Action '{context.ActionDescriptor.DisplayName}' executing");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // do something after the action executes
        _logger.LogInformation($"Action '{context.ActionDescriptor.DisplayName}' executed");
    }
}

This logs an informational message out to the console. You are able to get more information about the current Action out of the ActionExecutingContext or the ActionExecutedContext e.g. the arguments, the argument values and so on. This makes the ActionFilters pretty useful.

Using the ActionFilters

ActionFilters that actually are Attributes can be registered as an attribute of an Action or a Controller:

[HttpPost]
[ValidateModel] // ActionFilter as attribute
public ActionResult<Person> Post([FromBody] Person model)
{
    // save the person
    
	return model; //just to test the action
}

Here we use the ValidateModelAttribute that checks the ModelState and returns a BadRequestObjectResult in case the ModelState is invalid and we don’t need to check the ModelState in the actual Action.

To register ActionFilters globally you need to extend the MVC registration in the CofnigureServices method of the Startup.cs:

services.AddControllersWithViews()
    .AddMvcOptions(options =>
    {
        options.Filters.Add(new SampleActionFilter());
        options.Filters.Add(new SampleAsyncActionFilter());
    });

ActionFilters registered like this, are getting executed on every action. This way you are able to use ActionFilters that don’t derive from Attribute.

The Logging LoggingActionFilter we created previously is a little more special. It is depending on an instance of an ILoggerFactory, which need to be passed into the constructor. This won’t work well as an attribute, because Attributes don’t support constructor injection via dependency injection. The ILoggerFactory is registered in the ASP.NET Core dependency injection container and needs to be injected into the LoggingActionFilter.

Because of this, there are some more ways to register ActionFilters. Globally we are able to register it as a type, that gets instantiated by the dependency injection container and the dependencies can be solved by the container.

services.AddControllersWithViews()
    .AddMvcOptions(options =>
    {
        options.Filters.Add<LoggingActionFilter>();
    })

This works well. We now have the ILoggerFactory in the filter.

To support automatic resolution in Attributes, you need to use the ServiceFilterAttribute on the Controller or Action level:

[ServiceFilter(typeof(LoggingActionFilter))]
public class HomeController : Controller
{

In addition to the global filter registration, the ActionFilter needs to be registered in the ServiceCollection before we can use it with the ServiceFilterAttribute:

services.AddSingleton<LoggingActionFilter>();

To be complete, there is another way to use ActionFilters that needs arguments passed into the constructor. You can use the TypeFilterAttribute to automatically instantiate the filter. But using this attribute, the Filter isn’t instantiate by the dependency injection container and the arguments need to get specified as argument of the TypeFilterAttribute. See the next snippet from the docs:

[TypeFilter(typeof(AddHeaderAttribute),
    Arguments = new object[] { "Author", "Juergen Gutsch (@sharpcms)" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}

The Type of the filter and the arguments are specified with the TypeFilterAttribute.

Conclusion

Personally I like the way to keep the actions clean using ActionFilters. If I find repeating tasks inside my Actions, that are not really relevant to the actual responsibility of the Action, I try to move it out to an ActionFilter, or maybe a ModelBinder or a MiddleWare, depending on how globally it should work. The more it is relevant to an Action the more likely I use an ActionFilter.

There are some more kind of filters, which all work similar. To learn more about the different kind of filters, you definitely need to read the docs.

In the tenth part of the series we move to the actual view logic and extend the Razor Views with custom TagHelpers.


Sessions

This is series of articles on Building Conf planner app with Asp.net Core: