Hangfire RecurringJob with dependency injection
Asked Answered
C

2

5

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?

Cruciferous answered 30/10, 2015 at 21:38 Comment(0)
G
1

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!

Gerardgerardo answered 12/12, 2015 at 16:19 Comment(0)
Q
22

Not sure when it was added, but I just did something similar to this in my current project and it works fine. The EmailBackgroundTask is a non-static class and that's a non-static method. The class has 4 dependencies injected via the Hangfire.Unity DI package.

RecurringJob.AddOrUpdate<EmailBackgroundTask>(x=>x.SendPeriodicEmail(),Cron.MinuteInterval(5));
Qintar answered 19/10, 2016 at 16:8 Comment(3)
I'm not sure if the accepted answer was the case when this question was asked, but this should be the accepted answer now.Unsay
this doesn't use the dependency injection system, it simply provides a default instance of the type that is mentioned as a generic parameter. And like mentioned in the documentation (docs.hangfire.io/en/latest/background-methods/…), implementing a JobActivator like @Steve did is the actual solutionCholecystotomy
The current (1.7.27) version supports asp.net core Dependency Injection without having to add a JobActivator etc, just by adding AddHangfire() at startup. Write your code like the example above and Hangfire will inject any constructor dependencies to create EmailBackgroundTask. If you use a different DI container you may need to do something else.Rooks
G
1

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!

Gerardgerardo answered 12/12, 2015 at 16:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.