无法在Hangfire作业中使用依赖项注入



上下文

我使用Hangfire(版本1.7.11(作为调度程序。但我不能在工作中使用适当的DI。

到目前为止有效的方法

考虑到SomeConcreteService有一个无参数构造函数:,我没有问题调度类似的东西

RecurringJob.AddOrUpdate<SomeConcreteService>(jobId, mc => Console.WriteLine(
$"Message from job: {mc.GetValue()}"), "1/2 * * * *");

什么不起作用

但当我尝试使用这里推荐的方法将服务注入Hangfire作业时,我会遇到一个异常:https://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html

当我尝试使用DI添加新的计划作业时,我会得到以下异常:

引发异常:System.Linq.Expressions.dll中的System.InvalidOperationException:从作用域"引用的类型为"TestHangfire.IMyContract"的变量"mc",但未定义为"">

异常发生在以下行:

RecurringJob.AddOrUpdate<IMyContract>(jobId, mc => Console.WriteLine(
$"Message from job {jobId} => {mc.GetValue()}"), "1/2 * * * *");

这个问题太琐碎了,我确信我遗漏了一些显而易见的东西。

谢谢你的帮助。

(几乎(完整的代码

服务:

public interface IMyContract
{
string GetValue();
}
public class MyContractImplementation : IMyContract
{
public string _label;
public MyContractImplementation(string label)
{
_label = label;
}
public string GetValue() => $"{_label}:{Guid.NewGuid()}";
}

2种激活剂:

public class ContainerJobActivator : JobActivator
{
private IServiceProvider _container;
public ContainerJobActivator(IServiceProvider serviceProvider) =>
_container = serviceProvider;
public override object ActivateJob(Type type) => _container.GetService(type);
}
public class ScopedContainerJobActivator : JobActivator
{
readonly IServiceScopeFactory _serviceScopeFactory;
public ScopedContainerJobActivator(IServiceProvider serviceProvider)
{
_serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
}
public override JobActivatorScope BeginScope(JobActivatorContext context) =>
new ServiceJobActivatorScope(_serviceScopeFactory.CreateScope());
private class ServiceJobActivatorScope : JobActivatorScope
{
readonly IServiceScope _serviceScope;
public ServiceJobActivatorScope(IServiceScope serviceScope) =>
_serviceScope = serviceScope;
public override object Resolve(Type type) =>
_serviceScope.ServiceProvider.GetService(type);
}
}

启动:

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage("connection string", new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
}));
services.AddHangfireServer();
services.BuildServiceProvider();
services.AddScoped<IMyContract>(i => new MyContractImplementation("blabla"));
// doesn't work either
// services.AddSingleton<IMyContract>(i => new MyContractImplementation("blabla"));
// doesn't work either
// services.AddTransient<IMyContract>(i => new MyContractImplementation("blabla"));
}
public void Configure(
IApplicationBuilder app, 
IWebHostEnvironment env,
IServiceProvider serviceProvider)
{
// Just to ensure the service is correctly injected...
Console.WriteLine(serviceProvider.GetService<IMyContract>().GetValue());
// I face the problem for both activators: ScopedContainerJobActivator or ContainerJobActivator
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(serviceProvider));
// GlobalConfiguration.Configuration.UseActivator(new ScopedContainerJobActivator(serviceProvider));
app.UseRouting();
app.UseHangfireDashboard();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(
JsonSerializer.Serialize(
Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs()
.Select(i => new { i.Id, i.CreatedAt, i.Cron }).ToList()));
});
endpoints.MapGet("/add", async context =>
{
var manager = new RecurringJobManager();
var jobId = $"{Guid.NewGuid()}";
// I GET AN EXCEPTION HERE: 
// Exception thrown: 'System.InvalidOperationException' in System.Linq.Expressions.dll: 'variable 'mc' of type 'TestHangfire.IMyContract' referenced from scope '', but it is not defined'
manager.AddOrUpdate<IMyContract>(jobId, mc => Console.WriteLine(
$"Message from job {jobId} => {mc.GetValue()}"), "1/2 * * * *");
// doesn't work either: it's normal, it is just a wrapper of what is above
// RecurringJob.AddOrUpdate<IMyContract>(jobId, mc => Console.WriteLine($"Message from job {jobId} => {mc.GetValue()}"), "1/2 * * * *");
await context.Response.WriteAsync($"Schedule added: {jobId}");
});
});
}
}

我发现了这个问题。

实际上,似乎是表达式引起了问题,而且添加重复作业的另一种方法是传输类型和方法信息,在我看来,问题是由一个过于进化的表达式引起的。所以我改变了方法,通过给我一个参数,我的服务有了一个方法来完成整个工作。

以下是有效的新代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Hangfire;
using Hangfire.SqlServer;
using Hangfire.Storage;
using System.Text.Json;
namespace TestHangfire
{
#region Service
public interface IMyContract
{
void MakeAction(string someText);
}
public class MyContractImplementation : IMyContract
{
public string _label;
public MyContractImplementation(string label)
{
_label = label;
}
public void MakeAction(string someText) => Console.WriteLine($"{_label}:{someText}");
}
#endregion
#region 2 kinds of activators
public class ContainerJobActivator : JobActivator
{
private IServiceProvider _container;
public ContainerJobActivator(IServiceProvider serviceProvider)
{
_container = serviceProvider;
}
public override object ActivateJob(Type type)
{
return _container.GetService(type);
}
}
#endregion
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage("Server=localhost,1433;Database=HangfireTest;user=sa;password=xxxxxx;MultipleActiveResultSets=True", new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
}));
services.AddHangfireServer();
services.BuildServiceProvider();
services.AddTransient<IMyContract>(i => new MyContractImplementation("blabla"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(serviceProvider));
app.UseRouting();
app.UseHangfireDashboard();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(JsonSerializer.Serialize(Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs()
.Select(i => new { i.Id, i.CreatedAt, i.Cron }).ToList()));
});
endpoints.MapGet("/add", async context =>
{
var manager = new RecurringJobManager();
var jobId = $"{Guid.NewGuid()}";
manager.AddOrUpdate<IMyContract>(jobId, (IMyContract mc) => mc.MakeAction(jobId), "1/2 * * * *");
await context.Response.WriteAsync($"Schedule added: {jobId}");
});
});
}
}
}

最新更新