Reputation: 2405
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
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
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
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
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