×

iFour Logo

Dependency injection in action filters in ASP.NET Core

Kapil Panchal - February 12, 2021

Listening is fun too.

Straighten your back and cherish with coffee - PLAY !

  • play
  • pause
  • pause
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();
        ...
    }
    ...
}

				
				

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

 
using Microsoft.Extensions.DependencyInjection;
				

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.

The ServiceFilter attribute


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

 
[ServiceFilter(typeof(ThrottleFilter))] 

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));
    }
    ...
}

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

public void ConfigureServices(IServiceCollection services)
{
    ...
services.AddScoped();
    ...
}


			

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;
    }
}
			   

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();
    });
}
 

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; }
} 

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().FirstOrDefault();
        if (throttleAttribute != null)
        {
        }
    }
}
 

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.


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(); ... } ... } Note that in order to use the generic version of the GetService you need to add the following statements:   using Microsoft.Extensions.DependencyInjection; 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))] 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)); } ... } Naturally, as we are resolving our filters from the IoC container, we need to register it: public void ConfigureServices(IServiceCollection services) { ... services.AddScoped(); ... } 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; } } 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(); }); } 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. See here 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; } } 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().FirstOrDefault(); if (throttleAttribute != null) { } } } 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.
Kapil Panchal

Kapil Panchal

A passionate Technical writer and an SEO freak working as a Content Development Manager at iFour Technolab, USA. With extensive experience in IT, Services, and Product sectors, I relish writing about technology and love sharing exceptional insights on various platforms. I believe in constant learning and am passionate about being better every day.

Build Your Agile Team

Enter your e-mail address Please enter valid e-mail

Categories

Ensure your sustainable growth with our team

Talk to our experts
Sustainable
Sustainable
 
Blog Our insights
Location Intelligence Use Cases, and Benefits
Location Intelligence Use Cases, and Benefits

You must be wondering what exactly is Location Intelligence. To put it simply, it is basically a way of deriving actionable insights from geographic data and spatial relationships...

13 Ways Power Apps Simplifies eDiscovery
13 Ways Power Apps Simplifies eDiscovery

E-Discovery is a crucial process for legal research enabling lawyers to find the digital evidence they need. It involves finding, collecting, and filtering e-data related to their...

Top Data Analytics Trends You Can't Ignore
Top Data Analytics Trends You Can't Ignore

Can you believe that 147 zettabytes of data have already been created in 2024, and guess what? It is anticipated to be 180 zettabytes by 2025 (according to Statista). Now just think...