这一篇是接着前一篇在写的。如果没有看过前一篇文章,建议先去看一下前一篇,这儿是传送门

一、前言

前一篇文章,我们从应用启动时异步运行任务开始,说到了必要性,也说到了几种解决方法,及各自的优缺点。最后,还提出了一个比较合理的解决方法:通过在Program.cs里加入代码,来实现IWebHost启动前运行异步任务。

实现的代码再贴一下:

public class Program
{
    public static async Task Main(string[] args)
    {
        IWebHost webHost = CreateWebHostBuilder(args).Build();

        using (var scope = webHost.Services.CreateScope())
        {
            var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();

            await myDbContext.Database.MigrateAsync();
        }

        await webHost.RunAsync();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

这个方法是有效的。但是,也会有一点不足。

从.Net Core的最简规则来说,我们不应该在Program.cs中加入其它代码。当然,我们可以把这部分代码转到一个外部类中,但最后也必须手动加入到Program.cs中。尤其是在多个应用中,使用相同的模式时,这种方式会很麻烦。

    为防止非授权转发,这儿给出本文的原文链接:https://www.cnblogs.com/tiger-wang/p/13714679.html

也许,我们可以采用向DI容器中注入启动任务?

二、向DI容器中注入启动任务

这种方式,是基于IStartupFilterIHostedService两个接口,通过这两个接口可以向依赖注入容器中注册类。

首先,我们为启动任务创建一个简单接口:

public interface IStartupTask
{
    Task ExecuteAsync(CancellationToken cancellationToken = default);
}

再建一个扩展方法,用来向DI容器注册启动任务:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddStartupTask<T>(this IServiceCollection services)
        where T : class, IStartupTask
        => services.AddTransient<IStartupTask, T>();
}

最后,再建一个扩展方法,在应用启动时,查找所有已注册的IStartupTask,按顺序执行他们,然后启动IWebHost

public static class StartupTaskWebHostExtensions
{
    public static async Task RunWithTasksAsync(this IHost webHost, CancellationToken cancellationToken = default)
    {
        var startupTasks = webHost.Services.GetServices<IStartupTask>();

        foreach (var startupTask in startupTasks)
        {
            await startupTask.ExecuteAsync(cancellationToken);
        }

        await webHost.RunAsync(cancellationToken);
    }
}

这样就齐活了。

还是用一个例子来看看这个方式的具体应用。

三、示例 - 数据迁移

实现IStartupTask其实和实现IStartupFilter很相似,可以从DI容器中注入。如果需要考虑作用域,还可以注入IServiceProvider,并手动创建作用域。

例子中,数据迁移类可以写成这样:

public class MigratorStartupFilter: IStartupTask
{
    private readonly IServiceProvider _serviceProvider;
    public MigratorStartupFilter(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task ExecuteAsync(CancellationToken cancellationToken = default)
    {
        using(var scope = _seviceProvider.CreateScope())
        {
            var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
            await myDbContext.Database.MigrateAsync();
        }
    }
}

下面,把任务注入到ConfigureServices()中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddStartupTask<MigrationStartupTask>();
}

最后,用上一节中的扩展方法RunWithTasksAsync()来替代Program.cs中的Run():

public class Program
{
    public static async Task Main(string[] args)
    {
        // await CreateWebHostBuilder(args).Build().RunAsync();
        await CreateWebHostBuilder(args).Build().RunWithTasksAsync();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

从功能上来说,跟上一篇的代码区别不大,但这样的写法,又多了一些优点:

  1. 任务代码放到了Program.cs之外。这符合微软的建议,也更容易理解;
  2. 任务放到了DI容器中,这样更容易添加额外的任务;
  3. 如果没有额外任务,这个代码和标准的Run()一样,所以这个代码可以独立成一个模板。

简单来说,使用RunWithTasksAsync()后,可以轻松地向DI容器添加额外的任务,而不需要任何其它的更改。

满意了吗?好像感觉还差一点点…

四、不够完美的地方

如果要照着完美去做,好像还差一点点。

这个一点点是在于:任务现在运行在IConfiguration和DI容器配置完成后,IStartupFilters运行和中间件管道配置完成之前。换句话说,如果任务需要依赖于IStartupFilters,那这个方案行不通。

在大多数情况下,这没什么问题。以我自己的经验来看,好像没有什么功能需要依赖于IStartupFilters。但作为一个框架类的代码,需要考虑这种情况发生的可能性。

以目前的方案来说,好像还没办法解决。

应用启动时,当调用WebHost.Run()时,是内部调用WebHost。看一下StartAsync()的简化代码:

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
    _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();

    var application = BuildApplication();

    _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
    _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
    var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
    var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
    var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);

    await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);

    _applicationLifetime?.NotifyStarted();

    await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
}

如果我们希望任务是加在BuildApplication()调用和Server.StartAsync()的调用之间,该怎么办?

