Dependency injection for filter attribute (non-global)

job-filters
dotnetcore
aspnetcore
Tags: #<Tag:0x00007f499a0e7e38> #<Tag:0x00007f499a0e7cd0> #<Tag:0x00007f499a0e7b90>

#1

Hi,

I have a Hangfire filter attribute class, which needs some dependencies via .NET Core’s inbuilt DI, but the filter attribute is to be applied on only one job instance method, so usage like shown below won’t work as it’ll be global in scope (i.e. filter will apply to all job methods):

services.AddHangfire((provider, config) => config.UseFilter(new MyFilterAttribute(provider.GetService<SomeDependency>())));

Is there a good way to do DI for a non-global filter class, so that it can be applied as an attribute to an individual job method?


#2

Still learning filters and attributes. I tried these steps below, and it seems to be working. Can someone please review and confirm if it’s the right approach?

  1. Separate the attribute and filter classes:
public class MyHangfireFilterAttribute : JobFilterAttribute
{
    // no implementation (empty class)
}
public class MyHangfireFilter: IClientFilter, IServerFilter, IElectStateFilter
{
    private readonly SomeDependency _dependency;

    public MyHangfireFilter(SomeDependency someDependency)
    {
        _dependency = someDependency;
    }

    // further implementation...
}
  1. Register the filter:
services.AddHangfire((provider, config) => 
    config.UseFilter(new MyHangfireFilter(provider.GetService<SomeDependency>())));
  1. Decorate the job method with the attribute:
public class MyHangfireJobs
{
    [MyHangfireFilter]
    public void DoSomething()
    {
    }
}

So, separating the attribute and filter classes, but following their naming convention (NameFilterAttribute for attribute class and NameFilter for filter class) did the trick. An article about passive attributes in .NET helped.

Can someone please review and confirm if it’s the right approach?


#3

That looks right to me


#4

Thanks for checking @ihockett.


#5

I am wondering how you were able to inject dependencies. I am trying this now to avoid need to define an attribute global and then inside this filter implementation exit early for all jobs it’s not relevant too.

I keep getting the complains from C# compiler that I am trying to use attribute parameter type which aren’t supported. Looks like you can’t use classes or interfaces? So my question what are you injecting or what am I doing wrong?

I am trying to inject a simple class e.g. MyClass: IMyClassInterface when I use as parameter in my AttributeFilter class e.g. public NotifyWhenFailedAttribute(MyClass manualService = null, int maxRetries=10).


#6

Hey @alien, if you’re using .NET Core’s builtin DI then you can do it like this:

  1. Register the class/interface service in Startup.cs (web) or Program.cs (console):

    services.AddTransient<SomeDependencyClass>();

    Note: change service lifetime to scoped or singleton as per your needs.

  2. Register filter instance with dependency in Program.cs (required for console app only; not required for web):

    services.AddHangfire((provider, config) => config.UseFilter(new MyFilterAttribute(provider.GetService<SomeDependencyClass>())));

Hope it helps.


#7

But are you marking job methods with [MyFilter] then? I am not fully understanding how you can inject your dependencies this way? As attribute don’t support classes or interfaces as parameter types for the constructor. I am getting the compiler errors:
https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0655

My goal is to have a simple Filter which I can assign to a specific without needing to instantiate it in Startup.cs and then register it via GlobalJobFilters.Filters.Add(myFilter);