C# 11 introduces a large list of new language features. One of the smaller features is the support for generic attributes.
Before C# 11, when you had to pass type information to an attribute, you had to use a type parameter.
Let’s use an example that always has annoyed me; the [ServiceFilterAttribute]
. If you have ever used an ASP.NET MVC filter that needed dependencies, then you should know this attribute.
Here is an example I copied from the Microsoft documentation:
[ServiceFilter(typeof(LoggingResponseHeaderFilterService))] | |
public IActionResult WithServiceFilter() => | |
Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}"); |
You see in the example above is that the type of the actual filter we want to invoke is passed as a parameter to the ServiceFilterAttribute.
The constructor looks something like this:
/// <summary> | |
/// Instantiates a new <see cref="ServiceFilterAttribute"/> instance. | |
/// </summary> | |
/// <param name="type">The <see cref="Type"/> of filter to find.</param> | |
public ServiceFilterAttribute(Type type) | |
{ | |
ServiceType = type ?? throw new ArgumentNullException(nameof(type)); | |
} |
By not being able to use generics, I could pass anything I want as a parameter to the ServiceFilter no matter if it is actually a filter or not. The compiler will not help me to avoid passing an incorrect argument.
A type safe ServiceFilter
With the release of C# 11 and the introduction of Generic Attributes, we can create a type safe variant of the [ServiceFilterAttribute]
as an example:
[Route("api/[controller]")] | |
[ApiController] | |
public class CoursesController : ControllerBase | |
{ | |
[HttpGet] | |
[TypeSafeServiceFilterAttribute<ExampleFilter>()] | |
public IActionResult Get() | |
{ | |
return Ok(); | |
} | |
} |
public class ExampleFilter : IActionFilter | |
{ | |
public void OnActionExecuting(ActionExecutingContext context) | |
{ | |
} | |
public void OnActionExecuted(ActionExecutedContext context) | |
{ | |
} | |
} |
public class TypeSafeServiceFilterAttribute<T> : ServiceFilterAttribute where T: IActionFilter | |
{ | |
public TypeSafeServiceFilterAttribute():base(typeof(T)) | |
{ | |
} | |
} |
Now the compiler will warn me when I try to pass a type that isn’t a filter:
Remark: There are multiple interfaces that can be used to implement a filter. In the example above I only used the IActionFilter. So this is not a production ready solution for all use cases.