Recently I wanted to add a logger to a custom authorization filter(which was not the best idea) but I didn't want to new up an instance inside the filter, I wanted DI to handle all of that for me.
I did some digging and came across the TypeFilterAttribute, ServiceFilterAttribute and the IFilterFactory.
IFilterFactory
IFilterFactory interface allows you to create an instance of your filter by implementing the CreateInstance method inside your filter.
When MVC invokes a filter, it first tries to cast it to an IFilterFactory. If that cast succeeds, it then calls the CreateInstance method.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] | |
public class SecurityFilterUsingFactory : Attribute, IAuthorizationFilter, IFilterFactory | |
{ | |
private IAuthenticationService _authenticationService; | |
private ILogger _logger; | |
public SecurityFilterUsingFactory(IAuthenticationService authenticationService, ILogger<string> logger) | |
{ | |
_authenticationService = authenticationService; | |
_logger = logger; | |
} | |
public bool IsReusable => false; | |
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) | |
{ | |
// gets the dependecies from the serviceProvider | |
// and creates an instance of the filter | |
return new SecurityFilterUsingFactory( | |
(IAuthenticationService)serviceProvider.GetService(typeof(IAuthenticationService)), | |
(ILogger<string>)serviceProvider.GetService(typeof(ILogger<string>))); | |
} |
TypeFilter & ServiceFilter
The ServiceFilterAttribute implements the IFilterFactory. IFilterFactory exposes the CreateInstance method for creating an IFilterMetadata instance. The CreateInstance method loads the specified type from the container.
Filters used with the ServiceFilterAttribute need to be registered with the container.
// This method gets called by the runtime. Use this method to add services to the container. | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddTransient<IMovieService, MovieService>(); | |
services.AddTransient<IAuthenticationService, AuthenticationService>(); | |
//register your filter here | |
services.AddScoped<SecurityFilter>(); | |
services.AddMvc() | |
.AddMvcOptions(options => | |
{ | |
options.Filters.Add<SecurityFilter>(); | |
options.Filters.Add<SecurityFilterUsingFactory>(); | |
}) | |
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1); | |
} |
[ServiceFilter(typeof(SecurityFilter))] | |
[HttpGet("api/movies/all")] | |
public async Task<ActionResult> GetMoviesAsync() | |
{ | |
return Ok(await _movieService.GetMoviesAsync()); | |
} |
The TypeFilterAttribute is similar to the ServiceFilterAttribute, but its type isn't resolved directly from the DI container. It instantiates the type by using Microsoft.Extensions.DependencyInjection.ObjectFactory.
This means that types referenced using the TypeFilterAttribute don't need to be registered with the container first and the TypeFilterAttribute can optionally accept constructor arguments for the type.
When using the TypeFilterAttribute, setting IsReusable is a hint that the filter instance may be reused outside of the request scope it was created within. The framework provides no guarantees that a single instance of the filter will be created. Avoid using IsReusable when using a filter that depends on services with a lifetime other than singleton.
[HttpGet("api/movie/{id}")] | |
[TypeFilter(typeof(SecurityFilterUsingFactory))] | |
public async Task<ActionResult> GetMovieByIdAsync(int id) | |
{ | |
if (id <= 0) | |
return BadRequest(); | |
var movie = await _movieService.GetMovieById(id); | |
if (movie == null) | |
return NotFound(); | |
return Ok(movie); | |
} |
The sample code can be found here
Top comments (0)