【问题标题】:Implementing service worker in existing ASP.NET Core MVC application在现有 ASP.NET Core MVC 应用程序中实现服务工作者
【发布时间】:2020-10-06 15:05:13
【问题描述】:

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

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

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

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

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

提前谢谢你。

【问题讨论】:

  • 只需添加一个 BackgroundService 作为文档显示。 “服务工作者”没有什么特别的,只是一个电话services.AddHostedService
  • Service Workers 是 Service Worker API 的一部分,它是 JavaScript。我不知道你为什么想要一个单独的项目。您可能希望添加一些 API 端点供工作人员调用。我建议找到一些有关创建它们的教程。它们确实在浏览器中运行,因此它可能不是您问题的最佳解决方案。
  • @HereticMonkey 问题与 Javascript 无关。 OP使用了错误的标签
  • 如果您创建一个新的“服务工作者”应用程序,您会看到它只是向AddHostedService 注册一些服务。这些服务将在 GenericHost/WebHost 开始运行时开始运行
  • 您不应该将相等性与DateTime.Now 进行比较。 DateTime 精确到票证(0.0001 毫秒),因此该行代码以完全正确的毫秒运行的机会非常小。最好做类似Set the user status as "Expired" if EndOfSubscription date is <= DateTime.Now

标签: asp.net-core asp.net-core-mvc asp.net-core-hosted-services


【解决方案1】:

Service workerWorker service?

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

教程Background tasks with hosted services in ASP.NET Core 展示了如何创建和使用BackgroundService,但有点……过度设计。这篇文章试图同时展示太多东西,结果却遗漏了一些重要的东西。

更好的介绍是 Steve Gordon 的 What are Worker Services?

后台服务

创建后台服务所需的只是一个实现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 容器构造的服务将其添加到托管服务列表中,一旦在Web 应用程序的@987654337 中调用.Run() @:

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();
    }
}

【讨论】:

    猜你喜欢
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-10
    • 1970-01-01
    • 2022-07-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多