Result Filters in ASP.NET Core MVC

Result Filters in ASP.NET Core MVC

In this article, I will discuss Result Filters in ASP.NET Core MVC Applications with Examples. Please read our previous article discussing Error Pages Based on Status Code in ASP.NET Core MVC Application.

What are Result Filters 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. This means that with Result Filters, we can modify the view or the result data before it is returned to the client.

How Do We Create a Result Filter in ASP.NET Core MVC?

In ASP.NET Core MVC, we 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, a class can be created by inheriting the ResultFilterAttribute class and overriding the [OnResultExecuting] and [OnResultExecuted] methods. 

  • Before Result Execution (OnResultExecuting): This method is executed just before the action result is executed. You can use this method to modify the action result or insert additional processing before the result is handled.
  • After Result Execution (OnResultExecuted): This method is called after the action result execution is completed. It allows us to modify the response, log information, handle exceptions, or perform other operations after the result has been processed.

Note: If you need to perform asynchronous operations within the Custom Result Filter, you should implement the IAsyncResultFilter interface or AsyncResultFilterAttribute and need to implement or override the OnResultExecutionAsync method. First, I will show you the example using ResultFilterAttribute, and then I will show you another example using the IResultFilter interface.

When Should We Use Result Filters in ASP.NET Core MVC?
  • Modifying Results: Result filters can modify or replace the result being executed. For example, you could change the view or data returned by an action based on certain conditions.
  • Logging: They provide a convenient place to log the use of particular actions or results, such as response size or execution time.
  • Custom Headers: Custom headers are added to the HTTP response based on certain conditions evaluated before or after the action result.
Example to Understand Result Filter in ASP.NET Core MVC:

Let us look at one example of understanding result filters in ASP.NET Core MVC. Imagine you are developing a web application that includes a feature to log the execution time of certain pages and modify the HTTP response based on the user role. For example, we are going to dynamically modify the view based on a query string (admin=true changes the result to “AdminView”).

Define Result Filter:

First, create a custom result filter that measures execution time and appends a custom header if the execution time exceeds a predefined threshold. So, create a class file named CustomResultFilterAttribute.cs within the Models folder and copy and paste the following code.

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

namespace FiltersDemo.Models
{
    public class CustomResultFilterAttribute :  ResultFilterAttribute
    {
        private Stopwatch _timer;

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            // Initialize and start the timer
            _timer = Stopwatch.StartNew();

            // Add a custom header before executing the result
            context.HttpContext.Response.Headers.Append("X-Pre-Execute", "Header set before execution");

            // Example: Modify the result based on authorization (dummy condition here)
            if (context.HttpContext.Request.Query.ContainsKey("admin") && context.Result is ViewResult viewResult)
            {
                context.Result = new ViewResult
                {
                    ViewName = "AdminView",
                    ViewData = viewResult.ViewData,
                    TempData = viewResult.TempData
                };
            }

            base.OnResultExecuting(context);
        }

        public override void OnResultExecuted(ResultExecutedContext context)
        {
            _timer.Stop();
            var actionName = context.ActionDescriptor.DisplayName;
            var executionTime = _timer.ElapsedMilliseconds;
            var resultType = context.Result.GetType().Name;

            // Log details about the action execution
            Debug.WriteLine($"Action '{actionName}' executed in {executionTime} ms, resulting in {resultType}");

            base.OnResultExecuted(context);
        }
    }
}
OnResultExecuting Method

The OnResultExecuting method is called just before the action result is executed, i.e., before the framework writes the response. Here’s what happens in this method:

  • Timer Initialization: A Stopwatch instance is created and started to measure the duration of the result’s execution. This is useful for performance monitoring.
  • Setting a Custom Header: The HTTP response includes a custom header X-Pre-Execute. This header is set before the result is actually executed, allowing you to send additional information in the HTTP headers.
  • Modifying the Result Conditionally: The method checks if the incoming request contains a specific query parameter (admin). If this condition is met, the method changes the view being returned. It sets the view to AdminView instead of the original view, effectively altering the output based on the request parameters.
  • Calling Base Method: Finally, it calls base.OnResultExecuting(context) to ensure that any logic in the base class is also executed.
OnResultExecuted Method

The OnResultExecuted method is called right after the action result is executed, i.e., after the result has been processed and the response has likely been sent to the client. Here’s what happens in this method:

  • Stop Timer: It stops the Stopwatch instance that was started in OnResultExecuting to get the total execution time.
  • Logging: It logs details about the action, such as the action name, execution time, and the type of result. This is crucial for debugging and monitoring the behavior of your web application.
  • Calling Base Method: It calls base.OnResultExecuted(context) to ensure that any logic in the base class is also executed.
