ASP.NET Core 3.x启动时运行异步任务(二)
这一篇是接着前一篇在写的。如果没有看过前一篇文章,建议先去看一下前一篇,这儿是传送门
一、前言
前一篇文章,我们从应用启动时异步运行任务开始,说到了必要性,也说到了几种解决方法,及各自的优缺点。最后,还提出了一个比较合理的解决方法:通过在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容器中注入启动任务
这种方式,是基于IStartupFilter和IHostedService两个接口,通过这两个接口可以向依赖注入容器中注册类。
首先,我们为启动任务创建一个简单接口:
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>();
}
从功能上来说,跟上一篇的代码区别不大,但这样的写法,又多了一些优点:
- 任务代码放到了
Program.cs之外。这符合微软的建议,也更容易理解; - 任务放到了DI容器中,这样更容易添加额外的任务;
- 如果没有额外任务,这个代码和标准的
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的代码和第三节的代码相同,略过。   我们终于做到了在应用程序完全构建完成后去执行我们的任务,包括IStartupFilters`和中间件管道。
现在的流程,类似于下面这个微软官方的图:

(全文完)
![]() |
微信公众号:老王Plus 扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送 本文版权归作者所有,转载请保留此声明和原文链接 |
ASP.NET Core 3.x启动时运行异步任务(二)的更多相关文章
- ASP.NET Core 3.x启动时运行异步任务(一)
这是一个大的题目,需要用几篇文章来说清楚.这是第一篇. 一.前言 在我们的项目中,有时候我们需要在应用程序启动前执行一些一次性的逻辑.比方说:验证配置的正确性.填充缓存.或者运行数据库清理/迁移等 ...
- 如何在ASP.NET Core程序启动时运行异步任务(3)
原文:Running async tasks on app startup in ASP.NET Core (Part 3) 作者:Andrew Lock 译者:Lamond Lu 之前我写了两篇有关 ...
- 如何在ASP.NET Core程序启动时运行异步任务(2)
原文:Running async tasks on app startup in ASP.NET Core (Part 2) 作者:Andrew Lock 译者:Lamond Lu 在我的上一篇博客中 ...
- 如何在ASP.NET Core程序启动时运行异步任务(1)
原文:Running async tasks on app startup in ASP.NET Core (Part 1) 作者:Andrew Lock 译者:Lamond Lu 背景 当我们做项目 ...
- 探索ASP.Net Core 3.0系列四:在ASP.NET Core 3.0的应用中启动时运行异步任务
前言:在本文中,我将介绍ASP.NET Core 3.0 WebHost的微小更改如何使使用IHostedService在应用程序启动时更轻松地运行异步任务. 翻译 :Andrew Lock ht ...
- asp.net core 系列 9 三种运行环境和IIS发布
一.在asp.net core中使用多个环境 ASP.NET Core 配置是基于运行时环境, 使用环境变量.ASP.NET Core 在应用启动时读取环境变量ASPNETCORE_ENVIRONME ...
- ASP.NET Core 2.1 使用Docker运行
重要提示,本文为 ASP.NET Core 2.1 如果你是 2.2 那么请将文中的镜像换为 microsoft/dotnet:2.2.0-aspnetcore-runtime 即可,其他操作一样 1 ...
- ASP.Net Core MVC6 RC2 启动过程分析[偏源码分析]
入口程序 如果做过Web之外开发的人,应该记得这个是标准的Console或者Winform的入口.为什么会这样呢? .NET Web Development and Tools Blog ASP.NE ...
- C# ASP.NET Core使用HttpClient的同步和异步请求
引用 Newtonsoft.Json // Post请求 public string PostResponse(string url,string postData,out string status ...
随机推荐
- Micro LED巨量转移技术研究进展
近年来,Micro LED因其功耗低.响应快.寿命长.光效率高等特点,被视为继LCD.OLED之后的新一代显示面板技术.Micro LED的英文全名是Micro Light Emitting Diod ...
- Vue源码分析之数据驱动
响应式特点 数据响应式 修改数据时,视图自动更新,避免繁琐Dom操作,提高开发效率 双向绑定 数据改变,视图随之改变.视图改变,数据随之改变 数据驱动 开发时仅需要关注数据本身,不需要关心数据如何渲染 ...
- eclipse git提交项目以及down项目大致步骤
down git项目步骤: 复制了url之后,import projects项目选中from git ,会出现如下截图,信息会自动填充 填好信息一直Next即可 完成之后,打开“Git Reposit ...
- 第二篇 Scrum冲刺博客
一.会议图片 二.项目进展 成员 完成情况 今日任务 冯荣新 搜索框,首页轮播图,分类导航 商品列表,商品详情轮播图 陈泽佳 背景展示,选择并显示图片 历史足迹,静态页面 徐伟浩 登录权限获取 商品信 ...
- [转]camera教程
camera教程 Lens一般由几片透镜组成透镜结构,按材质可分为塑胶透镜(plastic)或玻璃透镜(glass),玻璃镜片比树脂镜片贵.塑胶透镜其实是树脂镜片,透光率和感光性等光学指标比不上镀膜镜 ...
- 玩转Spring——Spring IOC/DI
什么是IOCioc :Inversion of Control,即控制反转. 它不是一种技术,而是一种设计思想,即java程序中获取对象的方式发生反转,由最初的new方式创建,转变成由第三方框架创建. ...
- asyncio系列之Lock实现
import types import select import time import socket import functools import collections class Futur ...
- 蒲公英 · JELLY技术周刊 Vol.19 从零开始的 Cloud IDE 开发
蒲公英 · JELLY技术周刊 Vol.19 你是否也会有想法去开发一个自己的 IDE 却苦于时间和精力不足,完成 Desktop IDE 却又被 Cloud IDE 的概念追在身后难以入睡,这样的两 ...
- 看视频常见的 720p、1080p、4k,这些分辨率到底包含了什么
从早期的420p,到后来的720p,到现在的非1080p不看.视频的清晰度飞快提升,但是在看到色彩越来越丰富清晰度越来越高的画面时,你有关注过他们的到底是怎么做到的么?我们一起来了解一下吧. 想必大家 ...
- 时间转换c#
简要 本篇文章主要介绍基于.net(c#)封装的时间帮助类.主要功能是将时间戳与DateTime类型相互转换. 1.其中包括将毫秒级时间戳(13位).秒级时间戳(10位)转换为DateTime. 2. ...
