Search code examples
c#dependency-injectionhangfire

Hangfire RecurringJob with dependency injection


Is it possible to have Hangfire instantiate objects with the configure JobActivator when they're scheduled to run as a RecurringJob?

The signature of the method seems to force static only usages:

public static void AddOrUpdate<T>(
    string recurringJobId,
    Expression<Action<T>> methodCall,

I have several ideas on how i could "abuse" statics to back channel things around, but i feel like i might be missing something. Was there a design decision that hangfire only supports statics in chron jobs?


Solution

  • Quick answer is no, the default job activator only works on parameter-less constructors or static methods. I did something (in VB.net) quick and dirty to see if I could get it working and have shown it below.

    By using "AddOrUpdate", you are telling Hangfire to create an instance of T and then access the method of T, so this signature works on instance members, not statics. if you use one of the other "AddOrUpdate" method signatures without the generic parameter, it will require a static.

    Now the fun part: if type T doesn't have a parameter-less default constructor then it will fail using the default jobactivator as you said.

    This is where you can now use a custom jobactivator to supply dependencies to the constructors of your tasks. If you create your own class inheriting from JobActivator then you can supply dependencies to your jobs.

    Here is my VB code:

    Imports Hangfire
    Imports System.Reflection
    
    Public Class JobActivationContainer
        Inherits JobActivator
    
        Private Property ParameterMap As Dictionary(Of Type, [Delegate])
    
        Private Function CompareParameterToMap(p As ParameterInfo) As Boolean
            Dim result = ParameterMap.ContainsKey(p.ParameterType)
            Return result
        End Function
    
        Public Overrides Function ActivateJob(jobType As Type) As Object
            Dim candidateCtor As Reflection.ConstructorInfo = Nothing
            'Loop through ctor's and find the most specific ctor where map has all types.
            jobType.
                GetConstructors.
                ToList.
                ForEach(
                    Sub(i)
                        If i.GetParameters.ToList.
                            TrueForAll(AddressOf CompareParameterToMap) Then
                                If candidateCtor Is Nothing Then candidateCtor = i
                                If i IsNot candidateCtor AndAlso i.GetParameters.Count > candidateCtor.GetParameters.Count Then candidateCtor = i
                        End If
                    End Sub
                )
    
                If candidateCtor Is Nothing Then
                    'If the ctor is null, use default activator.
                    Return MyBase.ActivateJob(jobType)
                Else
                    'Create a list of the parameters in order and activate
                    Dim ctorParameters As New List(Of Object)
                    candidateCtor.GetParameters.ToList.ForEach(Sub(i)       ctorParameters.Add(ParameterMap(i.ParameterType).DynamicInvoke()))
                Return Activator.CreateInstance(jobType, ctorParameters.ToArray)
            End If
        End Function
    
        Public Sub RegisterDependency(Of T)(factory As Func(Of T))
            If Not ParameterMap.ContainsKey(GetType(T)) Then    ParameterMap.Add(GetType(T), factory)
        End Sub
    
        Public Sub New()
            ParameterMap = New Dictionary(Of Type, [Delegate])
        End Sub
    End Class
    

    I know this doesn't answer the question on how to "abuse" statics, but it does show how you could roll-your-own IoC container for hangfire, or use one of the already supported IoC's as per the manual: http://hangfirechinese.readthedocs.org/en/latest/background-methods/using-ioc-containers.html

    Note: I plan to use a proper IoC, my code above was purely academic and would need a lot of work!