问题描述
我有一个 aspnetcore 服务器(为 Blazor wasm 应用程序提供服务),它有一个 Quartz 计划作业正在运行。当作业触发时,它会调用我的一个服务器控制器上的方法。
如果我在我的控制器上正常调用这个方法(例如通过 Web API 调用),它工作正常。
当我从 Quartz IJob 调用该方法时,控制器中使用的 DbContext 似乎已被释放。 我已经尝试以正常方式以及通过 IServiceProvider 将控制器注入到作业中,两者都具有相同的结果。
控制器:
public class NotificationController : ControllerBase
{
private readonly ApplicationDbContext context;
public NotificationService(ApplicationDbContext context)
{
this.context = context;
}
public async Task MyMethod()
{
await context.SaveChangesAsync(); //This is where it fails when Quartz calls it,seems context is not populated
}
}
我的工作(IServiceProvider 尝试):
public class ReminderJob : IJob
{
private readonly IServiceProvider serviceProvider;
public ReminderJob(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public async Task Execute(IJobExecutionContext jobcontext)
{
using (var scope = serviceProvider.CreateScope())
{
await scope.ServiceProvider.GetrequiredService<NotificationController>().MyMethod();
}
}
}
我的工作(DI 尝试):
public class ReminderJob : IJob
{
private readonly NotificationController notificationController;
public ReminderJob(NotificationController notificationController)
{
this.notificationController = notificationController;
}
public async Task Execute(IJobExecutionContext jobcontext)
{
await notificationController.MyMethod();
}
}
我的 Startup.cs(ConfigureServices 中的相关行):
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddControllersWithViews();
services.AddMvcCore().AddControllersAsServices();
services.AddRazorPages();
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionScopedJobFactory(); //I also tried passing options.CreateScope to this method,but no difference
q.AddJobAndTrigger<ReminderJob>(configuration);
});
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
services.AddDbContext<ApplicationDbContext>(options =>
options.UsesqlServer(configuration.GetConnectionString("DefaultConnection")));
}
VS 尝试执行 context.SaveChangesAsync() 时没有抛出异常,但是,在它没有命中后直接断点,但是当我在调试时检查上下文的详细信息时,它似乎没有被填充正确。
如何在 IJob 中使用 Controller,并确保 Controller 的依赖项不会被处理?
解决方法
如何在 IJob 中使用控制器
请勿在 Job 中使用控制器。
将所需的功能移动/提取/重构为服务抽象
//Service abstraction
public interface INotificationService {
Task MyMethod();
}
public class NotificationService : INotificationService {
private readonly ApplicationDbContext context;
public NotificationService(ApplicationDbContext context) {
this.context = context;
}
public async Task MyMethod() {
await context.SaveChangesAsync();
}
}
让作业和控制器依赖于服务来调用所需的功能。
public class NotificationController : ControllerBase {
private readonly INotificationService service;
public NotificationController (INotificationService service ) {
this.service = service ;
}
public async Task<IActionResult> MyMethod() {
await service.MyMethod();
return Ok();
}
}
public class ReminderJob : IJob {
private readonly INotificationService service;
public ReminderJob(INotificationService service) {
this.service = service;
}
public Task Execute(IJobExecutionContext jobcontext) {
return service.MyMethod();
}
}
当然还要向 DI 容器注册所有必要的服务。
//...
services.AddScoped<INotificationService,NotificationService>();
//...