6

I've just installed Hangfire package in my MVC website. I've created a Startup class

[assembly: OwinStartup(typeof(Website.Startup))]

namespace Website
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            Hangfire.ConfigureHangfire(app);
            Hangfire.InitializeJobs();
        }
    }
}

and a Hangfire class

public class Hangfire
{
    public static void ConfigureHangfire(IAppBuilder app)
    {
        app.UseHangfire(config =>
        {
            config.UseSqlServerStorage("DefaultConnection");
            config.UseServer();
            config.UseAuthorizationFilters(); 
        });
    }

    public static void InitializeJobs()
    {
        RecurringJob.AddOrUpdate<CurrencyRatesJob>(j => j.Execute(), "* * * * *");
    }
}

Also, I've created a new job in a separate class library

public class CurrencyRatesJob
{
    private readonly IBudgetsRepository budgetsRepository;

    public CurrencyRatesJob(IBudgetsRepository budgetsRepository)
    {
        this.budgetsRepository = budgetsRepository;
    }

    public void Execute()
    {
        try
        {
            var budgets = new BudgetsDTO();
            var user = new UserDTO();

            budgets.Sum = 1;
            budgets.Name = "Hangfire";
            user.Email = "email@g.com";

            budgetsRepository.InsertBudget(budgets, user);
        }
        catch (Exception ex)
        {
            var message = ex.ToString();
            throw new NotImplementedException(message);
        }
    }
}

So when I run the application, in the Hangfire's dashboard I get the following error:

Failed An exception occurred during job activation.
System.MissingMethodException

No parameterless constructor defined for this object.

System.MissingMethodException: No parameterless constructor defined for this object.
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)
   at Hangfire.JobActivator.ActivateJob(Type jobType)
   at Hangfire.Common.Job.Activate(JobActivator activator)

So, I'm a little lost here. What am I missing?

Marian Ene
  • 615
  • 2
  • 7
  • 21
  • Do you have the register code where hangfire gets notified of what classes it should be using? – dbarnes May 04 '15 at 17:33
  • I think I don't have it. I don't remember reading about that. Can you tell me more? – Marian Ene May 04 '15 at 17:35
  • Well I'm not a hangfire expert, but it's clear it's trying to resolve the `CurrencyRatesJob` but it can't because it doesn't know what `IBudgetsRepository` should be resolved to. That is why you are getting the no empty constructor error. Maybe this post can help http://stackoverflow.com/questions/26615794/hangfire-autofac-with-mvc-app-injection-fails. – dbarnes May 04 '15 at 17:38
  • @MarianEne Check my response below, this takes care of injecting dependencies and the issues you already have. – radu florescu Apr 09 '16 at 20:56

4 Answers4

5

It appears you have not connected Hangfire to the IoC container you are using and therefore it uses its default strategy to create a requested type, which in your specific example means calling:

System.Activator.CreateInstance(typeof(CurrencyRatesJob));

Because the CurrencyRatesJob class does not have a default parameterless constructor, this fails with the error message you show in your question.

To connect Hangfire to your IoC infrastructure, you need to create your own JobActivator class that overrides the ActivateJob method and uses the configured IoC container to create instances of the requested job types.

An example that uses Unity as the container (UnityJobActivator) can be found here and an example for the Funq container (FunqJobActivator) can be found here.

The process is described in the Hangfire documentation, and standard implementations for several container types are available from the Hangfire github repo

Community
  • 1
  • 1
Alex
  • 13,024
  • 33
  • 62
  • Well, I'm using Ninject and I've seen that there is a Nuget package (Hangfire.Ninject), but I'm not sure how to work with it. I tried creating a parameterless constructor in the job and it was executed, but I didn't have the IBudgetsRepository object. – Marian Ene May 04 '15 at 18:58
  • @MarianEne, right, because that is what you need the IoC for, to resolve the `IBudgetsRepository` dependency as the instance injected into your `CurrencyRatesJob`. Using that Nuget package, you would use the `UseNinjectActivator` extension method at startup: `GlobalConfiguration.Configuration.UseNinjectActivator(kernel);` with `kernel` being the instance of your Ninject kernel. – Alex May 04 '15 at 19:08
  • so I'm a little confused about where should I add this: `var kernel = new StandardKernel(); GlobalConfiguration.Configuration.UseNinjectActivator(kernel);`. If I add it to Global.asax I get a `Ninject.ActivationException`; if I add it to the job, right after the `try` I get the same parateterless exception. – Marian Ene May 04 '15 at 19:49
  • @MarianEne, you mentioned you are already using Ninject in your project, so I would assume a `kernel` instance is already created in your MVC project startup code. If you are using the 'NinjectMvc' Nuget package, you should find a `NinjectWebCommon.cs` file in your `App_Start` folder. In its `private static void RegisterServices(IKernel kernel)` method, add the line `GlobalConfiguration.Configuration.UseNinjectActivator(kernel);` – Alex May 04 '15 at 20:22
  • So, I've renamed the class where I have the Hangfire configuration, from Hangfire to HangfireConfig, because there were some conflicts when I wanted to use UseNinjectActivator. Then I moved `Hangfire.GlobalConfiguration.Configuration.UseNinjectActivator(kernel);` to my `NinjectWebCommon` class, where I have the kernel and I use it for binding the interfaces to the appropriate classes. Now I get another error saying that `Hangfire.IGlobalConfiguration` does not contain a definition of `UseNinjectActivator`. – Marian Ene May 04 '15 at 20:57
  • Have you added the line `using Hangfire;` to the `NinjectWebCommon.cs` file? – Alex May 04 '15 at 21:01
1

I found a pretty straightforward discussion here: Hangfire Discussion

I will include my sample code:

public class Job : IJob
{
    private readonly IService _service;
    private readonly IDbContext _context;

    public Job()
    {
         // this needs to be here, although this won't be used in the actual running
    }

    public Job(IService service, IDbContext context) : this()
    {
        _service = service;
        _context = context;
    }

    public override void Run(SomeModel searchLocationModel)
    {
    }
}

My actual invoke of Hangfire is below:

IJob job = NinjectWebCommon.Kernel.TryGet<Job>();

RecurringJob.AddOrUpdate(job.ToString(), () => job.Run(model), Cron.Weekly, TimeZoneInfo.Utc);
radu florescu
  • 4,315
  • 10
  • 60
  • 92
1

You need to inject the dependencies to get this working. Install nuget unity package :

Install-Package Hangfire.Unity

And then register on Global.asax you will have BootStraper initialise method.Navigate to boot strapper class and in initialise have following code,

DependencyResolver.SetResolver(new UnityDependencyResolver(container));

Complete code will look something like following if you are using Unity.

public static class Bootstrapper
{
    public static IUnityContainer Initialise()
    {
        var container = BuildUnityContainer();

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));

        return container;
     }



 private static IUnityContainer BuildUnityContainer()
 {
    var container = new UnityContainer();       
    GlobalConfiguration.Configuration.UseUnityActivator(container);
    RegisterTypes(container);
    return container;
 }
Simsons
  • 12,295
  • 42
  • 153
  • 269
0

Neither of the above answers were possible to implement in our project. So we ended up creating a background job helper that used reflection to instantiate the class (without a parameterless constructor) and then calling the method.

using Newtonsoft.Json.Linq;
using System;
using System.Reflection;

public static class BackgroundJobHelper
{
    public static object Execute(string userId, Type classType, string functionName, object[] param)
    {
        ServiceFactory serviceFactory = new ServiceFactory(userId);
        var classToInvoke = Activator.CreateInstance(classType, new object[] { serviceFactory });
        return Send(classType, classToInvoke, functionName, param);
    }

    private static object Send(Type classType, object className, string functionName, object[] param, Type[] fnParameterTypes = null)
    {
        MethodInfo methodInfo;
        if (!fnParameterTypes.IsNullOrEmpty())
        {
            methodInfo = classType.GetMethod(functionName, fnParameterTypes);
        }
        else
        {
            methodInfo = classType.GetMethod(functionName);
        }
        var methodParameters = methodInfo.GetParameters();
        //Object of type 'System.Int64' cannot be converted to type 'System.Int32'. While deserializing int is converted into long hence explictly make it Int32.
        for (int i = 0; i < param.Length; i++)
        {
            var methodParameterType = methodParameters[i].ParameterType;
            if (param[i] != null)
            {
                if (param[i] is long l)
                {
                    if (l >= int.MinValue && l <= int.MaxValue) param[i] = (int)l;
                }
                else if (param[i].GetType() == typeof(JObject))
                {
                    param[i] = (param[i] as JObject).ToObject(methodParameterType);
                }
                else if (param[i].GetType() == typeof(JArray))
                {
                    param[i] = (param[i] as JArray).ToObject(methodParameterType);
                }
            }
        }
        return methodInfo.Invoke(className, param);
    }
}

Usage:

var backgroundJob = new BackgroundJobClient(new SqlServerStorage(db.Database.Connection));
var result = backgroundJob.Schedule(() => BackgroundJobHelper.Execute(userId, this.GetType(), nameof(this.SendMailAsync), new object[] { projectId, remarks }), TimeSpan.FromSeconds(30));
xrnd
  • 1,273
  • 3
  • 21
  • 38