DEV Community

Cover image for Dependency injection in action filters in ASP.NET Core
Harshal Suthar
Harshal Suthar

Posted on • Originally published at ifourtechnolab.com

Dependency injection in action filters in ASP.NET Core

It is quite common to decorate the ASP.NET MVC controller actions with filter attributes to differentiate cross-cutting concerns from the main concern of the action. Sometimes these filters require the use of other components but the attributes are very limited in their efficiency and dependence injection into an attribute is not directly possible.

This post looks at some of the different techniques for injection dependence in action filters in the ASP.NET Core. We discuss when each method should be used before taking a step back and when we can approach the problem separately for a cleaner solution.

DI and action filter attributes

The Attributes in C # are very simple. You can pass static values to the constructor and/or set public properties directly.

Because attribute parameters are evaluated at compile time, they should be compiled time constant. So injecting dependencies from an IoC container is not an option. In a situation where we want an attribute to have access to another component, we must use a workaround.

Let's look at some options:

The RequestServices.GetService Service Locator

If we cannot inject a component into our attribute, it seems that the next best option is to request the component from our IoC container (either directly or by wrapper).

In the .NET core, we can use the service location to resolve components from a built-in IoC container using the RequestServices.GetService:

    public class ThrottleFilterAttribute : Attribute, IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        var cache = context.HttpContext.RequestServices.GetService<idistributedcache>();
        ...
    }
    ...
}
</idistributedcache>

Enter fullscreen mode Exit fullscreen mode

Note that in order to use the generic version of the GetService you need to add the following statements:

using Microsoft.Extensions.DependencyInjection;
Enter fullscreen mode Exit fullscreen mode

This will work but is not recommended. Unlike constructor injection, it can be much harder to work out your dependence with the service locator (anti)pattern. The beauty of constructor injection is simplicity. Just by looking at the definition of a constructor, we immediately know all the classes on which the class depends. The service location makes the unit test harder which requires you to have more knowledge about the internals of the subject under test.

Read More: Hashing, Encryption And Random In Asp.net Core

The ServiceFilter attribute

The servicefilter attribute may be used at the action or controller level. Usage is very straightforward:

[ServiceFilter(typeof(ThrottleFilter))] 

Enter fullscreen mode Exit fullscreen mode

The servicefilter attribute allows us to specify the type of our action filter and can automatically resolve the class from the built-in IoC container. This means we can change our action filter to accept dependencies directly from the constructor:

public class ThrottleFilter :IActionFilter
{
    private readonlyIDistributedCache _cache;
    public ThrottleFilter(IDistributedCache cache)
    {
        _cache = cache ?? throw new ArgumentNullException(nameof(cache));
    }
    ...
}

Enter fullscreen mode Exit fullscreen mode

Naturally, as we are resolving our filters from the IoC container, we need to register it:

public void ConfigureServices(IServiceCollection services)
{
    ...
services.AddScoped<throttlefilter>();
    ...
}
</throttlefilter>

Enter fullscreen mode Exit fullscreen mode

The TypeFilter attribute

The servicefilter is very useful for attributes that have dependencies that need to be resolved from the IoC container but the lack of property support is a major limitation. If we modify our ThrottleFilter example to add configuration properties (while retaining IDistributedCache dependencies), then the servicefilter is no longer useful to us. We can however use typefilter.

public class ThrottleFilter :IActionFilter
{
    private readonlyIDistributedCache _cache;
    public intMaxRequestPerSecond{ get; set; }
    public ThrottleFilter(IDistributedCache cache, intmaxRequestPerSecond)
    {
        _cache = cache ?? throw new ArgumentNullException(nameof(cache));
MaxRequestPerSecond = maxRequestPerSecond;
    }
}

Enter fullscreen mode Exit fullscreen mode

The typefilter is similar to the servicefilter, but there are two notable differences:

  • The type being resolved doesn't need to be registered with the IoC container..
  • Arguments can be provided that are used while constructing the filter

Global action filters

So far, we've only talked about filters implemented as attributes but you can also apply filters globally:

 public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
    {
options.Filters.Add<throttlefilter>();
    });
}
</throttlefilter> 

Enter fullscreen mode Exit fullscreen mode

This allows us to resolve the filter from the IoC container in the same way as using a servicefilter attribute on an action or controller but instead, it will be applied to every action of each controller. You cannot use global filter isolation even if you need to be able to configure individual actions.

Passive Attributes

If we think about it, it does not make sense for action filter attributes to house complex logic and interactions with other components. Attributes are best for identification and by choosing which actions or controllers we belong to. They are also best for individual actions or configurations of controllers. They probably shouldn't be full of complex code.

The presence of a ThrottleAttribute on the action indicates that it should be throttled. This is an opt-in approach but we can easily use an opt-out approach where an attribute indicates that we do not want to throttle the action.

Searching for Reliable .NET Development Company? Enquire Today.

We also want to be able to alternately refer to maximum requests per second. Implementation is trivial:

public class ThrottleAttribute : Attribute, IFilterMetadata
{
    public intMaxRequestPerSecond{ get; set; }
} 

Enter fullscreen mode Exit fullscreen mode

Note that we apply IFilterMetadata. This is a marker interface that makes it easier to read our attributes from the global filters.

The global action filter is registered with MVC in the same way as we showed at the beginning of the section. All dependencies are resolved automatically by the built-in IoC container.

public class ThrottleFilter :IActionFilter
{
    private readonlyIDistributedCache _cache;
    public ThrottleFilter(IDistributedCache cache)
    {
        _cache = cache ?? throw new ArgumentNullException(nameof(cache));
    }
    public void OnActionExecuted(ActionExecutedContext context)
    {
        //noop
    }
    public void OnActionExecuting(ActionExecutingContext context)
    {
varthrottleAttribute = context.ActionDescriptor.FilterDescriptors
.Select(x =>x.Filter).OfType<throttleattribute>().FirstOrDefault();
        if (throttleAttribute != null)
        {
        }
    }
}
</throttleattribute> 

Enter fullscreen mode Exit fullscreen mode

Conclusion

It is not possible to inject components directly into action filter attributes but there are various workarounds to allow the same thing to be accomplished effectively. Using a servicefilter is a relatively clean way to allow dependency injection into individual action filters. Specifying the type for the filter in this way does mean that invalid types can be entered and will not be discovered until runtime, however.

Before implementing any of these techniques, you should consider a passive attribute approach. In our opinion, this is the best solution to the problem. To summarize, this method requires two classes. We have a very simple marker attribute applied to actions and this is combined with a global action filter that has real logic.

Top comments (0)