Filters in ASP.NET Core MVC allow us to run certain actions before or after specific stages in the request processing pipeline. There are some built-in filters in ASP.NET Core. We can also write custom filters to execute actions at various stages of the request pipeline. They also help us to handle cross-cutting concerns and avoid duplication of codes.

We have discussed Filters in detail in the Implementing Action Filters in ASP.NET Core article. In that article, we have explained how to implement an Action Filter in an ASP.NET Core Web API application. In this article, we’ll explain the different Filter types available in ASP.NET Core MVC and how to implement each type.

If you’ve missed some of the previous articles in the series we recommend visiting the series page: ASP.NET Core MVC Series.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

To download this article’s source code visit: Filters in ASP.NET Core MVC.

First, let’s look at the different types of Filters available in ASP.NET Core MVC.

Filter types

Each filter type is executed at a different stage in the request pipeline.

Authorization filters are run first and are used to determine whether the current user is authorized for the current request. They can short-circuit the pipeline if a request is unauthorized.

Resource filters come right after the authorization. We can use it to implement caching or short-circuit the filter pipeline for performance reasons.

Action filters can run code immediately before and after an individual action method is called. We can use it to manipulate the arguments passed into action and the result returned from the action. Razor Pages do not support Action filters.

Exception filters are used to globally handle all unhandled exceptions that occur in the application.

Result filters can run code immediately before and after the execution of individual action results. They run only when the action method has executed successfully. 

Authorization Filter

Authorization filters control access to action methods. They are the first filters to be executed within the filter pipeline. They have a before method called OnAuthorization(), but they don’t have an after method.

Implementation

Let’s implement an Authorization Filter. For that, we need to implement IAuthorizationFilter interface and the OnAuthorization() method inside it.

In the filter, let’s define a field called _permission which is supplied via the constructor. We can also create a method CheckUserPermission() that performs the authorization logic. 

For testing purposes, let’s assume that the hypothetical user always has just the Read permission and no Write permission. When the user is not authorized, we can set the Result property of the HTTP Context as UnauthorizedResult which will short circuit the execution pipeline.

Let’s create the AuthorizeActionFilter class:

public class AuthorizeActionFilter : IAuthorizationFilter
{        
    private readonly string _permission;

    public AuthorizeActionFilter(string permission)
    {            
        _permission = permission;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = CheckUserPermission(context.HttpContext.User, _permission);

        if (!isAuthorized)
        {
            context.Result = new UnauthorizedResult();
        }
    }

    private bool CheckUserPermission(ClaimsPrincipal user, string permission)
    {
        // Logic for checking the user permission goes here. 
            
        // Let's assume this user has only read permission.
        return permission == "Read";            
    }
}

Now, let’s create an Attribute for the filter that we just created using the TypeFilterAttribute: 

public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(string permission)
        : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { permission };
    }
}

We have the Authorize attribute ready for use now. Now let’s use it in our application.

For testing this attribute, let’s create 3 controller actions – Index(), Read() and Edit().

Let’s leave Index() method without specifying the Authorize attribute. For the Read()  method, let’s specify Authorize attribute with Read permission. Let’s specify the Authorize attribute with Write permission for the Edit() method:

public IActionResult Index()
{
    return View();
}

[Authorize("Read")]
public IActionResult Read()
{
    return View();
}

[Authorize("Write")]
public IActionResult Edit()
{
    return View();
}

Testing

Remember that for testing purposes, we implemented the filter in such a way that only Read permission is available for all users.

Let’s run the application and navigate to \home\index:

home-index

Since no authorization is required, it works fine.

Now let’s navigate to \home\read:

home-read

This action method requires Read permission which is available for the user. 

If we place a breakpoint in the OnAuthorized() method in the AuthorizationFilter, We can verify that it is not executed for the Index() method but is executed for the Read() method.

Now let’s navigate to \home\edit:

home-edit

This action requires the Write permission which is not available for the user and hence it throws an HTTP ERROR 401 denoting an unauthorized request.

What we see here is the standard browser error page for HTTP 401 Response. We can also create custom error pages corresponding to various status codes in our application for a better user experience.