Modifying Home Controller:

You can apply this filter to a specific action, a controller, or globally across all controllers. Here, we apply it to the Home Controller only. Next, modify the Home Controller as follows:

using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
{
    [CustomResultFilter]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            // The view name can be dynamically changed based on the filter
            return View();
        }
    }
}
Index.cshtml

Next, modify the Index.cshtml view as follows:

@{
    ViewData["Title"] = "Home Page";
}

<h1>Normal User View</h1>
AdminView.cshtml

Next, add the AdminView.cshtml view within the Views/Home folder and then copy and paste the following code:

@{
    ViewData["Title"] = "AdminView";
}

<h1>Admin Panel</h1>
Running and Testing the Application

Access /Home/Index normally to see the “Index View”. I

Result Filters in ASP.NET Core MVC Applications with Examples

Access /Home/Index?admin=true to trigger the admin view condition.

Result Filters in ASP.NET Core MVC Applications with Examples

In both requests, if you check the response headers, then you will see the custom header, which we set using the Result Filter as shown in the below image:

Result Filters in ASP.NET Core MVC Applications with Examples

Custom Result Filter using IAsyncResultFilter Interface:

Now, let us rewrite the previous example using IAsyncResultFilter. We need to implement the IAsyncResultFilter interface and needs to provide the necessary asynchronous handling for the result execution and post-execution events. Create a class file named CustomResultFilter.cs and then copy and paste the following code:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

namespace FiltersDemo.Models
{
    public class CustomResultFilter : IAsyncResultFilter
    {
        private Stopwatch _timer;

        public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            // Initialize and start the timer
            _timer = Stopwatch.StartNew();

            // Add a custom header before executing the result
            context.HttpContext.Response.Headers.Append("X-Pre-Execute", "Header set before execution");

            // Example: Modify the result based on authorization (dummy condition here)
            if (context.HttpContext.Request.Query.ContainsKey("admin") && context.Result is ViewResult viewResult)
            {
                context.Result = new ViewResult
                {
                    ViewName = "AdminView",
                    ViewData = viewResult.ViewData,
                    TempData = viewResult.TempData
                };
            }

            // Execute the result as planned
            var executedContext = await next();

            // Stop the timer after the result is executed
            _timer.Stop();
            var actionName = context.ActionDescriptor.DisplayName;
            var executionTime = _timer.ElapsedMilliseconds;
            var resultType = executedContext.Result.GetType().Name;

            // Log details about the action execution
            Debug.WriteLine($"Action '{actionName}' executed in {executionTime} ms, resulting in {resultType}");
        }
    }
}
Code Explanation:
  • Initialization of Stopwatch: A Stopwatch is instantiated and started to measure the time taken to execute the action result. This is useful for performance monitoring.
  • Setting Custom Header: Before the action result is executed, a custom header (X-Pre-Execute) is added to the HTTP response. This could be used for various purposes, such as providing metadata about the response or for debugging purposes.
  • Conditional Result Modification: The method checks if the HTTP request contains a specific query parameter (admin). If this condition is met, and the current result is a ViewResult (which typically renders a view), it modifies the result to change the view that will be rendered.
  • Execution of Result: The await next() call is very important. This line hands over control to the next filter in the pipeline, or if there are no further filters, it executes the action result. This is an asynchronous operation and the method awaits its completion before proceeding. After await next() completes, it returns a ResultExecutedContext, which contains details about the executed action result.
  • Stopwatch Stopping and Logging: Once the action result has been executed and the control returns to the filter, the stopwatch is stopped. The elapsed time (how long the result took to execute), the name of the executed action, and the type of result that was executed are logged using Debug.WriteLine().
Modifying the Home Controller:

Next, modify the Home Controller as follows. As the CustomResultFilter is not an attribute, we cannot directly apply it to the Controller or Action method level. Here, we are using TypeFilter to apply the Custom Result filter at the controller level.

using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
{
    [TypeFilter(typeof(CustomResultFilter))]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            // The view name can be dynamically changed based on the filter
            return View();
        }
    }
}

With these changes, run the application, and it should work as expected.

In the next article, I will discuss Custom Result Filter in ASP.NET Core MVC Application. In this article, I explain Result Filters in ASP.NET Core Applications with Examples. I hope you enjoy this Result Filters in ASP.NET Core article.

Leave a Reply

Your email address will not be published. Required fields are marked *