简体   繁体   English

带有 .NET 核心的 Hangfire 依赖注入

[英]Hangfire dependency injection with .NET Core

How can I use .NET Core's default dependency injection in Hangfire?如何在 Hangfire 中使用 .NET Core 的默认依赖注入?

I am new to Hangfire and searching for an example which works with ASP.NET Core.我是 Hangfire 的新手,正在寻找一个适用于 ASP.NET 核心的示例。

See full example on GitHub https://github.com/gonzigonz/HangfireCore-Example .在 GitHub https://github.com/gonzigonz/HangfireCore-Example上查看完整示例。
Live site at http://hangfirecore.azurewebsites.net/实时站点位于http://hangfirecore.azurewebsites.net/

  1. Make sure you have the Core version of Hangfire:确保你有 Hangfire 的核心版本:
    dotnet add package Hangfire.AspNetCore

  2. Configure your IoC by defining a JobActivator .通过定义JobActivator配置您的 IoC。 Below is the config for use with the default asp.net core container service:以下是用于默认 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. Next register hangfire as a service in the Startup.ConfigureServices method:接下来在Startup.ConfigureServices方法中将Startup.ConfigureServices注册为服务:

     services.AddHangfire(opt => opt.UseSqlServerStorage("Your Hangfire Connection string"));
  4. Configure hangfire in the Startup.Configure method.Startup.Configure方法中配置 hangfire。 In relationship to your question, the key is to configure hangfire to use the new HangfireActivator we just defined above.关于你的问题,关键是配置HangfireActivator以使用我们刚刚定义的新HangfireActivator To do so you will have to provide hangfire with the IServiceProvider and this can be achieved by just adding it to the list of parameters for the Configure method.为此,您必须为IServiceProvider提供 hangfire,只需将其添加到Configure方法的参数列表即可实现。 At runtime, DI will providing this service for you:在运行时,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. When you enqueue a job, use the registered type which usually is your interface.当您排队作业时,请使用通常是您的接口的注册类型。 Don't use a concrete type unless you registered it that way.除非您以这种方式注册,否则不要使用具体类型。 You must use the type registered with your IoC else Hangfire won't find it.您必须使用在 IoC 中注册的类型,否则 Hangfire 将找不到它。 For Example say you've registered the following services:例如,假设您已经注册了以下服务:

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

Then you could enqueue DbManager with an instantiated version of the class:然后,您可以使用类的实例化版本将DbManager加入DbManager

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

However you could not do the same with MyService .但是,您不能对MyService做同样的事情。 Enqueuing with an instantiated version would fail because DI would fail as only the interface is registered.使用实例化版本入队会失败,因为 DI 会失败,因为仅注册了接口。 In this case you would enqueue like this:在这种情况下,你会像这样排队:

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

DoritoBandito's answer is incomplete or deprecated. DoritoBandito 的答案不完整或已弃用。

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

Register services:注册服务:

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

Enqueue:入队:

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

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

As far as I am aware, you can use .net cores dependency injection the same as you would for any other service.据我所知,您可以像使用任何其他服务一样使用 .net cores 依赖项注入。

You can use a service which contains the jobs to be executed, which can be executed like so您可以使用包含要执行的作业的服务,可以像这样执行

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

Here is an example of the Job Service class这是 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);
    }      
}

And in your projects Startup.cs you can add a dependency as normal在您的项目 Startup.cs 中,您可以像往常一样添加依赖项

services.AddTransient< IClientService, ClientService>();

Not sure this answers your question or not不确定这是否能回答您的问题

All of the answers in this thread are wrong/incomplete/outdated.该线程中的所有答案都是错误的/不完整的/过时的。 Here's an example with ASP.NET Core 3.1 and Hangfire.AspnetCore 1.7.这是 ASP.NET Core 3.1 和 Hangfire.AspnetCore 1.7 的示例。

Client:客户:

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

Server (same or different application):服务器(相同或不同的应用程序):

{
    //...
    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)
    {

    }
}

Currently, Hangfire is deeply integrated with Asp.Net Core.目前,Hangfire 与 Asp.Net Core 深度集成。 Install Hangfire.AspNetCore to set up the dashboard and DI integration automatically.安装Hangfire.AspNetCore以自动设置仪表板和 DI 集成。 Then, you just need to define your dependencies using ASP.NET core as always.然后,您只需像往常一样使用 ASP.NET 核心定义您的依赖项。