In this section, we have learned how to implement an Authorization Filter.

Resource Filters 

As we understand from the name, Resource filters can be used for handling resources and helps to short circuit the request execution pipeline if required. A common usage of this type of filter is the implementation of Caching. This can avoid the rest of the pipeline when a Cache hit happens.

Implementation

Let’s look at how we can implement caching using the resource filter.

First, let’s define a class CacheResourceFilter implementing IResourceFilter interface. The Resource filter has a before method OnResourceExecuting() and an after method  OnResourceExecuted().

Let’s define a dictionary object _cache for holding the cached value and a string value _cacheKey for storing the Cache key:

public class CacheResourceFilter : IResourceFilter
{
    private static readonly Dictionary<string, object> _cache
            = new Dictionary<string, object>();
    private string _cacheKey;

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        _cacheKey = context.HttpContext.Request.Path.ToString();
        if (_cache.ContainsKey(_cacheKey))
        {
            var cachedValue = _cache[_cacheKey] as string;
            if (cachedValue != null)
            {
                context.Result = new ContentResult()
                { Content = cachedValue };
            }
        }
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
        if (!String.IsNullOrEmpty(_cacheKey) && !_cache.ContainsKey(_cacheKey))
        {
            var result = context.Result as ContentResult;
            if (result != null)
            {
                _cache.Add(_cacheKey, result.Content);
            }
        }
    }
}

In the OnResourceExecuting() method, we can check if the _cacheKey is present in the _cache dictionary. If it is present, we set the context.Result with the cached value. We can short-circuit the filter pipeline by setting the context.Result property. 

In the OnResourceExecuting() method, we check if the _cacheKey is already present in the _cache. If not, we’ll insert the context.Result value into the _cache.

Then, let’s create an attribute for this filter:

public class CacheResourceAttribute : TypeFilterAttribute
{
    public CacheResourceAttribute()
        : base(typeof(CacheResourceFilter))
    {
    }
}

Finally, let’s create a controller and an action method to return a text indicating the time at which the content was generated. Also, let’s decorate the controller with the CacheResource attribute we just created.

[CacheResource]
public class CachedController : Controller
{
    public IActionResult Index()
    {
        return Content("This content was generated at " + DateTime.Now);
    }
}

Testing

 Now, let’s run the application and navigate to /cached:

cached

When we access this URL for the first time, we can see the content is generated with the current timestamp. Then, for all subsequent access to the same URL, we’ll get a cached version of the resource. We can verify this by checking the timestamp in the URL. Also, if we place a breakpoint in the controller action method, we can see that it is hit only in the first request. For all subsequent requests, we can see that we have short-circuited the execution pipeline by using the resource filter.

Great! We have learned how to implement a Resource Filter.

Action Filters

Action filters run right before and after each action, the method is executed. We have discussed implementing an action filter in detail in the article section: Action Filters Implementation.

The linked article shows how to implement an action filter in an ASP.NET Core Web API application. We can follow the same steps for implementing action filters in an ASP.NET Core MVC application.

Implementation

Let’s create an action filter that handles invalid models. If the model is invalid, we are going to return a standard BadRequest response with a custom message.

Our custom validation class needs to  inherit from the abstract ActionFilterAttribute class and override the OnActionExecuting() and OnActionExecuted() methods:

 

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var param = context.ActionArguments.SingleOrDefault();

        if (param.Value == null)
        {
            context.Result = new BadRequestObjectResult("Model is null");
            return;
        }

        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

The OnActionExecuting() runs before the action method and OnActionExecuted() after the action method.

In the OnActionExecuting() method, we can check if the Model is null or ModelState is invalid. In both these cases, we can return a BadRequest response. This way we can handle this across the application instead of having to write this code in each of the action methods.

Now let’s use this filter in an action method:

[ValidateModel]
[HttpPost]
public IActionResult Create([FromForm]Book book)
{
    return View();
}

Testing

If we call this action method without supplying a valid Book model, we can see the standard BadRequest response with the custom message that we have given:

