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
中。尤其是在多个应用中,使用相同的模式时,这种方式会很麻烦。
也许,我们可以采用向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`和中间件管道。
现在的流程,类似于下面这个微软官方的图:
(全文完)
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 背景 当我们做项目 ...
- windows server 2008 r2 托管 asp.net core 程序无法启动
windows server 2008 r2 托管 asp.net core 程序无法启动时,需安装以下更新: Update for Windows Server 2008 R2 x64 Editio ...
- ASP.NET Core 2.1 使用Docker运行
1.新建一个 ASP.NET Core 2.1 项目 然后运行一下项目,确保我们刚刚建立的项目可以正常运行. 2.编写 Dockerfile 新建一个文本文件,命名为 Dockerfile FROM ...
- 在Spring Boot启动时运行代码
Spring Boot会自动为我们执行很多配置,但是迟早您将不得不做一些自定义工作. 在本文中,您将学习如何进入应用程序引导生命周期并在Spring Boot启动时执行代码 . 因此,让我们看看该框架 ...
- 在 Excel 启动时运行宏
如果您希望每当启动 Microsoft Excel 时都自动执行某些操作,可以录制或编写一个每当打开工作簿时都将运行的宏.有两个办法可以做到这一点: 录制一个宏,然后用 Auto_Open 这一名称保 ...
- 计算机启动时运行ccleaner,CCleaner中设置表详解
CCleaner是一款具备强大清理能力的系统优化和隐私保护软件.它可以清理文件夹.历史记录.注册表垃圾等,还能对电脑磁盘进行清理. 在大家使用CCleaner的过程中,是否想过更改CCleaner的设 ...
最新文章
- Stanford NLP 解读 ACL 2018 论文——用于调试 NLP 模型的语义等价对立规则
- python 函数进度条怎么_python输出结果刷新及进度条的实现操作
- 电脑端腾讯视频如何设置离线下载完成后自动关机
- php中将SimpleXMLElement Object数组转化为普通数组
- python getcwd 与dirname_Python中获取路径os.getcwd()和os.path.dirname(os.path.realpath(__file__))的区别和对比...
- Eclipse中的SVN插件
- javascript js string.Format()收集
- 4.3.8 使用模板
- Sql执行计划,优化sql必备!
- 旅游信息管理后台(SSM后台管理系统)
- js实现简易五子棋游戏
- 【数据仓库】 BI 项目管理之角色和职责
- cad图形如何导入到奥维地图_CAD图导入奥维简易操作步骤--陈浩
- wso2 mysql_WSO2
- 中国房地产总市值与GDP的比例
- 【机器学习】ROC曲线和AUC面积
- 亚马逊与独立站的区别,站外引流,私域流量
- Java利用itchat4j插件实现个人微信自动化
- Document-Level Relation Extraction with Adaptive Thresholding and Localized Context Pooling
- python输出中文加数字_Python实现阿拉伯数字加上中文数字