0

我正在开发一个 ASP.NET Core MVC Web 应用程序,其中我有这两个任务应该作为后台服务运行:

  1. 如果 EndOfSubscription 日期为 == DateTime.Now,则将用户状态设置为“已过期”
  2. 在 EndOfSubscription 日期前 1 个月向该用户发送提醒电子邮件

经过搜索,我发现我可以使用 service worker 来实现这一点。但是我完全困惑如何在需要访问我的模型和数据库的现有 ASP.NET Core MVC Web 应用程序中使用这个服务工作者。

我应该将这些任务隔离在单独的服务工作者项目中吗?但在这种情况下,我应该为两个项目共享同一个数据库吗?

在这种情况下,有人可以指导我采取主要步骤吗?

先感谢您。

4

1 回答 1

2

Service Worker还是Worker 服务

  • Service Worker 是一种在浏览器中运行后台任务的方法,如果您想在服务器上执行某些操作,则绝对不适合。
  • Worker 服务本质上是一个模板,其中包含在控制台应用程序中运行 BackgroundService/IHostedService 以及(可选地,通过扩展)作为 Linux 守护程序或 Windows 服务所需的(少数)调用。您不需要该模板来创建和运行 BackgroundService。

ASP.NET Core 中托管服务的后台任务教程展示了如何创建和使用 BackgroundService,但有点……过度设计。这篇文章试图同时展示太多的东西,最终错过了一些重要的东西。

一个更好的介绍是史蒂夫戈登的什么是工人服务?.

后台服务

创建后台服务所需的只是一个实现IHostedService接口的类。与实现所有接口方法不同,从BackgroundService基类继承并仅覆盖该方法更容易ExecuteAsync

这篇文章的例子表明这个方法不需要太花哨:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);
        }
    }
}

这只是一个有延迟的循环。这将一直运行,直到 Web 应用程序终止并发出stoppingToken. 该服务将由 DI 容器创建,因此它可以像ILogger或任何其他单例服务一样具有服务依赖项。

注册服务

后台服务需要注册为 中的服务ConfigureServices,注册其他服务的方式相同。如果您有一个控制台应用程序,您可以在主机ConfigureServices调用中对其进行配置。如果您有 Web 应用程序,则需要在以下位置注册Startup.ConfigureServices

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddDbContext<OrdersContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        
        ...

        //Register the service
        services.AddHostedService<Worker>();

        services.AddRazorPages();
    }

这注册Worker为可以由 DI 容器构建的服务,并将其添加到托管服务列表中,该服务将.Run()在 Web 应用程序的调用中启动一次Main

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

使用 DbContext 和其他作用域服务

添加 DbContext 作为依赖项比较棘手,因为DbContext它是一个作用域服务。我们不能只注入一个DbContext实例并将其存储在一个字段中 - DbContext 旨在用作工作单元,它收集为单个场景所做的所有更改并将所有更改提交到数据库或丢弃它们。它应该在using块内使用。如果我们处理我们注入的单个DbContext实例,我们从哪里得到一个新实例?

为了解决这个问题,我们必须注入 DI 服务,IServiceProvider显式地创建一个作用域并从这个作用域中获取我们的 DbContext:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    private readonly IServiceProvider _services;

    //Inject IServiceProvider
    public Worker(IServiceProvider services, ILogger<Worker> logger)
    {
        _logger = logger;
        _services=services;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            //Create the scope
            using (var scope = _services.CreateScope())
            {
                //Create OrdersContext in the scope
                var ctx = scope.ServiceProvider.GetRequiredService<OrdersContext>();
                
                var latestOrders = await ctx.Orders
                                            .Where(o=>o.Created>=DateTime.Today)
                                            .ToListAsync();
                //Make some changes
                if (allOK)
                {
                    await ctx.SaveChangesAsync();
                }
            }
            //OrdersContext will be disposed when exiting the scope
            ...
        }
    }
}

OrdersContext在范围退出时释放,并且任何未保存的更改都将被丢弃。

没有说整个代码需要在里面ExecuteAsync。一旦代码开始变得太长,我们可以轻松地将重要代码提取到单独的方法中:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

        using (var scope = _services.CreateScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<OrdersContext>();
            await DoWorkAsync(ctx,stoppingToken);
        }

        await Task.Delay(1000, stoppingToken);
    }
}

private async Task DoWorkAsync(OrdersContext ctx,CancellationToken stoppingToken)
{
    var latestOrders = await ctx.Orders
                                .Where(o=>o.Created>=DateTime.Today)
                                .ToListAsync();
    //Make some changes
    if (allOK)
    {
        await ctx.SaveChangesAsync();
    }
}
于 2020-10-06T07:05:13.747 回答