Filters in ASP.NET

Filters in ASP.NET allow you to intercept an incoming HTTP request, do some sort of processing work and hand the (modified) request back to the pipeline to do other work, until the final output is generated and sent back to the caller. There are some built-in filters that are provided as part of the framework that you may have used, not knowing that they are filters… Authorize, HandleError and ResponseCache are examples of these. You can also build your own. Anytime you have a cross-cutting need in your application – a requirement that applies to the application as a whole – such as logging, global error handling, etc., it’s worth checking to see if creating an ASP.NET filter would be an appropriate solution.

You may have also come across another term: middleware. Both filters and middleware work in a similar fashion in that both are taking the incoming HTTP request, operating on that request in some way and then letting other processes in the pipeline takeover until that request is handled as a whole. However, filters are an ASP.NET MVC construct that comes into effect within that context. Middleware on the other hand, operates at a higher level and can handle requests even before it hits a particular route (or action method).

Types of Filters in ASP.NET

  • Authorization Filter: Allows you to write custom logic to determine whether a particular caller should be given access to a particular endpoint.
  • Resource Filter: Runs after Authorization filter and then encloses the rest of the pipeline. Allows you to short-circuit or bypass the rest of the pipeline.
  • Action Filter: Runs right before and/or right after an action method executes. It can be used to alter the input parameters or the output of the action method.
  • Result Filter: Executes after an action method completes executing but before an ActionResult executes.
  • Exception Filter: Executes when an exception occurs in an action method.

Create Your Own ASP.NET Filter

Let’s create a simple action filter. You can use the same concept to create any of the other types of filters by simply replacing the events targeted for other ones. To follow along, create a brand new ASP.NET web application. If you are familiar with the dotnet CLI, you can create a new one by using the command dotnet new webapi. Then, create a new class that inherits from the IActionFilter interface. This interface requires that you wire up two methods: OnActionExecuting and OnActionExecuted. The former event fires right before the endpoint action method is executed and the second one fires after the action method has run. As a learning exercise, let us modify the behavior of the example weatherforecast endpoint that you receive in the webapi template, replacing the random weather forecasts with our own awesome forecast.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Filterations
{
    public class MyAwesomeFilter : IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
            Console.WriteLine("Im in ActionExecutedContext");

            // Add awesome weather forecast
            var awesomeWeather = new WeatherForecast
            {
                Date = DateTime.Now,
                Summary = "Awesomeness",
                TemperatureC = 22
            };

            context.Result = new ObjectResult(awesomeWeather);
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            Console.WriteLine("Im in ActionExecutingContext");
        }
    }
}

Now, to wire this up, go to the Program.cs class and use the optional options object when adding controllers to the pipeline.

using Filterations;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(
    options =>
    {
        options.Filters.Add(new MyAwesomeFilter());
    });

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Run your app and hit the weatherforecast endpoint. It will return your new awesome weather forecast, every single time.

output of the weatherforecast endpoint showing weather forecast that was injected using the IActionFilter

Filter Attribute

While the example above allows you to intercept all incoming requests and indiscriminately apply this filter, if you want to selectively apply the filter to only select endpoints, you’ll want to convert it to a filter attribute. You can do so by inheriting from the Attribute class.

MyAwesomeFilter, now inheriting the Attribute class

Now that you have converted this filter as an attribute, you can decorate controllers or action methods of your choosing with this annotation and ASP.NET will selectively apply this filter to just those endpoints. Note the example below where the filter attribute is only applied to the GetWeatherForecast endpoint.

using Microsoft.AspNetCore.Mvc;

namespace Filterations.Controllers;

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

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [Route("weather")]
    [HttpGet(Name = "GetWeatherForecast")]
    [MyAwesomeFilter] // I'm selectively applying the filter to *only this* action method
    public IEnumerable<WeatherForecast> GetWeatherForecast()
    {
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToArray();
    }

    [Route("random")]
    [HttpGet(Name = "GetRandomNumber")]
    public int GetRandomNumber()
    {
        var randomNumber = new Random().Next(0, 1000000);
        return randomNumber;
    }
}

After converting the filter to an attribute and decorating select endpoints with this attribute, also remember to remove the global wiring of this filter that you added as an option to the AddControllers() in Program.cs, reverting back to the non-overloaded version.

var builder = WebApplication.CreateBuilder(args);

// revert back to the non-overloaded version of the method
builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Test the weather endpoint and you’ll see the same output from before, the one that we are generating in the filter.

weather endpoint still returns the output set in the filter

However, if you invoke the random endpoint, it will return you a random number (not returning the ouput from the filter).

random endpoint does not run the filter

Closing Thoughts

Filters can be a powerful tool in your toolkit when building ASP.NET web applications. It can eliminate extra lines of code that you would otherwise have to write in multiple endpoints and instead allow you to centralize that logic in a single place. Besides the ActionFilter that we saw in the examples here, go try out some of the other types of filters on your own and see where you can incorporate these concepts in your own applications and architectures.

Leave a Comment

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