这段代码能给出答案:我们需要装饰IServer。 ¨K16K 首先,我们替换IServer的实现: ¨G8G 在这段代码中,我们拦截StartAsync()调用并注入任务,然后回到内置处理。 下面是对应的扩展代码: ¨G9G 这个扩展代码做了两件事:在DI容器中注册了IStartupTask,并装饰了之前注册的IServer实例。装饰方法Decorate()我略过了,有兴趣的可以去了解一下 - 装饰模式。 Program.cs的代码和第三节的代码相同,略过。 &emsp; 我们终于做到了在应用程序完全构建完成后去执行我们的任务,包括IStartupFilters`和中间件管道。

现在的流程,类似于下面这个微软官方的图:

(全文完)


微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

ASP.NET Core 3.x启动时运行异步任务(二)的更多相关文章

  1. ASP.NET Core 3.x启动时运行异步任务(一)

    这是一个大的题目,需要用几篇文章来说清楚.这是第一篇.   一.前言 在我们的项目中,有时候我们需要在应用程序启动前执行一些一次性的逻辑.比方说:验证配置的正确性.填充缓存.或者运行数据库清理/迁移等 ...

  2. 如何在ASP.NET Core程序启动时运行异步任务(3)

    原文:Running async tasks on app startup in ASP.NET Core (Part 3) 作者:Andrew Lock 译者:Lamond Lu 之前我写了两篇有关 ...

  3. 如何在ASP.NET Core程序启动时运行异步任务(2)

    原文:Running async tasks on app startup in ASP.NET Core (Part 2) 作者:Andrew Lock 译者:Lamond Lu 在我的上一篇博客中 ...

  4. 如何在ASP.NET Core程序启动时运行异步任务(1)

    原文:Running async tasks on app startup in ASP.NET Core (Part 1) 作者:Andrew Lock 译者:Lamond Lu 背景 当我们做项目 ...

  5. 探索ASP.Net Core 3.0系列四:在ASP.NET Core 3.0的应用中启动时运行异步任务

    前言:在本文中,我将介绍ASP.NET Core 3.0 WebHost的微小更改如何使使用IHostedService在应用程序启动时更轻松地运行异步任务. 翻译 :Andrew Lock   ht ...

  6. asp.net core 系列 9 三种运行环境和IIS发布

    一.在asp.net core中使用多个环境 ASP.NET Core 配置是基于运行时环境, 使用环境变量.ASP.NET Core 在应用启动时读取环境变量ASPNETCORE_ENVIRONME ...

  7. ASP.NET Core 2.1 使用Docker运行

    重要提示,本文为 ASP.NET Core 2.1 如果你是 2.2 那么请将文中的镜像换为 microsoft/dotnet:2.2.0-aspnetcore-runtime 即可,其他操作一样 1 ...

  8. ASP.Net Core MVC6 RC2 启动过程分析[偏源码分析]

    入口程序 如果做过Web之外开发的人,应该记得这个是标准的Console或者Winform的入口.为什么会这样呢? .NET Web Development and Tools Blog ASP.NE ...

  9. C# ASP.NET Core使用HttpClient的同步和异步请求

    引用 Newtonsoft.Json // Post请求 public string PostResponse(string url,string postData,out string status ...

随机推荐

  1. 求求大厂给个Offer:List面试题

    前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y 从今天开始,我,三歪,正式开始写面试系列.我给这 ...

  2. Chrome扩展应用Postman地址(直接搜是搜不到的)

    https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop

  3. Spring Boot Jpa 多条件查询+排序+分页

    事情有点多,于是快一个月没写东西了,今天补上上次说的. JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将 ...

  4. Vue生命周期,我奶奶看了都懂了

    最近一直在学习Vue,而vue生命周期是我们不可能绕开的一个很核心的知识点,今天来简单的梳理一下大概的内容. 一.钩子函数 在一开始学习的时候,总有钩子函数这个名词冒出来,而且在vue官网文档中也频繁 ...

  5. 小白学PyTorch 动态图与静态图的浅显理解

    文章来自公众号[机器学习炼丹术],回复"炼丹"即可获得海量学习资料哦! 目录 1 动态图的初步推导 2 动态图的叶子节点 3. grad_fn 4 静态图 本章节缕一缕PyTorc ...

  6. 【转】Windows10删除文件时却提示文件不存在的解决方案

    Windows10系统使用一段时间后用户都会定期进行删除清理系统垃圾,减少系统盘的容量占用,但在删除的过程中许多用户都有可能遇到无法删除的情况,如下为删除文件时却提示文件不存在的解决方案. 1.新建一 ...

  7. MPI计算π

    MPI计算\(\pi\) 利用公式 \[\int_0^1 \frac{4}{1+x^2}dx = \pi \] #include<stdio.h> #include<mpi.h> ...

  8. C#.WinForm 拖动文件到PictrueBox(支持跨UAC拖动)

    如程序以普通方式打开,那么DragDrop DragEnter 事件是可以正常使用的.但以管理员身份运行时,这两个方法将失效. 原因是 Windows机制(用户界面特权隔离). UIPI:用户界面特权 ...

  9. 使用ClickHouse表函数将MySQL数据导入到ClickHouse

    #clickhouse-client :create database dw; :use dw; --导入数据: CREATE TABLE Orders ENGINE = MergeTree ORDE ...

  10. WPF Devexpress ChartControl CrosshairLabel显示内容居右

    源码可加Q群:580749909. 一.解决的问题 ChartControl中希望CrosshairLabel的内容据右 or 自定义 二.实现. 多个显示实例(实例:条形,线形,点等等)下的内容设置 ...