【问题标题】:No parameterless constructor defined for this object - Hangfire scheduler没有为此对象定义无参数构造函数 - Hangfire 调度程序
【发布时间】:2020-01-16 00:49:29
【问题描述】:

我刚刚在我的 MVC 网站中安装了 Hangfire 包。 我创建了一个 Startup 类

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

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

还有一个 Hangfire 课程

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(), "* * * * *");
    }
}

另外,我在单独的类库中创建了一个新作业

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);
        }
    }
}

所以当我运行应用程序时,在 Hangfire 的仪表板中我收到以下错误:

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)

所以,我有点迷路了。我错过了什么?

【问题讨论】:

  • 你有注册码,hangfire 会在其中收到关于它应该使用哪些类的通知?
  • 我想我没有。我不记得读过那个。你能告诉我更多吗?
  • 好吧,我不是hangfire 专家,但很明显它正在尝试解决CurrencyRatesJob,但它不能,因为它不知道IBudgetsRepository 应该解决什么问题。这就是为什么你会得到 no empty constructor 错误。也许这篇文章可以帮助stackoverflow.com/questions/26615794/…。
  • @MarianEne 在下面查看我的回复,这会处理注入依赖项和您已经遇到的问题。

标签: c# scheduler hangfire


【解决方案1】:

您似乎没有将 Hangfire 连接到您正在使用的 IoC 容器,因此它使用其默认策略来创建请求的类型,在您的具体示例中意味着调用:

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

由于CurrencyRatesJob 类没有默认的无参数构造函数,因此失败并显示您在问题中显示的错误消息。

要将 Hangfire 连接到您的 IoC 基础架构,您需要创建自己的 JobActivator 类来覆盖 ActivateJob 方法并使用配置的 IoC 容器来创建请求的作业类型的实例。

使用 Unity 作为容器的示例 (UnityJobActivator) 可以在 here 中找到,Funq 容器的示例 (FunqJobActivator) 可以在 here 中找到。

Hangfire documentation 中描述了该过程,Hangfire github repo 中提供了几种容器类型的标准实现

【讨论】:

  • 好吧,我正在使用 Ninject,我看到有一个 Nuget 包 (Hangfire.Ninject),但我不确定如何使用它。我尝试在作业中创建一个无参数构造函数并执行它,但我没有 IBudgetsRepository 对象。
  • @MarianEne,对,因为这是您需要 IoC 来解决 IBudgetsRepository 依赖项作为注入到您的 CurrencyRatesJob 中的实例。使用该 Nuget 包,您将在启动时使用 UseNinjectActivator 扩展方法:GlobalConfiguration.Configuration.UseNinjectActivator(kernel); 其中 kernel 是您的 Ninject 内核的实例。
  • 所以我有点困惑我应该在哪里添加这个:var kernel = new StandardKernel(); GlobalConfiguration.Configuration.UseNinjectActivator(kernel);。如果我将它添加到 Global.asax 我会得到一个Ninject.ActivationException;如果我将它添加到工作中,就在try 之后,我会得到同样的无参数异常。
  • @MarianEne,你提到你已经在你的项目中使用了 Ninject,所以我假设你的 MVC 项目启动代码中已经创建了一个 kernel 实例。如果您使用的是“NinjectMvc”Nuget 包,您应该在您的 App_Start 文件夹中找到一个 NinjectWebCommon.cs 文件。在其private static void RegisterServices(IKernel kernel) 方法中,添加行GlobalConfiguration.Configuration.UseNinjectActivator(kernel);
  • 因此,我将拥有 Hangfire 配置的类从 Hangfire 重命名为 HangfireConfig,因为在我想使用 UseNinjectActivator 时出现了一些冲突。然后我将Hangfire.GlobalConfiguration.Configuration.UseNinjectActivator(kernel); 移动到我的NinjectWebCommon 类,在那里我有内核,我用它来将接口绑定到适当的类。现在我收到另一个错误,说Hangfire.IGlobalConfiguration 不包含UseNinjectActivator 的定义。
【解决方案2】:

您需要注入依赖项才能使其正常工作。 安装 nuget unity 包:

Install-Package Hangfire.Unity

然后在 Global.asax 上注册,您将拥有 BootStraper 初始化方法。导航到引导程序类并在初始化中有以下代码,

DependencyResolver.SetResolver(new UnityDependencyResolver(container));

如果您使用 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;
 }

【讨论】:

    【解决方案3】:

    我在这里找到了一个非常简单的讨论:Hangfire Discussion

    我将包含我的示例代码:

    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)
        {
        }
    }
    

    我对 Hangfire 的实际调用如下:

    IJob job = NinjectWebCommon.Kernel.TryGet<Job>();
    
    RecurringJob.AddOrUpdate(job.ToString(), () => job.Run(model), Cron.Weekly, TimeZoneInfo.Utc);
    

    【讨论】:

      【解决方案4】:

      以上答案都无法在我们的项目中实现。所以我们最终创建了一个后台作业助手,它使用反射来实例化类(没有无参数构造函数),然后调用该方法。

      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);
          }
      }
      

      用法:

      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));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-29
        • 2016-04-25
        • 2018-02-11
        • 1970-01-01
        相关资源
        最近更新 更多