Skip to main content

Filters

Filters

Filters allow us to run code at specific points during the lifecycle of processing an HTTP request.

They are especially useful when we need to execute logic across multiple actions or controllers, and we want to avoid duplicating code.

For example, an authorization filter could block a user from accessing a resource if they are not logged in. This is a filter that applies to multiple actions, not just a single one.

Common Filter Types:

1. Authorization filters:

These filters are executed before the action method to determine if a user is authorized to access the resource. If the user is not authorized, the request is blocked before it reaches the action.

2. Resources filters:

These filters are executed after the authorization filters. They can be used for general processing or to implement caching. These filters can also short-circuit the pipeline, preventing other filters or the action method from being executed.

3. Action filters:

These filters run just before and just after the execution of an action method. They can be used to manipulate the action's parameters or the data returned by the action.

4. Exception filters:

These filters are executed when an exception is thrown during the execution of an action, if the exception is not caught by a try-catch block. They can also be triggered during controller creation or model binding.

5. Result filters:

These filters are executed before and after the execution of the action result. They allow manipulation of the response before it is sent to the client.

Applying Filters

Filters can be applied at different levels:

  • Action Level: The filter is executed when an HTTP request is processed by the action that is marked with the filter.
  • Controller Level: The filter is applied to all actions within the controller.
  • Global Filters: The filter is applied to the entire API, affecting all controllers and actions. Filters are typically applied using attributes, either on individual actions or at the controller level.

Provided Filters

ASP.NET provides several built-in filters for use.

Caching Filters

One example is the ResponseCaching filter. To enable it, add the filter in Program.cs:

builder.Services.AddControllers();
builder.Services.AddResponseCaching();
...
app.UseHttpsRedirection();
app.UseResponseCaching();
app.MapControllers();
...

Then apply it before any action or controller you want it to affect. For example:

GenreController.cs
[ResponseCache(Duration = 60)]
[HttpGet]
public async Task<ActionResult<List<Genre>>> Get(){ }

This will cause the first request to go through the method, but subsequent requests (within the cache duration) will return the response from the cache, unless authentication information is included in the request header.

Test in Postman

Remember to turn off Send no-cache header in Postman settings.

Authorization Filters

Since Authorization run before the action method, which means they execute before caching filters. If a request is blocked by an authorization filter (because the user is not authorized), the request will not proceed to the caching filter, and the response will not be cached.

To enable authentication, add the following in Program.cs:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
...
app.UseResponseCaching();
app.UseAuthentication();
app.UseAuthorization();
...

Then apply the Authorize filter to the controller or action before the caching filter. For example:

GenreController.cs
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ResponseCache(Duration = 60)]
[HttpGet]
public async Task<ActionResult<List<Genre>>> Get(){ }

This ensures that the Authorize filter runs first, and the ResponseCache filter is applied afterward, only if the user is authenticated.

Custom Filters

To create custom filter, we need to implement either the IActionFilter or IAsyncActionFilter interface.

For example:

public class MyActionFilter : IActionFilter
{
// this method is executed before the action
public void OnActionExecuting(ActionExecutingContext context){ }

// method is executed after the action
public void OnActionExecuted(ActionExecutedContext context){ }
}

Then, apply the custom filter in a controller:

GenreController.cs
[ServiceFilter(typeof(MyActionFilter))]
[HttpGet]
public async Task<ActionResult<List<Genre>>> Get(){}

Finally, register the filter in Program.cs:

Program.cs
builder.Services.AddTransient<MyActionFilter>();

Global Filters

To apply a filter globally (across all actions and controllers), configure it in the Program.cs or Startup.cs file. This is useful for concerns that apply throughout the application, such as logging, exception handling, or authentication.

For example, to create a global exception filter:

public class MyExceptionFilter: ExceptionFilterAttribute
{
private readonly ILogger<MyExceptionFilter> logger;
public MyExceptionFilter(ILogger<MyExceptionFilter> logger)
{
this.logger = logger;
}

// Log exception
// If there is a database, it can be used to log in the database
public override void OnException(ExceptionContext context)
{
logger.LogError(context.Exception, context.Exception.Message);
base.OnException(context);
}
}

In your controller, you can use the exception filter as follows:

GenreController.cs
[HttpGet("{id:int}")]
public ActionResult<List<Genre>> Get(int id, [FromHeader] string param2)
{
var genre = repository.GetGenreById(id);
if (genre == null)
{
throw new ApplicationException(); // use the filter here
}
return Ok(genre);
}

Finally, to apply this exception filter globally, add it in Program.cs:

Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add(typeof(MyExceptionFilter));
});