ASP.NET Core MVC uses number of built-in filters like Authorization, Resource, Action, Exception, and Result filters. Filters help you to remove repetitive code by injecting them at certain stages of the request pipeline.
Action filters execute custom code before and after execution of the Action method in specific sequence. You can inject filter execution using ASP.NET Core MVC dependency injection.
Authorization filters run first in the pipeline and useful to determine if the current request is authorized. Authorization filters can short-circuit the remaining pipeline if the request is not authorized.
Resource filters invoke right after Authorization filters. They are used for caching, short circuit rest of the pipeline, validate content type requested is correct.
Resource filters execute before model binding so you can handle binding for the specific requests.
Action filters run before and after the actual MVC action method. code executes. They are useful to manipulate arguments passed, as model binding already happened.
Action result can short circuit the rest of the pipeline and or redirect to different Action Method.
Exception filters are useful to write MVC specific error code, to handle all unhandled exceptions globally.
Result filter runs before and after IActionResult of Action Method executed successfully. You can override the result of IActionResult using the Result filter.
The following diagram shows the sequence of Filter Execution. Request pipeline executes Authorization filters first, if authorization is not successful then the rest of the pipeline is not executed. Similarly, other filters can escape the remaining pipeline if any validation fails.
In the following sections, we will implement all-action filters using the ASP.NET Core MVC web application. You can follow Getting Started with ASP.NET Core MVC and .NET5 to create .NET Web application.
Authorization filter has before method OnAuthorization from IAuthorizationFilter interface that executes before actual MVC action method executes, this does not have any after method.
Here we will implement an Authorization filter to validate the request's IP address. If the request is coming from a list of predefined IP addresses then only the Action method will be executed.
Create a new folder with the name Filters. In this folder, we will keep all filter implementation. Add a new class with name AuthorizeIPAddress.cs
Inherit IAuthorizationFilter to AuthorizeIPAddress class.
public class AuthorizeIPAddress : IAuthorizationFilter { }
Add a constructor to set allowed IP addresses list from where requests to the application will be allowed.
public class AuthorizeIPAddress : IAuthorizationFilter { private readonly string _allowedIPAddress; public AuthorizeIPAddress(string allowedIPAddress) { this._allowedIPAddress = allowedIPAddress; } }
Implement OnAuthorization method of interface IAuthorizationFilter. This method will get HTTP request's IP address using HTTPContext.
If the request is made from the allowed IP address then action method execution will happen otherwise Status403Forbidden will be returned to the user.
public void OnAuthorization (AuthorizationFilterContext context) { var requestIp = context.HttpContext.Connection.RemoteIpAddress; var ipAddresses = this._allowedIPAddress.Split(';'); var unauthorizedIp = true; if (requestIp.IsIPv4MappedToIPv6) { requestIp = requestIp.MapToIPv4(); } foreach (var address in ipAddresses) { var testIp = IPAddress.Parse(address); if (testIp.Equals(requestIp)) { unauthorizedIp = false; break; } } if (unauthorizedIp) { context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden); return; } }
Open Startup.cs file and add the following code to ConfigureServices. This code adds an Authorization filter to the pipeline and passes the list of allowed IP addresses. You can add this list to appsettings.json of application.
public void ConfigureServices(IServiceCollection services) { //other configuration services.AddScoped<AuthorizeIPAddress>(container => { //test for valid authorization return new AuthorizeIPAddress("127.0.0.1;192.168.1.5;::1"); }); }
This list only authorize requests coming from the IPv4 addresses of 127.0.0.1 and 192.168.1.5 and the IPv6 loopback address of ::1
Now you can implement the AuthorizeIPAddress authorization filter with the Action method or controllers or configure globally. Open the Index method of HomeController and use AuthorizeIPAddress as shown in the following code.
[ServiceFilter(typeof(AuthorizeIPAddress))] public IActionResult Index() { return View(); }
As localhost IP address allowed to make the request, run your application by pressing F5, you should be able to see the Index view rendered.
Now change the allowed IP address list from StartUp.cs file as shown in following code.
public void ConfigureServices(IServiceCollection services) { //other configuration services.AddScoped<AuthorizeIPAddress>(container => { //test for invalid authorization return new AuthorizeIPAddress("000.0.0.0"); }); }
Now if you run the application you will see Access to localhost was denied error.
Resource Filters are mostly used for Logging, Caching, Throttling, Modifying model binding. For this blog, we will implement for caching purpose to cache content of Action Methods.
Add a new class with the name CacheResourceFilter under the Filters folder. This filter implements IResourceFilter implements.
Use IMemoryCache to store objects to cache and retrieve stored cache content.
Add the following code to CacheResourceFilter and declare variables _memoryCache and _cacheKey. The _cacheKey variable will hold value of the Action Method name.
public class CacheResourceFilter : IResourceFilter { private readonly IMemoryCache _memoryCache; private string _cacheKey; public CacheResourceFilter() { this._memoryCache = new MemoryCache(new MemoryCacheOptions()); } }
Following code implement OnResourceExecuting method of interface IResourceFilter. The action method name is used as Key to store Action Method content. This code tries to get content from Cache if the cache is available for the action method.
OnResourceExecuting executes before Model binding and after authorization filter.
public void OnResourceExecuting(ResourceExecutingContext context) { _cacheKey = context.HttpContext.Request.Path.ToString(); string contentResult = string.Empty; contentResult = _memoryCache.Get(_cacheKey); if(!string.IsNullOrEmpty(contentResult)) { context.Result = new ContentResult() { Content = contentResult }; } }
If the content is returned from cache from OnResourceExecuting method then the remaining pipeline is not triggered.
So OnResourceExecuted also skipped by pipeline.
OnResourceExecuted called after execution of the remainder of the pipeline. This code stores content of action method to cache object if it is already not in cache.
public void OnResourceExecuted(ResourceExecutedContext context) { if (!string.IsNullOrEmpty(_cacheKey)) { var result = context.Result as ContentResult; if (result != null) { var cacheEntryOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromDays(1)); _memoryCache.Set(_cacheKey, result.Content, cacheEntryOptions); } } }
In Starpup.cs file add to following line to inject dependency as Singleton of CacheResourceFilter class.
services.AddSingleton<CacheResourceFilter>();
Add an Action method with the name Message in HomeController with the following code.
[ServiceFilter(typeof(CacheResourceFilter))] public IActionResult Message() { return Content("This content was generated at " + DateTime.Now); }
Testing of Resource Filter: Execute application by pressing F5. Redirect to http://localhost:<port number>/home/message. You will see a message from the action method This content was generated at 5/6/2021 2:39:37 PM. If you refresh the page multiple times you will be seeing the same message as content is returned from cache resource, not from actual action method execution. You can use Different types of ASP.NET Core MVC Content Result.
Action Filters executes just before and after execution of Action Method. The most common use of action filters is to validate ASP.NET Core Model using ValidationAttribute, or tracking user activity.
By using Action Filter we will log how much time it took to execute the action method. Add a class with the name TimeTaken in the Filters folder and implement the IActionFilter interface.
Following code starts a timer when ActionExecuting is executing and the OnActionExecuted timer calculates how much time it took to finish the execution of the action method.
public class TimeTaken : IActionFilter { private Stopwatch timer; public void OnActionExecuting(ActionExecutingContext context) { timer = Stopwatch.StartNew(); } public void OnActionExecuted(ActionExecutedContext context) { timer.Stop(); string result = " Elapsed time: " + $"{timer.Elapsed.TotalMilliseconds} ms"; Debug.WriteLine(result, "Action Filter Log"); } }
Add following code in Startup.cs file to register TimeTaken action filter.
services.AddScoped();
Apply the TimeTaken Action filter to any action method as shown.
[ServiceFilter(typeof(TimeTaken))] public IActionResult Privacy() { return View(); }
View entire code for Action Filter
As we have added a message using Debug.Writeline, open output window from View -> Output menu. When you run your application you will see a message is added in the Output window as shown in the following figure.
Exception Filters does not have any after or before methods. Exception filters execute OnException method of IExceptionFilter, whenever an unhandled exception occur in application. This is the best way to customize your errors.
To implement Exception Filter create a class with the name AppExceptionHandler under the Filters folder. Inherit this class from the IExceptionFilter interface.
We will use model metadata provider by using IModelMetadataProvider, and injecting dependency through constructor.
public class AppExceptionHandler : IExceptionFilter { private readonly IModelMetadataProvider _modelMetadataProvider; public AppExceptionHandler( IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } }
OnException method excepts ExceptionContext with details of an unhandled exception. Following is the implementation of the OnException method that extracts details of exception and set to Model. Model is used to display an error message on Error View from Views -> Shared folder.
public void OnException(ExceptionContext context) { ErrorViewModel errorViewModel = new ErrorViewModel(); errorViewModel.ErrorMessage = context.Exception.Message; errorViewModel.Source = context.Exception.StackTrace; ViewResult errorViewResult = new ViewResult { ViewName = "error", ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState) { Model = errorViewModel } }; context.ExceptionHandled = true; context.Result = errorViewResult; }
ErrorViewModel is already created as part of application creation. Update it with the following properties.
public class ErrorViewModel { public string Source { get; set; } public string ErrorMessage { get; set; } }
Exception filter will render exception details through OnException method on ErrorView. Following code will show the exception details on browser.
@model ErrorViewModel @{ ViewData["Title"] = "Error"; } <h2 class="text-danger"> An error occurred while processing your request.</h2> <p> <b>Message:</b> @Model.ErrorMessage </p> <p> <b>Soruce:</b> @Model.Source </p>
Configure AppExceptionHandler exception filter in Startup.cs file.
services.AddControllersWithViews(config => config.Filters.Add(typeof(AppExceptionHandler)));
Action method to trigger exception.
public IActionResult ThrowSomeException() { throw new SqlNullValueException(); }
View entire code for Exception filte.
This generates output as:
Result filters implement IResultFilter interface. This uses methods OnResultExecuting and OnResultExecuted.
Both OnResultExecuting and OnResultExecuted runs only after the controller action method executed successfully. The difference between OnResultExecuting and OnResultExecuted is, you can change the content of the action result in OnResultExecuting however in OnResultExecuted content result is read-only.
To create Result Filter create a new class file with the name AddResultFilter.cs under the Filters folder. Add the following code to the AddResultFilter.cs file. This code adds an HTTP header with the name AppID to the header.
To create Result Filter create a new class file with name AddResultFilter.cs under Filters folder. Add following code to the AddResultFilter.cs file. This code add a HTTP header with name AppID to header.
public class AddResultFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext filterContext) { filterContext.HttpContext.Response.Headers.Add( "AppID", "Geeks App header was added by result filter."); } public void OnResultExecuted(ResultExecutedContext filterContext) { } }
Configure Result Filter in Startup.cs as
services.AddScoped();
Add Service Type attribute for Index Action method of Home Controller.
[ServiceFilter(typeof(AddResultFilter))] public IActionResult Index() { return View(); }
View entire code for Result Filter.
Run your application and verify the response header by redirecting to home.
Summary: ASP.NET Core MVC filters are very useful when you need to intercept request and response, to add additional feature
or perform validation.