【发布时间】:2017-06-09 08:36:12
【问题描述】:
如何在 Hangfire 中使用 .NET Core 的默认依赖注入?
我是 Hangfire 的新手,正在寻找一个适用于 ASP.NET Core 的示例。
【问题讨论】:
标签: c# asp.net-core hangfire
如何在 Hangfire 中使用 .NET Core 的默认依赖注入?
我是 Hangfire 的新手,正在寻找一个适用于 ASP.NET Core 的示例。
【问题讨论】:
标签: c# asp.net-core hangfire
在 GitHub 上查看完整示例 https://github.com/gonzigonz/HangfireCore-Example。
现场直播http://hangfirecore.azurewebsites.net/
确保您拥有核心版 Hangfire:dotnet add package Hangfire.AspNetCore
通过定义 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);
}
}
接下来将hangfire注册为Startup.ConfigureServices方法中的服务:
services.AddHangfire(opt =>
opt.UseSqlServerStorage("Your Hangfire Connection string"));
在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();
}
当您将作业排入队列时,请使用通常是您的接口的注册类型。除非您以这种方式注册,否则不要使用具体类型。您必须使用在您的 IoC 中注册的类型,否则 Hangfire 将找不到它。 例如假设您注册了以下服务:
services.AddScoped<DbManager>();
services.AddScoped<IMyService, MyService>();
然后您可以将 DbManager 加入到该类的实例化版本中:
BackgroundJob.Enqueue(() => dbManager.DoSomething());
但是你不能对MyService 做同样的事情。使用实例化版本排队会失败,因为 DI 会失败,因为只有接口被注册。在这种情况下,你会像这样排队:
BackgroundJob.Enqueue<IMyService>( ms => ms.DoSomething());
【讨论】:
ms => ms.DoSomething() 的情况下,表达式必须调用传入参数的方法。你不能做ms => Foo.StaticMethod(ms);在这种情况下,Hangfire 给出了一个神秘的例外。
BeginScope 功能是有原因的。在这里使用此实现的唯一方法是将其传递给根服务提供者。这意味着它永远不会被释放,这意味着每次创建作业时,都会泄漏所有创建的依赖项。
CreateScope 方法来解决这个问题。如果您在控制台应用程序中运行,您可能会手动创建一个根范围 - 将其传递给您的自定义激活器,然后实现 BeginScope 等等。很简单。既然 Hangfire 已经有一个正确的实现,你也许可以简单地实例化那个。
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 核心依赖注入。
您可以使用包含要执行的作业的服务,可以像这样执行
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>();
不确定这是否回答了您的问题
【讨论】:
JobActivator 的情况下被开箱即用地解决
此线程中的所有答案都是错误/不完整/过时的。这是 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<ISomeDependency , SomeDependency >();,那么该依赖项的范围是什么?
目前,Hangfire 与 Asp.Net Core 深度集成。自动安装Hangfire.AspNetCore 到set up the dashboard 和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>();
}
【讨论】:
使用以下代码进行 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();
}
}
}
【讨论】: