我正在尝试添加Hangfire来为管理员发送带有每周摘要的电子邮件。
我有后台调度程序
{
public class BackgroundScheduler : IBackgroundScheduler
{
private readonly IMediator _mediator;
private readonly IServiceScopeFactory _serviceScopeFactory;
public BackgroundScheduler(IRecurringJobManager recurringJobManager,IMediator mediator, IServiceScopeFactory serviceScopeFactory)
{
_mediator = mediator;
_serviceScopeFactory = serviceScopeFactory;
}
public void SendWeeklyDigestEmail()
{
_mediator.Send(new WeeklyDigestEmailCommand());
}
public void ScheduleRecurringTasks()
{
RecurringJob.AddOrUpdate(() => SendWeeklyDigestEmail(), "0 7 * * MON");
}
}
}
,它将命令发送给命令处理程序。该命令为空,因此我将直接向您展示命令处理程序:
public class WeeklyDigestEmailCommandHandler : IRequestHandler<WeeklyDigestEmailCommand>
{
private readonly IBackofficeDbContext _context;
private readonly IEmailNotification _emailNotification;
private readonly ITemplateProvider _templateProvider;
private readonly FrontendConfiguration _frontendConfiguration;
public WeeklyDigestEmailCommandHandler(IBackofficeDbContext context, IEmailNotification emailNotification, ITemplateProvider templateProvider,
IOptions<FrontendConfiguration> frontendConfiguration)
{
this._context = context;
_emailNotification = emailNotification;
_templateProvider = templateProvider;
_frontendConfiguration = frontendConfiguration.Value;
}
public async Task<Unit> Handle(WeeklyDigestEmailCommand command,
CancellationToken cancellationToken)
{
var usersByTenant = await _context.Users
.Include(x => x.Role)
.Where(x => x.Role.Name == Constants.Roles.TenantAdministrator)
.ToListAsync(cancellationToken);
Console.WriteLine(usersByTenant.Count);
var usersByTenantGrouped = usersByTenant
.GroupBy(x => x.TenantId);
var subject = "Weekly digest email";
var template = _templateProvider.GetTemplate(EmailTemplateType.WeeklyDigestEmailTemplate);
foreach (var tenantGroup in usersByTenantGrouped)
{
var tenantAdmins = tenantGroup.ToList();
var tenantAdminsIds = tenantAdmins.Select(ta => ta.Id).ToList();
var newCustomersCount = await _context.Customers
.Where(c => c.Created >= DateTime.UtcNow.AddDays(-7) && tenantAdminsIds.Contains(c.CreatedBy))
.CountAsync(cancellationToken);
var newProjectsCount = await _context.Projects
.Where(p => p.Created >= DateTime.UtcNow.AddDays(-7) && tenantAdminsIds.Contains(p.CreatedBy))
.CountAsync(cancellationToken);
var completedProjectsCount = await _context.Projects
.Where(p => p.LastModified >= DateTime.UtcNow.AddDays(-7) || p.Created >= DateTime.UtcNow.AddDays(-7) && p.Status == ProjectStatus.Completed && tenantAdminsIds.Contains(p.CreatedBy))
.CountAsync(cancellationToken);
var completedProjectTasksCount = await _context.ProjectTasks
.Where(t => t.LastModified >= DateTime.UtcNow.AddDays(-7) || t.Created >= DateTime.UtcNow.AddDays(-7) && t.Completed && tenantAdminsIds.Contains(t.CreatedBy))
.CountAsync(cancellationToken);
var overdueTasksCount = await _context.ProjectTasks
.Where(t => DateTime.UtcNow >= t.DueDate && tenantAdminsIds.Contains(t.CreatedBy))
.CountAsync(cancellationToken);
Console.WriteLine("Weekly digest email.");
foreach (var tenantAdmin in tenantAdmins)
{
var body = template.FillTemplate(tenantAdmin.Name, newCustomersCount, newProjectsCount, completedProjectsCount, completedProjectTasksCount, overdueTasksCount, _frontendConfiguration.Url);
await _emailNotification.SendEmailAsync(new EmailMessage
{
To = tenantAdmin.Email,
Subject = subject,
Model = body,
}, EmailTemplateType.WeeklyDigestEmailTemplate);
}
}
return Unit.Value;
}
我用这个命令做httppost用于测试目的,当我通过控制器发送它时,它正在发送电子邮件,一切都很好。但是当我尝试通过hangfire运行它时,它在usersByTenant变量之后停止运行,因为它似乎没有找到任何租户管理员,这很奇怪,因为当我运行它时,我得到它们并且程序继续。我尝试初始化dbcontext和hangfire之后,但它仍然不工作。
Program.cs:
builder.Services.AddDbContext<BackofficeDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DbConnectionString")));
// Add Hangfire
builder.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(builder.Configuration.GetConnectionString("DbConnectionString"));
});
//Register background scheduler
builder.Services.AddScoped<IBackgroundScheduler, BackgroundScheduler>();
var serviceProvider = builder.Services.BuildServiceProvider();
var backgroundScheduler = serviceProvider.GetService<IBackgroundScheduler>();
backgroundScheduler.ScheduleRecurringTasks();
我也试图使它在一个服务范围,但我得到一个错误,它被处置:
System.ObjectDisposedException: Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
我也不能使用ContextFactory,因为我只能访问db上下文的接口,因为实现是在解决方案中的另一个项目中。我怎样才能解决这个问题?
我建议您将ScheduleRecurringTasks
的实现更改为:
public static void ScheduleRecurringTasks()
{
RecurringJob.AddOrUpdate<IBackgroundScheduler>(x => x.SendWeeklyDigestEmail(), "0 7 * * MON");
}
这将为您解决两个问题:
- 您将不再需要自己构建服务提供者,因此您可以删除以下内容并通过调用静态方法更改它:
var serviceProvider = builder.Services.BuildServiceProvider();varbackgroundScheduler =serviceProvider.GetService ()
- 由于您在DI上使用接口注册
BackgroundScheduler
,作业的激活器应该尝试使用接口找到它,您所做的第一个实现将告诉激活器使用类类型找到它。
builder.Services.AddScoped<IBackgroundScheduler,BackgroundScheduler> ()