Simple Action Filters With MVC and C#

by Kenji Elzerman
Simple Action Filters With C# And .NET - Kens Learning Curve

If you work with the MVC framework, like an API, you might have heard or even used filters. We know 4 different filters; authorization, action, result, and exception filters. In this article, I want to talk about action filters with MVC and C#. This particular filter helps you understand how a method of a controller is executed. You can use the built-in action filters or create one of your own.

Goals

Although there are four types of filters, this article will only talk about action filters. After this article you

  • Know what filters and action filters are
  • Understand the order of the filters
  • Know how to use built-in action filters
  • Can create your own action filter
  • Know how to use the session in an action filter
  • Alter and return the result from an action through an action filter

Action Filters In Short

We use filters way more than you would think. For example, the Authorize attribute is a filter of the type Authorization Filters. And there are three more filter types: Action filters, Result filters, and Exception filters. All four filters have their interface, which distinguishes them from the different types.

It’s important to know that filters are executed in a specific order: Authorization, Action, Result, and Exception filters. This way you can determine at one point something has to happen.

A filter is an attribute with something extra. Where an attribute is created with the derived class Attribute, a filter has a different inheritance. An action filter needs to have an inheritance with ActionFilterAttribute. You can apply a filter on a method or an entire controller.

Included Action Filters

There are a few included action filters in the ASP.NET MVC framework:

  • OutputCache
  • HandleError
  • Authorize

You can find a lot of information about these filters if you use Google, but here is a small demonstration of the OutputCache:

[OutputCache(Duration = 200)]
public IActionResult Get()
{
    return MovieService.Get();
}

As you can see this is a method with an attribute. The OutputCache caches the output of the method (action) and holds that for 200 seconds. After 200 seconds the cache of this method is refreshed.

Usage In The Minimal API

With the arrival of the minimal API, some things have changed. A mapping doesn’t allow attributes as we know them. Using an action filter needs to be placed a little bit differently. In the case of the OutputCache, you have two options:

  1. Put the attribute in the delegate
  2. Use the CachOutput method on the mapping
app.MapGet("/api/movies/", [OutputCache(Duration = 200)] (IMovieService movieService) =>
{
    return Results.Ok(movieService.Get());
});

// OR 

app.MapGet("/api/movies/", (IMovieService movieService) =>
{
    return Results.Ok(movieService.Get());
}).CacheOutput();

Create Your Own Action Filters

An action filter intends to modify the way an action is executed. You can modify the output if you want. And that is what I want to achieve in this demo: I want the date and time to be sent back to the client each time an action is executed.

I will be using a non-minimal API because I find it easier to show you how it works.

It All Starts With A Class

Like most things with C#, we need a class. An action filter, or any filter actually, only works in the MVC API. It makes no sense to place these classes in a business library. I created a new class, with the name InfoActionFilter, to the folder Filters of my API. After that, I inherit from the class ActionFilterAttribute. The code looks like this:

using Microsoft.AspNetCore.Mvc.Filters;

namespace MinimalAPI.Filters;

public class InfoActionFilter: ActionFilterAttribute
{
}

If you look into the code of the ActionFilterAttribute, you will find all kinds of other inheritances that look familiar (IActionFilter, Attribute, etc.).

The Four Overrides

To make use of the new filter we need to override some methods. These methods do have a base and are mostly empty. Some have an async version.

The four overrides of the action filter are:

  • OnActionExecuting
  • OnActionExecuted
  • OnResultExecuting
  • OnResultExecuted

This is also the order they are called. So first, the OnActionExecuting will be called. This one happens before the action is executed. Here you can, for example, log when and how an action is activated.

The OnActionExecuted happens after the action is executed. Here you can log, for example, how long it took to execute the action or handle any errors.

The OnResultExecuting happens right before a result is sent back. The OnResultExecuted is executed as soon as the result is ready to send back to the client.

Since I want to alter the output of the action, I will be using the OnActionExecuted. And for the fun of it, I will be using OnActionExecuting too.

OnActionExecuting

I want to add some logging (not really, but for demo purposes it’s nice). So what I want to do is create a trade ID, which will be a GUID. I want to get this GUID everywhere, so I will add it to a session item. That means I must create this GUID before the action is executed. Hence the use of the OnActionExecuting. This method will be executed before the action is executed.

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    string traceId = Guid.NewGuid().ToString();
    filterContext.HttpContext.Session.SetString("TraceId", traceId);
}

Here I create a GUID and place it in the session of the current HttpContext. This way I can retrieve the value of the TraceId in the action, passing it along to other methods and classes.

Place the filter on the action or the controller. I just use the scaffolded code, the well-known WeatherForecastController.

[ApiController]
[Route("[controller]")]
[InfoActionFilter]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

...

Let’s start the API and see what happens

Session has not been configured for this application or request

If you get this error there is no need to panic. It means you haven’t configured the use of the session yet. This can be done in the Program.cs. There is no need to install a package, although some websites might recommend that.

All you need to do is add a distributed memory cache, add the session to the services, and make sure the app uses the session.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromSeconds(10);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseSession();

app.UseAuthorization();

app.MapControllers();

app.Run();

OnActionExecuted

Let’s imagine the controller and action are doing a lot of work and we keep a log of what they are doing per request. We use the trace ID to keep track of the request.

Then the action is done and the result, of type IActionResult, is returned. This is a great moment to use the OnActionExecuted, the filter method that is executed as soon as the action is done doing its job.

I want to change the data in the result. To demonstrate how you can do this, I want to return the date and time when the action is executed, the result from the action, and the trace ID. This is pretty easy to do:

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    filterContext.Result = new OkObjectResult(new
    {
        TraceId =  filterContext.HttpContext.Session.GetString("TraceId"),
        Result = ((ObjectResult)filterContext.Result).Value,
        Send = DateTime.Now
    });
}

First I override the OnActionExecuted. Then inside this method, I fill the filterContext.Result with a new OkObjectResult, the default object of the result. The OkObjectResult is part of the ObjectResult, which also holds other result types.

Inside the OkObjectResult I create a new anonymous object. Creating an object would be better, but I like to keep it short… Codewise. The properties TraceId, Result, and Send are created and filled.

Note that the Result, of the type object?, will fail if there is no value. There are some assumptions here. You should check the value of the filterContext.Result is not NULL.

The TraceId is filled with the value of the session item TraceId, which is set in the OnActionExecuting.

Quick Question

Why not declare the trace ID in the class as a property and reuse that one? Well, that doesn’t work. Each cycle of the filter starts clean, meaning it is initialized each time an overridden method is used. Setting private property has no point since you will lose the content.

This is something to keep in mind when you are working with ActionFilters. Never store (temporary) data inside them.

Conclusion On Simple Action Filters With MVC and C#

It’s pretty easy to create an action filter of your own. The hard part is to understand how to control the data. It took me one morning to figure out how to change the Result of the filterContext. Just know that this is read-only in the OnResultExecuting and OnResultExecuted.

Action filters with MVC and C# can be set as an attribute on a controller or action. But you can declare them globally, making them execute all the time. Use them wisely, because this could go wrong at some point.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

Table Of Contents
Kens Learning Curve