model-validate filters in ASP.NET Core MVC

That’s it! In this section, we have looked at how to implement an action filter.

Exception Filters

Exception Filters are used to handle any unhandled exceptions that occur in our application. They do not have before or after methods. They just implement the OnException() method. This method will be called whenever an unhandled exception occurs in our application.

Implementation

To see the exception filter in action, let’s create an action method that generates an unhandled exception:

public IActionResult GenerateError()
{
    throw new NotImplementedException();
}

Now, let’s create a custom exception filter by implementing the IExceptionFilter interface. We can provide the required exception handling behavior in the OnException() method:

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public CustomExceptionFilter(
        IModelMetadataProvider modelMetadataProvider)
    {
        _modelMetadataProvider = modelMetadataProvider;
    }

    public void OnException(ExceptionContext context)
    {
        var result = new ViewResult { ViewName = "CustomError" };
        result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState);
        result.ViewData.Add("Exception", context.Exception);

        // Here we can pass additional detailed data via ViewData
        context.ExceptionHandled = true; // mark exception as handled
        context.Result = result;
    }
}

In the OnException() method, we can set the ViewResult as CustomError . Then we can pass the exception details into the view as ViewData.

After that, we’ll create the CustomError view and display the error message in a user-friendly manner:

@{
    ViewData["Title"] = "CustomError";
    var exception = ViewData["Exception"] as Exception;
}

<h1>An Error has Occurred</h1>

<p>@exception.Message</p>

Then, we need to configure the exception filter in startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(config => 
        config.Filters.Add(typeof(CustomExceptionFilter)));
}

Testing

Now, let’s run the application and navigate to \home\generateerror:

custom-error

We can see that a custom error page is displayed with details of the exception which we can customize to show only the required information to the user. This way, we can give the user a much better experience and we can handle exceptions across the application in a consistent manner.

Exception Handling – Middlewares vs Filters

Additionally, we can use middleware for handling unhandled exceptions. So, when should we use an exception handling middleware and when should we go for an exception filter?

If we are concerned about errors that may occur outside of the MVC context or our code, for example, we may want to capture an error that occurs inside a middleware or a filter, then we’ll have to go for an exception handling middleware. On the other hand, if we want to get the MVC context during exception handling and perform some action based on that, then we’ll have to use an exception filter.

In this section, we have learned how to implement an exception filter in our application.

Result Filters

We can use Result filters to run code before or after the execution of controller action results. They are executed only if the controller action method is executed successfully. We can write logic to surround the view or to apply some customizations to all the action results in our application.

Let’s say we want to add a particular value to the header of all the action results in our application. 

We are going to take a look at how to implement this using a result filter.

First, let’s create a class AddHeaderFilter that implements the IResultFilter interface. Result filter has a before method called OnResultExecuting() and an after method called OnResultExecuted(). 

In the OnResultExecuting() method, let’s add a custom response header:

public class AddHeaderFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(
            "OnResultExecuting",
            "This header was added by result filter.");
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

After that, let’s create an attribute for the Result filter that we just created:

public class AddHeaderAttribute : TypeFilterAttribute
{
    public AddHeaderAttribute() : base(typeof(AddHeaderFilter))
    {
    }
}

Finally, let’s create an action method in the HomeController and apply the AddHeader result filter attribute.

[AddHeader]
public IActionResult TestResultFilter()
{
    return View();
}

Let’s run the application and navigate to home\testresultfilter.

On inspecting the headers, we can see that the custom header is present in the response:

Headers of Result Filters in ASP.NET Core MVC

This way we can use the Result filter to customize the action results across the application.

Great! In this section, we have learned how to implement a result filter.

Conclusion

In this article we have learned the following topics:

  • The different types of filters available in ASP.NET Core MVC.
  • Creating a custom Authorization Filter.
  • Implementing Caching using Resource Filter.
  • Using Exception filter to handle unhandled exceptions.
  • Adding a custom response header to all our action methods using Resource Filter.

With that, we come to the end of the ASP.NET Core MVC series. We hope you enjoyed reading through and had some good learning in the process!

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!