I need to create a custom action filter that implements both IActionModelConvention
and IFilterFactory
.
I use IActionModelConvention
for setting several routes at the same time, and I use IFilterFactory
to inject some services I need to use.
The problem is that the Apply()
method from the IActionModelConvention
is being called before the CreateInstance()
method from the IFilterFactory
, and I need the injected services to be available in the Apply()
.
My question is how do I inject the services before the Apply()
method is being called? and I also prefer to use IFilterFactory
to inject services because it doesn't force me to wrap the actual attribute with the [ServiceFilter]
or [TypeFilter]
attributes.
Here is my code:
public class Contains2RoutesAttribute : Attribute, IActionModelConvention, IFilterFactory
{
public ISomeService SomeService{ get; set; }
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
ISomeService someService = serviceProvider.GetService<ISomeService>();
return new Contains2RoutesAttribute() { SomeService = someService };
}
public void Apply(ActionModel action)
{
// Here I need to use the service injected:
this.SomeService.DoSomething(); // ERROR: The service here is null.
action.Selectors.Clear();
// Adding route 1:
action.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel { Template = "~/index1" }
});
// Adding route 2:
action.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel { Template = "~/index2" }
});
}
}
Your IActionModelConvention
implementation will run only once, at startup. Apply
will be called once for each action. To use ISomeService
inside of the Apply
function, pass it through as a constructor argument. Your Contains2RoutesAttribute
class need not be an attribute or an implementation of IFilterFactory
, as you've confirmed in the comments that it does not participate in the filter pipeline. Here's a code example, where I've also renamed the class to better represent what it's doing (it's no longer an attribute):
public class Contains2RoutesConvention : IActionModelConvention
{
private readonly ISomeService someService;
public Contains2RoutesConvention(ISomeService someService)
{
this.someService = someService;
}
public void Apply(ActionModel actionModel)
{
someService.DoSomething();
...
}
}
Register this convention in Startup.ConfigureServices
:
services.AddMvc(options =>
{
options.Conventions.Add(new Contains2RoutesConvention(new SomeService()));
});
This is where it gets a bit more interesting. You can't use dependency injection with a convention, so in this example, I've created an instance of SomeService
inline when constructing Contains2RoutesConvention
. If you want this instance to be a singleton that can be used elsewhere in your application, you can do something like this in ConfigureServices
:
var someService = new SomeService();
services.AddMvc(options =>
{
options.Conventions.Add(new Contains2RoutesConvention(someService));
});
services.AddSingleton<ISomeService>(someService);
Of course, this depends on whether or not SomeService
has dependencies of its own, but if it does, they would not be resolvable from the DI container as it's too early in the pipeline.