【问题标题】:Hangfire dependency injection with .NET Core使用 .NET Core 进行 Hangfire 依赖注入
【发布时间】:2017-06-09 08:36:12
【问题描述】:

如何在 Hangfire 中使用 .NET Core 的默认依赖注入?

我是 Hangfire 的新手,正在寻找一个适用于 ASP.NET Core 的示例。

【问题讨论】:

    标签: c# asp.net-core hangfire


    【解决方案1】:

    在 GitHub 上查看完整示例 https://github.com/gonzigonz/HangfireCore-Example
    现场直播http://hangfirecore.azurewebsites.net/

    1. 确保您拥有核心版 Hangfire:
      dotnet add package Hangfire.AspNetCore

    2. 通过定义 JobActivator 来配置您的 IoC。下面是用于默认 asp.net 核心容器服务的配置:

      public class HangfireActivator : Hangfire.JobActivator
      {
          private readonly IServiceProvider _serviceProvider;
      
          public HangfireActivator(IServiceProvider serviceProvider)
          {
              _serviceProvider = serviceProvider;
          }
      
          public override object ActivateJob(Type type)
          {
              return _serviceProvider.GetService(type);
          }
      }  
      
    3. 接下来将hangfire注册为Startup.ConfigureServices方法中的服务:

      services.AddHangfire(opt => 
          opt.UseSqlServerStorage("Your Hangfire Connection string"));
      
    4. Startup.Configure 方法中配置hangfire。关于您的问题,key 是配置hangfire 以使用我们刚刚在上面定义的新HangfireActivator。为此,您必须提供带有IServiceProvider 的hangfire,这可以通过将其添加到Configure 方法的参数列表中来实现。在运行时,DI 会为你提供这个服务:

      public void Configure(
          IApplicationBuilder app, 
          IHostingEnvironment env, 
          ILoggerFactory loggerFactory,
          IServiceProvider serviceProvider)
      {
          ...
      
          // Configure hangfire to use the new JobActivator we defined.
          GlobalConfiguration.Configuration
              .UseActivator(new HangfireActivator(serviceProvider));
      
          // The rest of the hangfire config as usual.
          app.UseHangfireServer();
          app.UseHangfireDashboard();
      }  
      
    5. 当您将作业排入队列时,请使用通常是您的接口的注册类型。除非您以这种方式注册,否则不要使用具体类型。您必须使用在您的 IoC 中注册的类型,否则 Hangfire 将找不到它。 例如假设您注册了以下服务:

      services.AddScoped<DbManager>();
      services.AddScoped<IMyService, MyService>();
      

    然后您可以将 DbManager 加入到该类的实例化版本中:

        BackgroundJob.Enqueue(() => dbManager.DoSomething());
    

    但是你不能对MyService 做同样的事情。使用实例化版本排队会失败,因为 DI 会失败,因为只有接口被注册。在这种情况下,你会像这样排队:

        BackgroundJob.Enqueue<IMyService>( ms => ms.DoSomething());
    

    【讨论】:

    • 值得注意的是,在ms =&gt; ms.DoSomething() 的情况下,表达式必须调用传入参数的方法。你不能做ms =&gt; Foo.StaticMethod(ms);在这种情况下,Hangfire 给出了一个神秘的例外。
    • 当前 Hangfire 版本 (1.6.19) 自动注册 AspNetCoreJobActivator。 github.com/HangfireIO/Hangfire/blob/master/src/…
    • 另外,扩展方法不适用于 Hangfire。需要创建一个包装器。
    • 这是错误的实现,不应使用。作业激活器具有BeginScope 功能是有原因的。在这里使用此实现的唯一方法是将其传递给根服务提供者。这意味着它永远不会被释放,这意味着每次创建作业时,都会泄漏所有创建的依赖项。
    • @Lukevp MS DI 容器有一个 CreateScope 方法来解决这个问题。如果您在控制台应用程序中运行,您可能会手动创建一个根范围 - 将其传递给您的自定义激活器,然后实现 BeginScope 等等。很简单。既然 Hangfire 已经有一个正确的实现,你也许可以简单地实例化那个。
    【解决方案2】:

    DoritoBandito 的回答不完整或已弃用。

    public class EmailSender {
         public EmailSender(IDbContext dbContext, IEmailService emailService)
         {
             _dbContext = dbContext;
             _emailService = emailService;
         }
    }
    

    注册服务:

    services.AddTransient<IDbContext, TestDbContext>();
    services.AddTransient<IEmailService, EmailService>();
    

    入队:

    BackgroundJob.Enqueue<EmailSender>(x => x.Send(13, "Hello!"));
    

    来源: http://docs.hangfire.io/en/latest/background-methods/passing-dependencies.html

    【讨论】:

    • 这是实现OP要求的正确方法。
    • 这里的关键是当你使用括号中的 时,会创建一个新对象;然后对其执行操作。确保添加 SERVICE 以使其正常工作。
    【解决方案3】:

    据我所知,您可以像使用任何其他服务一样使用 .net 核心依赖注入。

    您可以使用包含要执行的作业的服务,可以像这样执行

    var jobId = BackgroundJob.Enqueue(x =&gt; x.SomeTask(passParamIfYouWish));

    这里是 Job Service 类的示例

    public class JobService : IJobService
    {
        private IClientService _clientService;
        private INodeServices _nodeServices;
    
        //Constructor
        public JobService(IClientService clientService, INodeServices nodeServices)
        {
            _clientService = clientService;
            _nodeServices = nodeServices;
        }
    
        //Some task to execute
        public async Task SomeTask(Guid subject)
        {
            // Do some job here
            Client client = _clientService.FindUserBySubject(subject);
        }      
    }
    

    在您的项目 Startup.cs 中,您可以像往常一样添加依赖项

    services.AddTransient&lt; IClientService, ClientService&gt;();

    不确定这是否回答了您的问题

    【讨论】:

    • 此解决方案开箱即用。谢谢
    • 我也有依赖项在没有实现 JobActivator 的情况下被开箱即用地解决
    • 这是如何开箱即用的?所以 ClientService 只是你自己的 API。 BackgroundJob 如何知道调用 JobService 的实例? IJobService 是hangfire 接口吗?如果是这样,我找不到它。很困惑!
    【解决方案4】:

    此线程中的所有答案都是错误/不完整/过时的。这是 ASP.NET Core 3.1 和 Hangfire.AspnetCore 1.7 的示例。

    客户:

    //...
    using Hangfire;
    // ...
    
    public class Startup
    {
        // ...
    
        public void ConfigureServices(IServiceCollection services)
        {
            //...
            services.AddHangfire(config =>
            {
                // configure hangfire per your requirements
            });
        }
    }
    
    public class SomeController : ControllerBase
    {
        private readonly IBackgroundJobClient _backgroundJobClient;
    
        public SomeController(IBackgroundJobClient backgroundJobClient)
        {
            _backgroundJobClient = backgroundJobClient;
        }
        
        [HttpPost("some-route")]
        public IActionResult Schedule([FromBody] SomeModel model)
        {
            _backgroundJobClient.Schedule<SomeClass>(s => s.Execute(model));
        }
    }
    

    服务器(相同或不同的应用程序):

    {
        //...
        services.AddScoped<ISomeDependency, SomeDependency>();
    
        services.AddHangfire(hangfireConfiguration =>
        {
            // configure hangfire with the same backing storage as your client
        });
        services.AddHangfireServer();
    }
    
    public interface ISomeDependency { }
    public class SomeDependency : ISomeDependency { }
    
    public class SomeClass
    {
        private readonly ISomeDependency _someDependency;
    
        public SomeClass(ISomeDependency someDependency)
        {
            _someDependency = someDependency;
        }
    
        // the function scheduled in SomeController
        public void Execute(SomeModel someModel)
        {
    
        }
    }
    

    【讨论】:

    • 如果我在与客户端相同的应用程序上运行服务器并且我注册了这样的依赖项:services.AddScoped&lt;ISomeDependency , SomeDependency &gt;();,那么该依赖项的范围是什么?
    【解决方案5】:

    目前,Hangfire 与 Asp.Net Core 深度集成。自动安装Hangfire.AspNetCoreset up the dashboard 和DI 集成。然后,您只需像往常一样使用 ASP.NET 核心定义您的依赖项。

    【讨论】:

    • 我想将 ILogger 发送到 hangfire 中,但仅使用该软件包无法开箱即用。
    【解决方案6】:

    如果您尝试使用 ASP.NET Core(在 ASP.NET Core 2.2 中测试)快速设置 Hangfire,您也可以使用 Hangfire.MemoryStorage。所有的配置都可以在Startup.cs进行:

    using Hangfire;
    using Hangfire.MemoryStorage;
    
    public void ConfigureServices(IServiceCollection services) 
    {
        services.AddHangfire(opt => opt.UseMemoryStorage());
        JobStorage.Current = new MemoryStorage();
    }
    
    protected void StartHangFireJobs(IApplicationBuilder app, IServiceProvider serviceProvider)
    {
        app.UseHangfireServer();
        app.UseHangfireDashboard();
    
        //TODO: move cron expressions to appsettings.json
        RecurringJob.AddOrUpdate<SomeJobService>(
            x => x.DoWork(),
            "* * * * *");
    
        RecurringJob.AddOrUpdate<OtherJobService>(
            x => x.DoWork(),
            "0 */2 * * *");
    }
    
    public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
    {
        StartHangFireJobs(app, serviceProvider)
    }
    

    当然,一切都存储在内存中,一旦应用程序池被回收,它就会丢失,但这是一种快速的方法,可以通过最少的配置看到一切都按预期工作。

    要切换到 SQL Server 数据库持久化,您应该安装 Hangfire.SqlServer 包并简单地配置它而不是内存存储:

    services.AddHangfire(opt => opt.UseSqlServerStorage(Configuration.GetConnectionString("Default")));
    

    【讨论】:

      【解决方案7】:

      我必须在主函数中启动 HangFire。我就是这样解决的:

      public static void Main(string[] args)
          {
              var host = CreateWebHostBuilder(args).Build();
              using (var serviceScope = host.Services.CreateScope())
              {
                  var services = serviceScope.ServiceProvider;
      
                  try
                  {
                      var liveDataHelper = services.GetRequiredService<ILiveDataHelper>();
                      var justInitHangfire = services.GetRequiredService<IBackgroundJobClient>();
                      //This was causing an exception (HangFire is not initialized)
                      RecurringJob.AddOrUpdate(() => liveDataHelper.RePopulateAllConfigDataAsync(), Cron.Daily());
                      // Use the context here
                  }
                  catch (Exception ex)
                  {
                      var logger = services.GetRequiredService<ILogger<Program>>();
                      logger.LogError(ex, "Can't start " + nameof(LiveDataHelper));
                  }
              }
              host.Run();
          }
      
          public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
              WebHost.CreateDefaultBuilder(args)
                  .UseStartup<Startup>();
      }
      

      【讨论】:

        【解决方案8】:

        使用以下代码进行 Hangfire 配置

        using eForms.Core;
        using Hangfire;
        using Hangfire.SqlServer;
        using System;
        using System.ComponentModel;
        using System.Web.Hosting;
        
        namespace eForms.AdminPanel.Jobs
        {
            public class JobManager : IJobManager, IRegisteredObject
            {
                public static readonly JobManager Instance = new JobManager();
                //private static readonly TimeSpan ZeroTimespan = new TimeSpan(0, 0, 10);
                private static readonly object _lockObject = new Object();
                private bool _started;
                private BackgroundJobServer _backgroundJobServer;
                private JobManager()
                {
                }
                public int Schedule(JobInfo whatToDo)
                {
                    int result = 0;
                    if (!whatToDo.IsRecurring)
                    {
                        if (whatToDo.Delay == TimeSpan.Zero)
                            int.TryParse(BackgroundJob.Enqueue(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName)), out result);
                        else
                            int.TryParse(BackgroundJob.Schedule(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName), whatToDo.Delay), out result);
                    }
                    else
                    {
                        RecurringJob.AddOrUpdate(whatToDo.JobType.Name, () => RunRecurring(whatToDo.JobType.AssemblyQualifiedName), Cron.MinuteInterval(whatToDo.Delay.TotalMinutes.AsInt()));
                    }
        
                    return result;
                }
        
                [DisplayName("Id: {0}, Type: {1}")]
                [HangFireYearlyExpirationTime]
                public static void Run(int jobId, string jobType)
                {
                    try
                    {
                        Type runnerType;
                        if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
                        var runner = runnerType.CreateInstance<JobRunner>();
                        runner.Run(jobId);
                    }
                    catch (Exception ex)
                    {
                        throw new JobException($"Error while executing Job Id: {jobId}, Type: {jobType}", ex);
                    }
                }
        
                [DisplayName("{0}")]
                [HangFireMinutelyExpirationTime]
                public static void RunRecurring(string jobType)
                {
                    try
                    {
                        Type runnerType;
                        if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
                        var runner = runnerType.CreateInstance<JobRunner>();
                        runner.Run(0);
                    }
                    catch (Exception ex)
                    {
                        throw new JobException($"Error while executing Recurring Type: {jobType}", ex);
                    }
                }
        
                public void Start()
                {
                    lock (_lockObject)
                    {
                        if (_started) return;
                        if (!AppConfigSettings.EnableHangFire) return;
                        _started = true;
                        HostingEnvironment.RegisterObject(this);
                        GlobalConfiguration.Configuration
                            .UseSqlServerStorage("SqlDbConnection", new SqlServerStorageOptions { PrepareSchemaIfNecessary = false })
                           //.UseFilter(new HangFireLogFailureAttribute())
                           .UseLog4NetLogProvider();
                        //Add infinity Expiration job filter
                        //GlobalJobFilters.Filters.Add(new HangFireProlongExpirationTimeAttribute());
        
                        //Hangfire comes with a retry policy that is automatically set to 10 retry and backs off over several mins
                        //We in the following remove this attribute and add our own custom one which adds significant backoff time
                        //custom logic to determine how much to back off and what to to in the case of fails
        
                        // The trick here is we can't just remove the filter as you'd expect using remove
                        // we first have to find it then save the Instance then remove it 
                        try
                        {
                            object automaticRetryAttribute = null;
                            //Search hangfire automatic retry
                            foreach (var filter in GlobalJobFilters.Filters)
                            {
                                if (filter.Instance is Hangfire.AutomaticRetryAttribute)
                                {
                                    // found it
                                    automaticRetryAttribute = filter.Instance;
                                    System.Diagnostics.Trace.TraceError("Found hangfire automatic retry");
                                }
                            }
                            //Remove default hangefire automaticRetryAttribute
                            if (automaticRetryAttribute != null)
                                GlobalJobFilters.Filters.Remove(automaticRetryAttribute);
                            //Add custom retry job filter
                            GlobalJobFilters.Filters.Add(new HangFireCustomAutoRetryJobFilterAttribute());
                        }
                        catch (Exception) { }
                        _backgroundJobServer = new BackgroundJobServer(new BackgroundJobServerOptions
                        {
                            HeartbeatInterval = new System.TimeSpan(0, 1, 0),
                            ServerCheckInterval = new System.TimeSpan(0, 1, 0),
                            SchedulePollingInterval = new System.TimeSpan(0, 1, 0)
                        });
                    }
                }
                public void Stop()
                {
                    lock (_lockObject)
                    {
                        if (_backgroundJobServer != null)
                        {
                            _backgroundJobServer.Dispose();
                        }
                        HostingEnvironment.UnregisterObject(this);
                    }
                }
                void IRegisteredObject.Stop(bool immediate)
                {
                    Stop();
                }
            }
        }
        

        管理员作业经理

            public class Global : System.Web.HttpApplication
            {
                void Application_Start(object sender, EventArgs e)
                {
                    if (Core.AppConfigSettings.EnableHangFire)
                    {
                        JobManager.Instance.Start();
        
                        new SchedulePendingSmsNotifications().Schedule(new Core.JobInfo() { JobId = 0, JobType = typeof(SchedulePendingSmsNotifications), Delay = TimeSpan.FromMinutes(1), IsRecurring = true });
        
                    }
                }
                protected void Application_End(object sender, EventArgs e)
                {
                    if (Core.AppConfigSettings.EnableHangFire)
                    {
                        JobManager.Instance.Stop();
                    }
                }
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-05-22
          • 1970-01-01
          • 2021-10-23
          • 1970-01-01
          • 2021-02-02
          • 1970-01-01
          • 2020-12-20
          • 2018-03-03
          相关资源
          最近更新 更多