簡體   English   中英

帶有 .NET 核心的 Hangfire 依賴注入

[英]Hangfire dependency injection with .NET Core

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

我是 Hangfire 的新手,正在尋找一個適用於 ASP.NET 核心的示例。

在 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. 接下來在Startup.ConfigureServices方法中將Startup.ConfigureServices注冊為服務:

     services.AddHangfire(opt => opt.UseSqlServerStorage("Your Hangfire Connection string"));
  4. Startup.Configure方法中配置 hangfire。 關於你的問題,關鍵是配置HangfireActivator以使用我們剛剛定義的新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加入DbManager

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

但是,您不能對MyService做同樣的事情。 使用實例化版本入隊會失敗,因為 DI 會失敗,因為僅注冊了接口。 在這種情況下,你會像這樣排隊:

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

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

據我所知,您可以像使用任何其他服務一樣使用 .net cores 依賴項注入。

您可以使用包含要執行的作業的服務,可以像這樣執行

var jobId = BackgroundJob.Enqueue(x => 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< IClientService, ClientService>();

不確定這是否能回答您的問題

該線程中的所有答案都是錯誤的/不完整的/過時的。 這是 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)
    {

    }
}

目前,Hangfire 與 Asp.Net Core 深度集成。 安裝Hangfire.AspNetCore以自動設置儀表板和 DI 集成。 然后,您只需像往常一樣使用 ASP.NET 核心定義您的依賴項。

如果您嘗試使用 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")));

我必須在主函數中啟動 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>();
}

實際上,有一種基於依賴注入的作業注冊的簡單方法。 您只需要在 Startup 中使用以下代碼:

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

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

但是,我個人想要一個工作自我注冊,包括按需工作(從未執行的重復工作,除非通過hangfire儀表板上的手動觸發),這比那個要復雜一些。 我(例如)面臨工作服務激活的問題,這就是為什么我決定分享我的大部分實現代碼。

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

使用上面的代碼,我能夠創建具有完全依賴注入支持的hangfire作業服務。 希望它可以幫助某人。

使用以下代碼進行 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();
            }
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM