Метод повторного вызова задания Hangfire запускается с использованием существующих служб

#c# #asp.net-core-webapi #ioc-container #.net-6.0

Вопрос:

Я пытаюсь настроить Hangfire для запуска повторяющегося задания Startup.cs с помощью автономного метода. Чтобы это сработало, мне нужно захватить некоторые службы приложений, которые я уже внедрил. Но выполнение задания завершается с этой ошибкой:

 Recurring job 'Job: Dispatch Email from Queue' can't be scheduled due to an error and will be retried in 00:00:15.
System.InvalidOperationException: Recurring job can't be scheduled, see inner exception for details.
 ---> Hangfire.Common.JobLoadException: Could not load the job. See inner exception for the details.
 ---> Newtonsoft.Json.JsonSerializationException: Could not create an instance of type DA.BrrMs.Application.Interfaces.ServiceInterfaces.IBudgetReleaseRequestService. Type is an interface or abstract class and cannot be instantiated.
 

Вот что у меня есть:

 public class Startup
{
    private IConfiguration Configuration { get; }

     private RecurringJobManager _jobManager;

    public Startup(IConfiguration configuration) => Configuration = configuration;

    public void ConfigureServices(IServiceCollection services)
    {
        ...

        if (Configuration["SystemSettings:UseHangfire"] == "1")
        {
            services.AddHangfire(c => c.UseMemoryStorage());
            JobStorage.Current = new MemoryStorage();
            services.AddHangfireServer();
            GlobalConfiguration.Configuration.UseMemoryStorage();
        }

        RegisterServices(services);
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...

        var requestService = app.ApplicationServices.GetService<IBudgetReleaseRequestService>();
        var mailService    = app.ApplicationServices.GetService<IMailer>();

        _jobManager = new RecurringJobManager();
        _jobManager.AddOrUpdate("Job: Dispatch Email from Queue", () => StartupMailJob(requestService, mailService), Cron.Minutely);
    }

    private static void RegisterServices(IServiceCollection services) => DependencyContainer.RegisterServices(services);

    public static async Task StartupMailJob(IBudgetReleaseRequestService requestService, IMailer mailService)
    {
        try
        {
            var emailsToSend = await requestService.DispatchEmails();

            // Send emails
            foreach (var emailToSend in emailsToSend)
            {

            }

            // Update status of sent                
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

}

// In a project, far far away...

public static void RegisterServices(IServiceCollection services)
{
    ...
    services.AddScoped<IBudgetReleaseRequestService, BudgetReleaseRequestService>();
    ...
}
 

Как я могу удовлетворить требование Hangfire об экземпляре класса вместо интерфейса?

Ответ №1:

Вот реализация, которая, как я нашел, лучше всего сработала для меня:

1 Зарегистрируйте услугу, которую я хотел запланировать, в Startup.cs

  services.AddScoped<IMyJob, MYJob>();
 

2 Создан класс активатора, который используется для определения области выполнения задания.

 public class HangFireActivatorMyJob : IHangFireActivatorMyJob
{
    private readonly IServiceProvider _serviceProvider;

    public HangFireActivatorMyJob (IServiceProvider serviceProvider)
    {
        this._serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
    }

    public async Task Run(IJobCancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        await RunAtTimeOf(DateTime.Now);
    }

    public async Task RunAtTimeOf(DateTime now)
    {
        using IServiceScope scope = this._serviceProvider.CreateScope();
        var myJobService= scope.ServiceProvider.GetRequiredService<IMyJob>);
        await myJobService.RunSomeTaskOnJob();
    }
}
 

3 Зарегистрируйте активатор

 services.AddTransient<IHangFireActivatorMyJob, HangFireActivatorMyJob >();
 

4 Добавьте интерфейс IRecurringJobManager в метод настройки в файле Startup.cs

  public void Configure(
      IApplicationBuilder app,
      IWebHostEnvironment env,
      IRecurringJobManager recurringJobManager)
 

5 Добавьте задание в предоставленный IRecurringJobManager

 recurringJobManager.AddOrUpdate<HangFireActivatorMyJob >(nameof(HangFireActivatorMyJob ),
                job => serviceProvider.GetRequiredService<IHangFireActivatorMyJob>()
                    .Run(JobCancellationToken.Null)
                , Cron.Hourly(3), TimeZoneInfo.Utc);