No parameterless constructor defined for this object - Hangfire scheduler
Asked Answered
S

4

7

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 protected]";

            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?

Sekofski answered 4/5, 2015 at 17:30 Comment(4)
Do you have the register code where hangfire gets notified of what classes it should be using?Ido
I think I don't have it. I don't remember reading about that. Can you tell me more?Sekofski
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 #26616294.Ido
@MarianEne Check my response below, this takes care of injecting dependencies and the issues you already have.Anthesis
R
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

Refractometer answered 4/5, 2015 at 17:56 Comment(6)
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.Sekofski
@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.Refractometer
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.Sekofski
@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);Refractometer
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.Sekofski
Have you added the line using Hangfire; to the NinjectWebCommon.cs file?Refractometer
A
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);
Anthesis answered 9/4, 2016 at 20:54 Comment(0)
G
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;
 }
Gayelord answered 14/9, 2017 at 8:51 Comment(0)
V
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));
Vassalize answered 15/1, 2020 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.