If you are trying to quickly set up Hangfire with ASP.NET Core (tested in ASP.NET Core 2.2) you can also use Hangfire.MemoryStorage .如果您尝试使用 ASP.NET Core(在 ASP.NET Core 2.2 中测试)快速设置 Hangfire,您还可以使用Hangfire.MemoryStorage All the configuration can be performed in Startup.cs :所有的配置都可以在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)
}

Of course, everything is store in memory and it is lost once the application pool is recycled, but it is a quick way to see that everything works as expected with minimal configuration.当然,一切都存储在内存中,一旦应用程序池被回收,它就会丢失,但这是一种快速查看一切以最少配置按预期工作的方法。

To switch to SQL Server database persistence, you should install Hangfire.SqlServer package and simply configure it instead of the memory storage:要切换到 SQL Server 数据库持久性,您应该安装Hangfire.SqlServer包并简单地配置它而不是内存存储:

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

I had to start HangFire in main function.我必须在主函数中启动 HangFire。 This is how I solved it:我是这样解决的:

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

Actually there is an easy way for dependency injection based job registration.实际上,有一种基于依赖注入的作业注册的简单方法。 You just need to use the following code in your Startup:您只需要在 Startup 中使用以下代码:

public class Startup {
    public void Configure(IApplicationBuilder app)
    {
        var factory = app.ApplicationServices
            .GetService<IServiceScopeFactory>();

        GlobalConfiguration.Configuration.UseActivator(
            new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
    }
}

However i personally wanted a job self registration including on demand jobs (recurring jobs which are never executed, except by manual trigger on hangfire dashboard), which was a little more complex then just that.但是,我个人想要一个工作自我注册,包括按需工作(从未执行的重复工作,除非通过hangfire仪表板上的手动触发),这比那个要复杂一些。 I was (for example) facing issues with the job service activation, which is why i decided to share most of my implementation code.我(例如)面临工作服务激活的问题,这就是为什么我决定分享我的大部分实现代码。

//I wanted an interface to declare my jobs, including the job Id.
public interface IBackgroundJob { 
    string Id { get; set; } 
    void Invoke(); 
}

//I wanted to retrieve the jobs by id. Heres my extension method for that:
public static IBackgroundJob GetJob(
    this IServiceProvider provider,
    string jobId) => provider
        .GetServices<IBackgroundJob>()
        .SingleOrDefault(j => j.Id == jobId);

//Now i needed an invoker for these jobs.
//The invoker is basically an example of a dependency injected hangfire job.
internal class JobInvoker { 
    public JobInvoker(IServiceScopeFactory factory) { 
        Factory = factory; 
    }
    public IServiceScopeFactory Factory { get;  }
    public void Invoke(string jobId)
    {
        //hangfire jobs should always be executed within their own scope. 
        //The default AspNetCoreJobActivator should technically already do that.
        //Lets just say i have trust issues.
        using (var scope = Factory.CreateScope())
        {
            scope.ServiceProvider
              .GetJob(jobId)?
              .Invoke();
        }
}

//Now i needed to tell hangfire to use these jobs. 
//Reminder: The serviceProvider is in IApplicationBuilder.ApplicationServices
public static void RegisterJobs(IServiceProvider serviceProvider) { 
    var factory = serviceProvider.GetService();
    GlobalConfiguration.Configuration.UseActivator(new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
    var manager = serviceProvider.GetService<IRecurringJobManager>();
    var config = serviceProvider.GetService<IConfiguration>();
    var jobs = serviceProvider.GetServices<IBackgroundJob>();
    foreach (var job in jobs) {
        var jobConfig = config.GetJobConfig(job.Id);
        var schedule = jobConfig?.Schedule; //this is a cron expression
        if (String.IsNullOrWhiteSpace(schedule))
            schedule = Cron.Never(); //this is an on demand job only!

        manager.AddOrUpdate(
            recurringJobId: job.Id,
            job: GetJob(job.Id),
            cronExpression: schedule);
}
//and last but not least...  
//My Method for creating the hangfire job with injected job id 
private static Job GetJob(string jobId)
{
    var type = typeof(JobInvoker);
    var method = type.GetMethod("Invoke");
    return new Job(
        type: type,
        method: method,
        args: jobId);
}

Using the above code i was able to create hangfire job services with full dependency injection support.使用上面的代码,我能够创建具有完全依赖注入支持的hangfire作业服务。 Hope it helps someone.希望它可以帮助某人。

Use the below code for Hangfire configuration使用以下代码进行 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();
        }
    }
}

Admin Job Manager管理工作经理

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM