Back to: ASP.NET Core Tutorials For Beginners and Professionals
Action Filters in ASP.NET Core MVC
In this article, I will discuss the Action Filters in ASP.NET Core MVC Applications with Examples. Please read our previous article discussing Authorization Filters in ASP.NET Core MVC Applications. As part of this article, we will discuss the following pointers in detail.
- What are Action Filters in ASP.NET Core MVC?
- How to Create Custom Action Filter in ASP.NET Core MVC?
- How to Inject Services to Custom Action Filter in ASP.NET Core MVC?
- When to Use Action Filter in ASP.NET Core MVC?
What are Action Filters in ASP.NET Core MVC?
Action Filters in ASP.NET Core MVC allow us to execute logic before or after specific stages in the action execution pipeline. They are useful for handling cross-cutting concerns within an application, such as Logging, Authentication, Authorization, Caching, Exception Handling, etc. They help us to encapsulate and separate concerns from the action method logic, making our code more maintainable and reusable.
Types of Action Filters
- Built-in Action Filters: ASP.NET Core provides built-in action filters like [ActionName], [ValidateAntiForgeryToken], etc.
- Custom Filters: You can also create custom action filters by implementing the IActionFilter or IAsyncActionFilter interface, depending on whether you need synchronous or asynchronous execution. Also, you can create a Custom Action Filter by inheriting from the ActionFilterAttribute class.
Use Case:
- Logging and Auditing: Implementing logging of action method calls, parameters, execution times, etc.
- Data Transformation: Modifying the data passed to an action or returned from an action.
- Validation: Performing custom validation of action parameters or the request itself.
- Error Handling: Implementing custom error handling logic for actions.
- Caching: Implementing custom caching strategies for action results.
How to Create a Custom Action Filter in ASP.NET Core MVC?
In ASP.NET Core, you can create a Custom Action Filter in two ways: First, by creating a class implementing the IActionFilter interface and providing implementations for [OnActionExecuting] and [OnActionExecuted] methods. Second, by creating a class inheriting from the ActionFilterAttribute class and overriding the [OnActionExecuting] and [OnActionExecuted] methods.
Step 1: Define Custom Action Filter Class
Define a class that inherits from ActionFilterAttribute (override the OnActionExecuting and OnActionExecuted methods) or implements one of the filter interfaces (IActionFilter or IAsyncActionFilter) and provide implementations for the OnActionExecuting/ OnActionExecutingAsync and OnActionExecuted/OnActionExecutedAsync methods.
Step 2: Implement the Required Methods
Depending on whether you want to perform actions before or after the execution of an action method, override one or both of the following methods:
- OnActionExecuting: This method is called before the action method is executed. If you are implementing the IAsyncActionFilter interface, then you need to implement the OnActionExecutingAsync method.
- OnActionExecuted: This method is called after the action method executes but before the result is processed. If you are implementing the IAsyncActionFilter interface, then you need to implement the OnActionExecutedAsync method.
Step 3: Apply the Custom Filter
Apply the custom filter to controllers or action methods using attributes. You can also register the filter globally.
Example: Custom Action Filter for Logging in ASP.NET Core MVC
Let us create a custom action filter to log the action method execution start and end times to check how much time the action method execution takes. So, create a class file named CustomLoggingActionFilter.cs and copy and paste the following code. In the below code, CustomLoggingActionFilter inherited from the ActionFilterAttribute class and overriding the OnActionExecuting and OnActionExecuted methods.
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersDemo.Models { public class CustomLoggingActionFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { // Log the information before the action executes. Log("OnActionExecuting", context.RouteData); } public override void OnActionExecuted(ActionExecutedContext context) { // Log the information after the action executes. Log("OnActionExecuted", context.RouteData); } private void Log(string methodName, RouteData routeData) { var controllerName = routeData.Values["controller"]; var actionName = routeData.Values["action"]; string message = methodName + " Controller:" + controllerName + " Action:" + actionName + " Date: " + DateTime.Now.ToString() + Environment.NewLine; string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt"); //saving the data in a text file called Log.txt File.AppendAllText(filePath, message); } } }
As the above Custom Action Filter class is inherited from the ActionFilterAttribute class, we can directly use this class as an Attribute within the action methods or controllers.
Applying the Filter to an Action Method:
Let us modify the Home Controller as follows to apply the Custom Action Filter on the Index Action method:
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { [CustomLoggingActionFilter] public ActionResult Index() { return View(); } } }
With the above changes in place, run the application, navigate to the Index action method, and verify the Log.txt file, which should be generated within the Log folder.
Registering the Filter Globally
You can also register the filter globally to apply to all actions in the application. This can be done in the Main method of the Program.cs class file as follows:
builder.Services.AddControllersWithViews(options => { options.Filters.Add(new CustomLoggingActionFilter()); });
The above example demonstrates a simple logging action filter that writes log messages before and after executing action methods. You can customize and expand this according to your application’s specific needs and logic, such as adding error handling, authorization, or other cross-cutting concerns.
Creating Custom Action Filter By Implementing IActionFilter Interface:
In the previous example, the Custom Action Filter Class inherits from the ActionFilterAttribute class. Hence, we have applied the Custom Action Filter as an Attribute to the controller and action method. Now, let us rewrite the same example, and this time, let us implement the IActionFilter interface. So, modify the CustomLoggingActionFilter class as follows:
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersDemo.Models { public class CustomLoggingActionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // Log the information before the action executes. Log("OnActionExecuting", context.RouteData); } public void OnActionExecuted(ActionExecutedContext context) { // Log the information after the action executes. Log("OnActionExecuted", context.RouteData); } private void Log(string methodName, RouteData routeData) { var controllerName = routeData.Values["controller"]; var actionName = routeData.Values["action"]; string message = methodName + " Controller:" + controllerName + " Action:" + actionName + " Date: " + DateTime.Now.ToString() + Environment.NewLine; string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt"); //saving the data in a text file called Log.txt File.AppendAllText(filePath, message); } } }
Register the Filter Globally, on a Controller, or an Action:
You can register the custom action filter globally, which means it will be applied to all controllers and actions in your application. In your Program.cs, you can add the filter as follows.
builder.Services.AddControllersWithViews(options => { options.Filters.Add(new CustomLoggingActionFilter()); });
Or, you can apply the filter to a specific controller or action method by using it as an attribute. In this case, we must use the Built-in TypeFilter or ServiceFilter Attributes.
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { [TypeFilter(typeof(CustomLoggingActionFilter))] public class SomeController : Controller { // All actions in this controller will use the CustomLoggingActionFilter } public class AnotherController : Controller { [ServiceFilter(typeof(CustomLoggingActionFilter))] public ActionResult SomeAction() { // This action will use the CustomLoggingActionFilter return View(); } } }
Example: Custom Authorization Action Filter
The following example demonstrates a custom authorization filter restricting access to an action method based on a custom condition. So, create a class file with the name CustomAuthorizationFilter.cs and then copy and paste the following code:
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Models { public class CustomAuthorizationFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { bool isAuthorized = CheckCustomAuthorizationCondition(context); if (!isAuthorized) { context.Result = new UnauthorizedResult(); } } private bool CheckCustomAuthorizationCondition(ActionExecutingContext context) { // Implement your custom authorization logic here. // Return true if access is allowed; otherwise, return false. // You can access the HttpContext, User, and other context information as needed. // For example, check if the user has a specific role. //if (!context.HttpContext.User.IsInRole("Admin")) //{ // return false; //} return true; } } }
Use the Custom Authorization Attribute:
Now, you can use the CustomAuthorizationAttribute to decorate your controller actions or controller classes to apply the custom authorization logic.
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { // Apply the custom authorization filter to this action [CustomAuthorizationFilter] public ActionResult Index() { return View(); } } }
Using this approach, the CustomAuthorizationFilter will be executed before the Index action method execution, and you can define your custom authorization logic inside the OnActionExecuting method of the filter. If the authorization logic fails, you can set the context.Result to a suitable result (e.g., ForbidResult, UnauthorizedResult, or custom result) to deny access. Otherwise, the action will be executed if the user passes the authorization check.
Example: Result Modification Custom Action Filter
The following example shows how to modify the result of an action method using a filter. So, create a class file with the name CustomResultFilter.cs and then copy and paste the following code:
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Models { public class CustomResultFilter : ActionFilterAttribute { public override void OnResultExecuting(ResultExecutingContext context) { var originalResult = context.Result as ContentResult; if (originalResult != null) { // Modifying the result by appending additional content. originalResult.Content += "\n<!-- Custom content added by the filter. -->"; } } } }
Applying the filter to an action method:
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { // Apply the custom result filter to this action [CustomResultFilter] public ActionResult Index() { return Content("Original Content."); } } }
How to Inject Services to Custom Action Filter in ASP.NET Core MVC?
In ASP.NET Core MVC, we can inject services into action filters using dependency injection. There are mainly two ways to inject services into action filters:
- Constructor Injection: It’s used when registering the action filter in the DI container. The services will be injected through the constructor of the action filter.
- Service Locator Pattern: You can access the services from the HttpContext inside the action method execution.
Example: Using Constructor Injection
To use constructor injection, you must register your filter as a service. First, we need to define our service interface and implementation. Let us implement the logging example. So, first, define the Logger Service Interface and its implementation as follows:
namespace FiltersDemo.Models { public interface IActionExecutionLoggerService { public void Log(string methodName, RouteData routeData); } public class ActionExecutionLoggerService : IActionExecutionLoggerService { public void Log(string methodName, RouteData routeData) { var controllerName = routeData.Values["controller"]; var actionName = routeData.Values["action"]; string message = methodName + " Controller:" + controllerName + " Action:" + actionName + " Date: " + DateTime.Now.ToString() + Environment.NewLine; string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt"); //saving the data in a text file called Log.txt File.AppendAllText(filePath, message); } } }
Inject the service into the action filter using the constructor. So, modify the CustomLoggingActionFilter.cs class file as follows:
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersDemo.Models { public class CustomLoggingActionFilter : ActionFilterAttribute { private readonly IActionExecutionLoggerService _LoggerService; public CustomLoggingActionFilter(IActionExecutionLoggerService LoggerService) { _LoggerService = LoggerService; } public override void OnActionExecuting(ActionExecutingContext context) { // Log the information before the action executes. _LoggerService.Log("OnActionExecuting", context.RouteData); } public override void OnActionExecuted(ActionExecutedContext context) { // Log the information after the action executes. _LoggerService.Log("OnActionExecuted", context.RouteData); } } }
Register the service and filter in the Main method of the Program.cs class file:
builder.Services.AddScoped<IActionExecutionLoggerService, ActionExecutionLoggerService>(); builder.Services.AddScoped<CustomLoggingActionFilter>();
Apply the filter as a service filter to your actions or controllers:
To apply the filter as a service, we need to use ServiceFilter Attribute as follows:
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { // Apply the filter as a service filter to your actions or controllers: [ServiceFilter(typeof(CustomLoggingActionFilter))] public ActionResult Index() { return View(); } } }
Example: Using Service Locator Pattern
Here’s how to access services using the service locator pattern: Define your service interface and implementation. We are going with the same service and implementations. Then, we need to register the service with the Program class.
builder.Services.AddScoped<IActionExecutionLoggerService, ActionExecutionLoggerService>(); builder.Services.AddScoped<CustomLoggingActionFilter>();
Access the service within the action filter method:
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersDemo.Models { public class CustomLoggingActionFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { var _LoggerService = (IActionExecutionLoggerService)context.HttpContext.RequestServices.GetService(typeof(IActionExecutionLoggerService)); // Log the information before the action executes. _LoggerService.Log("OnActionExecuting", context.RouteData); } public override void OnActionExecuted(ActionExecutedContext context) { // Log the information after the action executes. var _LoggerService = (IActionExecutionLoggerService)context.HttpContext.RequestServices.GetService(typeof(IActionExecutionLoggerService)); _LoggerService.Log("OnActionExecuted", context.RouteData); } } }
Apply the filter to your actions or controllers:
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { // Apply the filter to your actions or controllers: [CustomLoggingActionFilter] public ActionResult Index() { return View(); } } }
Both methods allow injecting and utilizing services within action filters in ASP.NET Core MVC. Constructor injection is generally preferred due to better adherence to the dependency inversion principle and improved testability.
When to Use Action Filter in ASP.NET Core MVC?
Here are some common scenarios where using Action Filters might be appropriate:
- Logging and Auditing: When you need to log details about requests and responses, such as request times, parameters, user information, and the action’s outcome. This is especially useful for debugging and monitoring.
- Validation: Using an action filter can avoid repetitive code if you have custom validation logic that applies to multiple actions or controllers. This is useful when the validation isn’t just data model validation but involves more complex business rules.
- Error Handling: To implement centralized error handling across multiple actions or controllers, especially when the error handling logic is more than just simple exception catching.
- Performance Measurement: When you want to measure the performance of actions, such as how long an action takes to execute, log this information or perform another action based on it.
- Data Transformation: For modifying the data returned by the action methods before it gets sent to the view or the client. This could involve transforming or formatting data based on specific rules or requirements.
- Caching: Implementing custom caching strategies (especially more complex ones than what’s provided by default) for action results to improve performance.
- Authorization and Security: Although ASP.NET Core provides built-in attributes for basic authorization, you might use an action filter for more complex security checks beyond simple role or policy checks.
- Response Alteration: Modifying the HTTP response in certain ways, like adding custom headers, changing status codes, or modifying the response body in specific scenarios.
In the next article, I will discuss the Difference Between TypeFilter and ServiceFilter in ASP.NET Core MVC Applications. Here, in this article, I try to explain the Action Filters in ASP.NET Core MVC Application with Examples. I hope you understand the need and use of Action Filters in the ASP.NET Core MVC application.
About the Author: Pranaya Rout
Pranaya Rout has published more than 3,000 articles in his 11-year career. Pranaya Rout has very good experience with Microsoft Technologies, Including C#, VB, ASP.NET MVC, ASP.NET Web API, EF, EF Core, ADO.NET, LINQ, SQL Server, MYSQL, Oracle, ASP.NET Core, Cloud Computing, Microservices, Design Patterns and still learning new technologies.