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

一、前言

前一篇文章,我们从应用启动时异步运行任务开始,说到了必要性,也说到了几种解决方法,及各自的优缺点。最后,还提出了一个比较合理的解决方法:通过在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中。尤其是在多个应用中,使用相同的模式时,这种方式会很麻烦。

也许,我们可以采用向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`和中间件管道。

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

(全文完)

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. windows server 2008 r2 托管 asp.net core 程序无法启动

    windows server 2008 r2 托管 asp.net core 程序无法启动时,需安装以下更新: Update for Windows Server 2008 R2 x64 Editio ...

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

    1.新建一个 ASP.NET Core 2.1 项目 然后运行一下项目,确保我们刚刚建立的项目可以正常运行. 2.编写 Dockerfile 新建一个文本文件,命名为 Dockerfile FROM ...

  7. 在Spring Boot启动时运行代码

    Spring Boot会自动为我们执行很多配置,但是迟早您将不得不做一些自定义工作. 在本文中,您将学习如何进入应用程序引导生命周期并在Spring Boot启动时执行代码 . 因此,让我们看看该框架 ...

  8. 在 Excel 启动时运行宏

    如果您希望每当启动 Microsoft Excel 时都自动执行某些操作,可以录制或编写一个每当打开工作簿时都将运行的宏.有两个办法可以做到这一点: 录制一个宏,然后用 Auto_Open 这一名称保 ...

  9. 计算机启动时运行ccleaner,CCleaner中设置表详解

    CCleaner是一款具备强大清理能力的系统优化和隐私保护软件.它可以清理文件夹.历史记录.注册表垃圾等,还能对电脑磁盘进行清理. 在大家使用CCleaner的过程中,是否想过更改CCleaner的设 ...

最新文章

  1. Stanford NLP 解读 ACL 2018 论文——用于调试 NLP 模型的语义等价对立规则
  2. python 函数进度条怎么_python输出结果刷新及进度条的实现操作
  3. 电脑端腾讯视频如何设置离线下载完成后自动关机
  4. php中将SimpleXMLElement Object数组转化为普通数组
  5. python getcwd 与dirname_Python中获取路径os.getcwd()和os.path.dirname(os.path.realpath(__file__))的区别和对比...
  6. Eclipse中的SVN插件
  7. javascript js string.Format()收集
  8. 4.3.8 使用模板
  9. Sql执行计划,优化sql必备!
  10. 旅游信息管理后台(SSM后台管理系统)
  11. js实现简易五子棋游戏
  12. 【数据仓库】 BI 项目管理之角色和职责
  13. cad图形如何导入到奥维地图_CAD图导入奥维简易操作步骤--陈浩
  14. wso2 mysql_WSO2
  15. 中国房地产总市值与GDP的比例
  16. 【机器学习】ROC曲线和AUC面积
  17. 亚马逊与独立站的区别,站外引流,私域流量
  18. Java利用itchat4j插件实现个人微信自动化
  19. Document-Level Relation Extraction with Adaptive Thresholding and Localized Context Pooling
  20. python输出中文加数字_Python实现阿拉伯数字加上中文数字

热门文章

  1. Httpclient发送json请求
  2. R语言-异常数据处理2
  3. ArrayList 的实现原理
  4. WindowsPhone8可缩放图片控件的实现
  5. PHP开发学习-Apache+PHP+MySQL环境搭建
  6. 装了卡巴后VS 2003不能启动调试错误的解决方案
  7. 如何停止Internet Explorer 11的建议站点?
  8. 12C RAC for ASM添加磁盘步骤
  9. 性能优化8--内存泄露
  10. MySQL-5.5.33主从复制