How to use Action Filters with Dependency Injection in ASP.NET CORE?
Asked Answered
L

5

40

I use constructor-based dependency injection everywhere in my ASP.NET CORE application and I also need to resolve dependencies in my action filters:

public class MyAttribute : ActionFilterAttribute
{
    public int Limit { get; set; } // some custom parameters passed from Action
    private ICustomService CustomService { get; } // this must be resolved

    public MyAttribute()
    {
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // my code
        ...

        await next();
    }
}

Then in Controller:

[MyAttribute(Limit = 10)]
public IActionResult()
{
    ...

If I put ICustomService to the constructor, then I'm unable to compile my project. So, how do I supossed to get interface instances in action filter?

Lonne answered 31/8, 2016 at 18:11 Comment(3)
Can you add setter in the property CustomService so that it is writable also? and add ICustomService as parameter in constructor?Luckless
Possible duplicate of ASP.Net Core (MVC 6) - Inject service into Action FilterFerebee
Possible duplicate of How do I add a parameter to an action filter in asp.net?Forbidden
L
39

If you want to avoid the Service Locator pattern you can use DI by constructor injection with a TypeFilter.

In your controller use

[TypeFilter(typeof(MyActionFilterAttribute), Arguments = new object[] {10})]
public IActionResult() NiceAction
{
   ...
}

And your ActionFilterAttribute does not need to access a service provider instance anymore.

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public int Limit { get; set; } // some custom parameters passed from Action
    private ICustomService CustomService { get; } // this must be resolved

    public MyActionFilterAttribute(ICustomService service, int limit)
    {
        CustomService = service;
        Limit = limit;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        await next();
    }
}

For me the annotation [TypeFilter(typeof(MyActionFilterAttribute), Arguments = new object[] {10})]seems to be awkward. In order to get a more readable annotation like [MyActionFilter(Limit = 10)]your filter has to inherit from TypeFilterAttribute. My answer of How do I add a parameter to an action filter in asp.net? shows an example for this approach.

Laurenalaurence answered 31/8, 2016 at 20:6 Comment(1)
You can also use IAsyncActionFilter instead of IActionFilter if you need asyncLonne
F
41

You can use Service Locator:

public void OnActionExecuting(ActionExecutingContext actionContext)
{
     var service = actionContext.HttpContext.RequestServices.GetService<IService>();
}

Note that the generic method GetService<> is an extension method and lives in namespace Microsoft.Extensions.DependencyInjection.

If you want to use constructor injection use TypeFilter. See How do I add a parameter to an action filter in asp.net?

Forbidden answered 31/8, 2016 at 18:24 Comment(2)
It is a good suggestion, however it requires more work to unit test it.Nevsa
Works for me - when my class 'ApiKeyAuthAttribute : Attribute, IAsyncActionFilter' used constructor injection, I could not decorate my controller '[ApiKeyAuth(thingToInject)]' nor could I use '[ServiceFilter(typeof(ApiKeyAuthAttribute))]', whereas avoiding constructor injection altogether using the above and grabbing the resolved thingToInject implementation directly from RequestServices works.Nanette
L
39

If you want to avoid the Service Locator pattern you can use DI by constructor injection with a TypeFilter.

In your controller use

[TypeFilter(typeof(MyActionFilterAttribute), Arguments = new object[] {10})]
public IActionResult() NiceAction
{
   ...
}

And your ActionFilterAttribute does not need to access a service provider instance anymore.

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public int Limit { get; set; } // some custom parameters passed from Action
    private ICustomService CustomService { get; } // this must be resolved

    public MyActionFilterAttribute(ICustomService service, int limit)
    {
        CustomService = service;
        Limit = limit;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        await next();
    }
}

For me the annotation [TypeFilter(typeof(MyActionFilterAttribute), Arguments = new object[] {10})]seems to be awkward. In order to get a more readable annotation like [MyActionFilter(Limit = 10)]your filter has to inherit from TypeFilterAttribute. My answer of How do I add a parameter to an action filter in asp.net? shows an example for this approach.

Laurenalaurence answered 31/8, 2016 at 20:6 Comment(1)
You can also use IAsyncActionFilter instead of IActionFilter if you need asyncLonne
M
7

You can use ServiceFilters to instantiate the ActionFilters you need in the controller.

In the controller:

[ServiceFilter(typeof(TrackingAttribute), Order = 2)]

You need to register TrackingAttribute in the dependency container so the ServiceFilter can resolve it.

Read more about this at https://www.strathweb.com/2015/06/action-filters-service-filters-type-filters-asp-net-5-mvc-6/

Melton answered 24/7, 2018 at 15:14 Comment(1)
The official doc for using ServiceFilter is hereCourlan
I
1

I'm browsing around for more information, seems like what we need is here:

The following filters support constructor dependencies provided from DI:
 1. ServiceFilterAttribute
 2. TypeFilterAttribute
 3. IFilterFactory implemented on the attribute.

Reference: microsoft doc

Immateriality answered 11/9, 2023 at 15:58 Comment(0)
M
-1

A good option is doing this (Tested in .NET Core 3.1):

  1. Inside a Filter class put this:

    public static class FilterContainer {

         public class GenericFilter : ActionFilterAttribute
         {
             public override void OnActionExecuting(ActionExecutingContext filterContext)
             {
                 string Action = filterContext.ActionDescriptor.RouteValues["action"];
                 Console.WriteLine($"[action]: {Action} STARTING");
             }
    
             public override void OnActionExecuted(ActionExecutedContext filterContext)
             {
                 string Action = filterContext.ActionDescriptor.RouteValues["action"];
                 Console.WriteLine($"[action]: {Action} FINISHED");
             }
    
             public override void OnResultExecuting(ResultExecutingContext filterContext)
             {
                 string Action = filterContext.ActionDescriptor.RouteValues["action"];
                 Console.WriteLine($"[action]: {Action} GIVING RESULT");
             }
    
             public override void OnResultExecuted(ResultExecutedContext filterContext)
             {
                 string Action = filterContext.ActionDescriptor.RouteValues["action"];
                 ObjectResult ObjectResult = (ObjectResult)filterContext.Result;
                 Console.WriteLine($"[action]: {Action} RESULT GIVEN. Value: {ObjectResult.Value}");
             }
         }
     }
    
  2. Inside the Startup.cs/ConfigureServices(IServiceCollection services) put this:

    services.AddControllers().AddMvcOptions(options => options.Filters.Add(new FilterContainer.GenericFilter()));

The result is that a request to any kind of action inside your .NET Core app will go in and out through this pipeline without declaring a filter attribute above any action.

Let me show you an example inside the Output window of Visual Studio:

[action]: JSON STARTING
[action]: JSON FINISHED
[action]: JSON GIVING RESULT
[action]: JSON RESULT GIVEN. Value: TestId: 103, FullName:...
Messily answered 2/3, 2021 at 17:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.