James Smith
James Smith

Reputation: 431

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

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?

Upvotes: 42

Views: 46540

Answers (5)

Tien Dang
Tien Dang

Reputation: 83

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

Upvotes: 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:...

Upvotes: -1

Francisco Goldenstein
Francisco Goldenstein

Reputation: 13767

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/

Upvotes: 7

adem caglin
adem caglin

Reputation: 24083

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?

Upvotes: 42

Ralf B&#246;nning
Ralf B&#246;nning

Reputation: 15415

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.

Upvotes: 40

Related Questions