Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Customizing ASP.NET Core Part 9: ActionFilter

DZone's Guide to

Customizing ASP.NET Core Part 9: ActionFilter

We take a look at making your own ActionFilter classes in order to keep Actions small and readable. Read on to started mastering ASP.NET Core!

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

This post is a little late this time. My initial plan was to throw out two posts of this series per week, but this didn't work out since there are sometimes some more family and work tasks to do than expected.

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

Initial Series Topics

About ActionFilters

Action filters are a little bit like middleware, 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 the actions is executed 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 log-on 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 sections to place code in to execute before and after the action is executed. This ActionFilters cannot be used as attributes. If you want to use the ActionFilters as attributes in your Controllers, you need to drive 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 as a Web API or 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 Controllers and Actions directly. You can do this in an action filter to not mess up the actions with not 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 I 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.AddMvc()
    .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.AddMvc()
    .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, when using this attribute, the Filter isn't instantiated by the dependency injection container and the arguments need to get specified as an 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 end the arguments are specified with the TypeFilterAttribute

Conclusion

Personally, I like the way we can 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 am to use an ActionFilter.

There are some more kinds of filters, which all work similarly. 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. Customizing ASP.NET Core Part 10: TagHelpers.

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
web dev ,c# ,asp.net core ,actions ,filters ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}