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.
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
:
Since no authorization is required, it works fine.
Now let’s navigate to \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
:
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
:
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:
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
:
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:
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!
In the case of validation filter, how can I show a proper view? I use some dropdown lists and validation filter cuts out the action method. Because of that the view model is not populated and null exception is thrown.
Great Article, I have started my Journey Here, I suggested this article to more than 100 friends
I am getting “500” internal server error with message “System.InvalidOperationException: The AuthorizationPolicy named: ‘Read’ was not found.” . Do we need to add the policy separately in startup class also for using [Authorize(“Read”)]?
Could you check our source code and compare to your code. You don’t have to add anything in the Startup class.
Hi Vladinir,
Thanks very much for your time, with your help, It works fine now.
Great job.
Regards,
Tam
Hey, no problem.
That’s the least I can do for you since you’ve pointed out the problem with the example and suggested a fix. Thanks!
Hi Vladinir,
Here is the payload on Postman: https://localhost:5001/Books/Create?Id=&Name=2132 with Content-Type: application/x-www-form-urlencoded. It doesn’t return error as expected.
Regards,
Tam
Here is my Postman configuration:
https://uploads.disquscdn.com/images/563e42e27e9f117124114c96803b333cbe107b77a918994d036a157e6feba396.png
That gets you to this request:
https://uploads.disquscdn.com/images/bd903824bcc1b22049735d777553df622985e5cf16a8d5a4e7753c34a1bcf9e4.png
Which ultimately returns the Invalid id response:
https://uploads.disquscdn.com/images/50ac1eeb4b16d001e19daef9faaed2b2549d125088b450b72b5d1e95f76e0398.png
Have you configured it like this?
Hi Vladimir,
Thanks for the fast response.
Yes, I did set Content-Type: application/x-www-form-urlencoded
Here is the code from Postman:
POST /Books/Create HTTP/1.1
Host: localhost:5001
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: ab577537-2d00-4a98-908c-68acf351ae76
I have put the break point OnActionExecuting method and debug through. the param.Value != null, and context.ModelState.IsValid = true.
Regards,
Tam
Try this request out. Pay attention to the payload at the bottom. That’s how MVC forms its request. Use those params as the body of the request (raw is fine) and see what happens.
POST /Books/Create HTTP/1.1
Host: localhost:5001
Content-Type: application/x-www-form-urlencoded
User-Agent: PostmanRuntime/7.15.0
Accept: */*
Cache-Control: no-cache
Postman-Token: d85c1f2f-7886-42bb-87c0-5deaf1d34e24,d008b011-1a6a-4c4d-9686-519b5a01ff1c
Host: localhost:5001
accept-encoding: gzip, deflate
content-length: 13
Connection: keep-alive
cache-control: no-cache
Id=&Name=2132
Hi Vladimir,
This is a great tutorial MVC, thanks for your time and your effort to build a wonderful valuable material for learning.
For ValidateModelAttribute it works as expected using the Postman tool but it doesn’t work with the View when clicking on Create button, it returns error code 415 due to FromBody (public IActionResult Create([FromBody]Book book), if I change to FromForm the view works fine when the action takes place on Create button and displays {“Id”:[“The value ” is invalid.”]}, but with Postman test, the value of variable param inside OnActionExecuting method is not null. Again, great job.
Regards,
Tam
Hey Tam, thanks for the kind words, we appreciate it!
You are exactly on point for the [FromForm], but I am a bit confused why it doesn’t work for you when you changed it.
I made a quick fix, and now it works on both the View and postman requests.
Have you been using Content-Type: application/x-www-form-urlencoded request header in Postman?
On further investigation, I know what might have caused you the problem.
Try executing a application/x-www-form-urlencoded request with this payload in Postman:
Id=&Name=2132
You should get the same result as in the View, since that’s what the request payload looks like when you leave the Id field empty.