Skip to main content

Dependency Injection in MVC Action Filters using .NET 6

· 3 min read
Adnan Rafiq
Start and Finish Image

Image by awcreativeut

MVC Filters are discussed in detail at docs.microsoft.com

Filters in ASP.NET Core allow code to run before or after specific stages in the request processing pipeline.

Use case of TypeFilterAttribute?

Suppose you are writing a Filter Attribute of IAsyncResultFilter which gets executed when the action method completes unless the request response is short-circuited.

A result filter to remove cached value
public sealed class RemoveCacheResultFilter : Attribute, IAsyncResultFilter
{
private readonly IDistributedCache _distributedCache;

public RemoveCacheResultFilter(IDistributedCache distributedCache)
{
_distributedCache = distributedCache;
}

public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await _distributedCache.RemoveAsync("x").ConfigureAwait(false);
if (!context.Cancel) await next().ConfigureAwait(false);
}
}

It can be applied to the controller or its action method's, but it has dependency on IDistrubutedCache which can not be specified. So to fix this problem Microsoft.AspNetCore.Mvc has special filter type TypeFilterAttribute which creates the underlying filter instance and resolves any dependencies. It can be achieved using the below two approaches:

1: A result filter to remove cached value with DI using TypeFilterAttribute
[ApiController]
[Route("[controller]")]
public class UsersController : Controller
{
private readonly ILogger<UsersController> _usersLogger;

public UsersController(ILogger<UsersController> usersLogger)
{
_usersLogger = usersLogger;
}

[HttpGet(Name = "users/{userId:int}")]
[TypeFilter(typeof(RemovePlayerInstanceCacheResultFilter))]
[Produces(typeof(UserApiModel))]
public async Task<IResult> Get(int userId, CancellationToken cancellationToken)
{
if (userId <= 0) return Results.BadRequest();
_usersLogger.LogInformation("Sending query to the database for the user with Id {UserId}", userId);
//code omittted
return user is { } ? Results.Ok(user) : Results.NotFound();
}

internal record UserApiModel(int Id, [UsedImplicitly] string FirstName, string LastName);
}
2: A result filter to remove cached value with DI using classes
public class RemoveCacheFilterAttribute : TypeFilterAttribute
{
public RemoveCacheFilterAttribute() : base(
typeof(RemovePlayerInstanceCacheResultFilter))
{
}

// ReSharper disable once MemberCanBePrivate.Global
public sealed class RemoveCacheResultFilter : IAsyncResultFilter
{
private readonly IDistributedCache _distributedCache;

public RemoveCacheResultFilter(IDistributedCache distributedCache)
{
_distributedCache = distributedCache;
}

public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await _distributedCache.RemoveAsync("x").ConfigureAwait(false);
if (!context.Cancel) await next().ConfigureAwait(false);
}
}
}

A result filter to remove cached value with DI using classes
public class RemoveCacheFilterAttribute : TypeFilterAttribute
{
public RemoveCacheFilterAttribute() : base(
typeof(RemovePlayerInstanceCacheResultFilter))
{
}

// ReSharper disable once MemberCanBePrivate.Global
public sealed class RemoveCacheResultFilter : IAsyncResultFilter
{
private readonly IDistributedCache _distributedCache;

public RemoveCacheResultFilter(IDistributedCache distributedCache)
{
_distributedCache = distributedCache;
}

public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await _distributedCache.RemoveAsync("x").ConfigureAwait(false);
if (!context.Cancel) await next().ConfigureAwait(false);
}
}
}

Feedback

I would love to hear your feedback, feel free to share it on Twitter.