Back to: ASP.NET Core Tutorials For Beginners and Professionals
Custom Result Filter in ASP.NET Core MVC
In this article, I will discuss How to Create a Custom Result Filter in an ASP.NET Core MVC Application with Examples. Please read our previous article discussing Result Filter in ASP.NET Core MVC Application.
Custom Result Filter in ASP.NET Core MVC
In ASP.NET Core MVC, Result Filters are a specific type of filter that runs after the action method has been executed but before the result is processed and sent to the client, i.e., before or after executing action results. Action results are what an action method returns to generate a response. This could be a view, a file, a redirect, or a JSON result, among other things.
Custom Result Filters allow us to execute custom logic before the action result is executed (before the view is rendered or the content is returned to the client) or after the execution is completed (but still before the response is sent to the client). They can be useful for:
- Modifying or Replacing the Result.
- Adding HTTP Headers to the Response.
- Logging or Auditing Response Data.
- Handling Errors or Custom Processing of the Result Data.
- Caching Responses.
How to Create Custom Result Filter in ASP.NET Core MVC?
In ASP.NET Core, you can create a Custom Result Filter in two ways: First, by creating a class implementing the IResultFilter interface and providing implementations for OnResultExecuting and OnResultExecuted methods. Second, by creating a class inheriting from the ResultFilterAttribute class and overriding the OnResultExecuting and OnResultExecuted methods. The choice between implementing the interface or deriving from the class depends on whether you want to create a filter using attributes or a standalone filter class.
Creating and Using a Custom Result Filter in ASP.NET Core MVC involves the following steps:
- Define the Filter: Create a class that implements the IResultFilter/IAsyncResultFilter interface or inherits from the ResultFilterAttribute class. For synchronous operations, use IResultFilter, and for asynchronous, use IAsyncResultFilter.
- Implement Required Methods: For IResultFilter, you need to implement OnResultExecuting and OnResultExecuted. For IAsyncResultFilter, you need to implement OnResultExecutionAsync. If you are inheriting the ResultFilterAttribute class, you must override the OnResultExecuting and OnResultExecuted methods.
- Apply the Filter: Once your Custom Result Filter is created, you can apply it to actions or controllers using attributes or add it globally in the Main method of the Program class method.
Approach 1: Inheriting from ResultFilterAttribute Class
Create a Custom Result Filter by inheriting from the ResultFilterAttribute class and overriding its methods: OnResultExecuting and OnResultExecuted. So, create a class file named CustomResultFilterAttribute.cs and copy and paste the following code. In the below example, we are simply logging when the OnResultExecuting and OnResultExecuted methods are executed.
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersDemo.Models { public class CustomResultFilterAttribute : ResultFilterAttribute { public override void OnResultExecuting(ResultExecutingContext context) { // This method is called before the result is executed. LogMessage("Executing custom result filter attribute before result execution.\n"); } public override void OnResultExecuted(ResultExecutedContext context) { // This method is called after the result has been executed. LogMessage("Executing custom result filter attribute after result execution.\n"); } private void LogMessage(string message) { string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt"); //saving the data in a text file called Log.txt File.AppendAllText(filePath, message); } } }
Applying the Custom Result Filter to an Action Method:
Let us modify the Home Controller as follows to apply the Custom Result Filter on the Index Action method:
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { [CustomResultFilterAttribute] public IActionResult Index() { return View(); } } }
With the above code 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
We 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 CustomResultFilterAttribute()); });
Approach 2: Implementing IResultFilter (Synchronous Result Filter)
Let’s say we want to create a result filter that logs the type of ActionResult returned by our action methods. So, create a Custom Result Filter by implementing the IResultFilter interface. You need to implement two methods: OnResultExecuting and OnResultExecuted. These methods are called before and after the result is executed. So, modify the CustomResultFilter.cs as follows.
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersDemo.Models { public class CustomResultFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { // This method is called before the result is executed. // Log information before the action result starts executing LogMessage($"Result type is about to be executed: {context.Result.GetType().Name}\n"); LogMessage("Executing custom result filter attribute before result execution.\n"); } public void OnResultExecuted(ResultExecutedContext context) { // This method is called after the result has been executed. // Log information after the action result has finished executing LogMessage($"Result type has been executed: {context.Result.GetType().Name}\n"); LogMessage("Executing custom result filter attribute after result execution.\n"); } private void LogMessage(string message) { 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 CustomResultFilter as a service in your application’s Program.cs file. You can specify the filter’s scope (e.g., Transient, Scoped, Singleton) based on your requirements.
builder.Services.AddScoped<CustomResultFilter>();
Apply the CustomResultFilter to your controller action methods using the [ServiceFilter] attribute or add it globally in the Program.cs file. For example, apply on to the Action method as follows:
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { [ServiceFilter(typeof(CustomResultFilter))] public IActionResult Index() { return View(); } } }
Adding Globally:
Adding the following code in the Program.cs class file:
//Adding Custom Result Filter Globally builder.Services.AddControllersWithViews(options => { options.Filters.Add(new CustomResultFilterAttribute()); }); builder.Services.AddScoped<CustomResultFilter>();
Approach 3: Implementing IAsyncResultFilter (Asynchronous Result Filter)
Assume you want to compress the response’s content asynchronously in certain conditions. You would implement an asynchronous result filter to compress the response stream.
using Microsoft.AspNetCore.Mvc.Filters; using System.IO.Compression; namespace FiltersDemo.Models { public class CompressResultFilter : Attribute, IAsyncResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { // Check if the response should be compressed based on some logic var shouldCompress = ShouldCompressResponse(context); if (shouldCompress) { var originalBodyStream = context.HttpContext.Response.Body; using (var compressedStream = new MemoryStream()) { using (var compressionStream = new GZipStream(compressedStream, CompressionMode.Compress, leaveOpen: true)) { context.HttpContext.Response.Body = compressionStream; // Execute the result (action filters, action, result filters, result) await next(); // Flush the remaining data await compressionStream.FlushAsync(); } // Copy the compressed data to the original stream compressedStream.Seek(0, SeekOrigin.Begin); await compressedStream.CopyToAsync(originalBodyStream); } context.HttpContext.Response.Body = originalBodyStream; context.HttpContext.Response.Headers.Add("Content-Encoding", "gzip"); } else { // If we don't want to compress, just call the next delegate/middleware in the pipeline await next(); } } private bool ShouldCompressResponse(ResultExecutingContext context) { // Your logic to determine if the response should be compressed return true; // For this example, we're just compressing every response } } }
In this asynchronous result filter example, the filter checks if the response should be compressed, and if so, it sets up a new response body stream that compresses the content. After executing the action result (with await next();), it copies the compressed content back to the original response stream and sets the appropriate HTTP header.
Then, apply this filter:
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { [CompressResultFilter] public IActionResult Index() { return View(); } } }
Custom Result Filter Real-Time Example in ASP.NET Core MVC:
In ASP.NET Core MVC, you can create a Custom Result Filter to modify the content of an action result before it is sent to the client. Let’s go through an example where we create a custom result filter that adds a custom header to the response. Here’s how you can create a custom result filter that modifies the content of an action result:
Create a Custom Result Filter Attribute:
In this example, we have created a Custom Result Filter attribute called AddCustomHeaderResultFilterAttribute. This filter adds a custom header to the response and modifies the content if it’s a string.
namespace FiltersDemo.Models { using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; public class AddCustomHeaderResultFilterAttribute : ResultFilterAttribute { private readonly string _headerName; private readonly string _headerValue; public AddCustomHeaderResultFilterAttribute(string headerName, string headerValue) { _headerName = headerName; _headerValue = headerValue; } public override void OnResultExecuting(ResultExecutingContext context) { if (context.Result is ObjectResult objectResult) { if (objectResult.Value is string content) { context.HttpContext.Response.Headers.Add(_headerName, _headerValue); content += $" (Custom Header: {_headerValue})"; objectResult.Value = content; } } base.OnResultExecuting(context); } } }
Apply the Custom Result Filter Attribute to a Controller Action:
In this example, the AddCustomHeaderResultFilterAttribute is applied to the Index action of the HomeController.
using FiltersDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FiltersDemo.Controllers { public class HomeController : Controller { [AddCustomHeaderResultFilter("X-Custom-Header", "MyCustomValue")] public string Index() { return "String Data from Index Action Method"; } } }
When you run your ASP.NET Core MVC application and access the Index action, it will add a custom header (“X-Custom-Header”) with the value “MyCustomValue” to the response. It modifies the response’s content by appending the custom header information to the string.
Please note that this is a basic example, and you can create more complex Custom Result Filters to modify the content in different ways, such as manipulating JSON data, XML data, or other response types based on your specific requirements.
When to use Custom Result Filter in ASP.NET Core MVC?
Custom Result Filters in ASP.NET Core MVC are useful for modifying or inspecting the result of an action method before it’s sent to the client. There are specific scenarios where implementing a custom result filter is particularly useful:
- Modifying Results: If you want to modify the result of an action method before it’s sent to the client. For example, you might want to globally modify the view model for certain types of views or wrap certain action results in a standard API response format.
- Adding Response Headers: To add or modify HTTP headers in the response. A Custom Result Filter can be used to add headers that should be included in every response, like security headers (e.g., Content-Security-Policy) or custom headers for CORS (Cross-Origin Resource Sharing).
- Logging and Auditing: If you want to log details about the action results, such as execution time, the size of the returned data, or other metadata for auditing purposes.
- Security: If security-related tasks need to be performed before sending the response to the client, like redacting sensitive information from the result or implementing additional security checks based on the result type.
- Localization: When applying localization to the result data or modifying the result based on the user’s culture settings.
- Content Negotiation: In scenarios where you need to support multiple content types and want to handle the negotiation logic manually.
In the next article, I will discuss Response Caching in ASP.NET Core Applications. Here, in this article, I try to explain Custom Result Filter in ASP.NET Core MVC Application with Examples. I hope you enjoy this Custom Result Filter in ASP.NET Core MVC article.
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.