user3401335
user3401335

Reputation: 2405

How can I use dependency injection in asp.net core action filter?

I have a FilterAttribute that has two parameters, one defined in dependency injection and one defined on method of controller as as string

public controller : ControllerBase 
{
    [MyFilter("Parameter1", FromDependency)]
    public ActionResult MyMethod()
    {
         ....
    }
}

and the filter

public MyFilter : Attribute
{
    MyFilter(string parameter1, context fromDependency)
    {
    }
}

How can I inject the parameter from dependency injection?

Upvotes: 1

Views: 2536

Answers (4)

DubMan
DubMan

Reputation: 460

Just to add another option (when using later .NetCore). You can use the IAsyncActionFilter, (there are other filter interfaces that also are available and can use same/similar patern)

So lets say we want to maybe Audit a controller in an API, we can build out the filter to also have pre and post execution like in earlier syntaxes so:

public AuditChangeFilter : IAsyncActionFilter
{
   // DI things in constructor
   public AuditChangeFilter(ILogger<AuditChangeFilter> logger)

   public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutedContext, ActionExecutionDelegate next)
   {        
    // Code to execute before the action executes
    await OnActionExecutingAsync(actionExecutedContext);

    // Call the next delegate/middleware in the pipeline
    var resultContext = await next();

    // Code to execute after the action executes
    await OnActionExecutedAsync(actionExecutedContext, resultContext);
   }

  private async Task OnActionExecutingAsync(ActionExecutingContext actionExecutedContext)
  {
    // Pre execution code
  }

  private async Task OnActionExecutedAsync(ActionExecutingContext actionExecutedContext, ActionExecutedContext resultContext)
  {
    // Post execution code
  }
}

Now in order for this to work we need a little magic in the form of a wrapper filter attribute which will be the decorator:

public class AuditChangeAttribute : TypeFilterAttribute<AuditChangeFilter>
{
  public AuditChangeAttribute()
  {
  }
}

And thats it, oh a part from decorating your API controller method:

[HttpPost]
[ProducesResponseType(typeof(string), 200)]
[AuditChange] // or [AuditChangeAttribute]
public async Task<IActionResult> Post(YourViewModel viewModel)
{
}

Upvotes: 0

Markus
Markus

Reputation: 22456

You can implement an IFilterFactory for this purpose. The runtime checks for this interface when creating filters and calls the CreateInstance method that gets an IServiceProvider as a parameter. You can use this provider to create services and inject them into the filter.

The following sample is taken from the docs:

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }
}

If you need to both use services from DI and values defined on the attribute, you can use the following approach:

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    private readonly string _attrParam;

    public ResponseHeaderFilterFactory(string attrParam)
    {
      _attrParam = attrParam;
    }

    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) 
    {
        var svc = serviceProvider.GetRequiredService<IMyService>();
        return new InternalResponseHeaderFilter(_attrParam, svc);
    }

    private class InternalResponseHeaderFilter : IActionFilter
    {
        private readonly string _attrParam;
        private readonly IMyService _service;

        public InternalResponseHeaderFilter(string attrParam, IMyService service)
        {
          _attrParam = attrParam;
          _service = service;
        }

        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }
}

You can then apply the filter like this:

public controller : ControllerBase 
{
    [ResponseHeaderFilterFactory("Parameter1")]
    public ActionResult MyMethod()
    {
         ....
    }
}

Upvotes: 4

Vahid
Vahid

Reputation: 524

Injecting components into action filter attributes directly is not possible but there are various workarounds to allow us to effectively accomplish the same thing. Using ServiceFilter is a relatively clean way to allow dependency injection into individual action filters.

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

[ServiceFilter(typeof(MyFilter))]

And our filter:

public class MyFilter: IActionFilter
 { 
    MyFilter(string parameter1, context fromDependency)
    {
    }
 }

Obviously, as we are resolving our filter from the IoC container, we need to register it:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<MyFilter>(x => 
    new Service(x.GetRequiredService<IOtherService>(),
            "parameter1"));
    ...
}

more details in Paul Hiles article: here

Upvotes: 0

ale91
ale91

Reputation: 376

You can implement ActionFilterAttribute to get DI dependencies from HttpContext.RequestServices:

public sealed class MyAttr : ActionFilterAttribute
{
    MyAttr(string parameter1)
    {
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        //Get dependency from HttpContext services
        var myDependency = context.HttpContext.RequestServices.GetService<MyDependency>();

        //Use it
        myDependency.DoSomething();

        //....
    }
}

Upvotes: 1

Related Questions