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容器中注入启动任务

这种方式,是基于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`和中间件管道。

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

(全文完)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307613.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode583. 两个字符串的删除操作

一:题目 二:上码 class Solution { public:/**思路:题目给的是让求最值,那么首先就会想到的是动态规划,我们想得到答案的结果其实有多个的&#xff0c;但是我们是取最小的步数动态规划 五步走:1>:确定dp数组以及下标的含义dp[i][j]表示的是 以下标i-1结尾的字符串word1,和…

C# 中居然也有切片语法糖,太厉害了

一&#xff1a;背景 1. 讲故事昨天在 github 上准备找找 C# 9 又有哪些新语法糖可以试用&#xff0c;不觉在一个文档上看到一个很奇怪的写法: foreach (var item in myArray[0..5]) 哈哈&#xff0c;熟悉又陌生&#xff0c;玩过python的朋友对这个 [0..5] 太熟悉不过了&#x…

跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建

前言中间件(Middleware)对于Asp.NetCore项目来说&#xff0c;不能说重要&#xff0c;而是不能缺少&#xff0c;因为Asp.NetCore的请求管道就是通过一系列的中间件组成的&#xff1b;在服务器接收到请求之后&#xff0c;请求会经过请求管道进行相关的过滤或处理&#xff1b;正文…

leetcode647. 回文子串

一&#xff1a;题目 二&#xff1a;上码 class Solution { public:/**思路:动态规划五步走1>:确定dp数组以及下标的含义dp[i][j] 表示的是在[i,j]范围内的字串 是否是 回文子串&#xff0c;如果是的话那么dp[i][j] true2>确定dp数组的状态转移方程那么就有两种情况 s[i…

leetcode516. 最长回文子序列

一:题目 二:上码 class Solution { public:/**思路:1.分析题意 这个是让我们求最值,那么首先想到动态规划2.动态规划1>:确定dp数组以及下标的含义dp[i][j] 表示字符串在[i,j]范围内的最长回文子序列2>:确定dp数组的状态递推公式那么就是s[i] 与 s[j] 相等 不相等两种情况…

C#刷剑指Offer | 二叉搜索树的后序遍历序列

【C#刷题】| 作者 / Edison Zhou这是EdisonTalk的第289篇原创内容我们来用之前学到的数据结构知识来刷《剑指Offer》的一些核心题目&#xff08;精选了其中30道题目&#xff09;&#xff0c;希望对你有帮助&#xff01;本文题目为&#xff1a;二叉搜索树的后序遍历序列。1题目介…

leetcode739. 每日温度

一:题目 二:上码 // class Solution { // public: // vector<int> dailyTemperatures(vector<int>& temperatures) { // vector<int> ans(temperatures.size(),0);// for (int i 0; i < temperatures.size(); i) {// …

Leetcode周赛复盘——第 71 场力扣双周赛与第 279 场力扣周赛

双周赛&#xff1a; 5984. 拆分数位后四位数字的最小和 class Solution:def minimumSum(self, num: int) -> int:a, b, c, d sorted(list(map(int, str(num))))return 10 * (a b) c dstr(num)得到字符串序列&#xff0c;然后用map函数对序列的每个字符转换为数字&…

使用SWAGGER和ASP.NET CORE设置可选路由参数

使用SWAGGER和ASP.NET CORE设置可选路由参数根据OpenAPI 3.0&#xff0c;这是不可能的。但是&#xff0c;如果您真的希望成为现实呢&#xff1f;您是否必须解决并允许您的Swagger文档出错&#xff1f;我在这里向您展示如何使用Swagger和ASP.NET Core设置可选的路由参数。等等&a…

在数组中找重复数、只出现一次的数或丢失数的题目(Leetcode题解-Python语言)

在一维数组中的考察中&#xff0c;最常见的就是找出数组中的重复数、只出现一次的数或者丢失&#xff08;消失&#xff09;数等等。 一般来说&#xff0c;首先想到的就是用哈希表&#xff08;集合&#xff09;来记录出现过的数&#xff0c;基本所有的题都可以用集合来做&#…

Confluent官博:Kafka最牛队列,性能15倍于RabbitMQ!

“容器、Kubernetes、DevOps、微服务、云原生&#xff0c;这些技术名词的频繁出现&#xff0c;预兆着新的互联网技术时代的到来&#xff0c;大数据高并发将不再遥远&#xff0c;而是大部分项目都必须面对的&#xff0c;消息队列则是核心利器&#xff01;成熟的消息队列产品很多…

leetcode503. 下一个更大元素 II

一:题目 二:上码 class Solution { public:/**思路: 1.将两个nums拼接到一块这里拼接到一块,当我们最后的元素找不到比其大的时候 就会开始从头开始这样的话就可以继续进行 入栈 或者出栈的操作入栈就是比我栈顶小的元素&#xff0c;出栈的话 那就是 找到了比其大的元素了…

跟我一起学.NetCore之中间件(Middleware)应用和自定义

前言Asp.NetCore中的请求管道是通过一系列的中间件组成的&#xff0c;使得请求会根据需求进行对应的过滤和加工处理。在平时开发中会时常引用别人定义好的中间件&#xff0c;只需简单进行app.Usexxx就能完成中间件的注册&#xff0c;但是对于一些定制化需求还得自己进行处理和封…

leetcode42. 接雨水

一:题目 二:上码 // class Solution { // public: // /**超时 // 思路: // 1.我们按列来计算 这就是表明的是 我们求取接雨水 向上的高度就是雨水量 // 但是这里的话我们的需要对雨水的高度 来进行判定 // 2.那么如何判定…

Magicodes.IE之导入导出筛选器

总体设计Magicodes.IE是一个导入导出通用库&#xff0c;支持Dto导入导出以及动态导出&#xff0c;支持Excel、Word、Pdf、Csv和Html。在本篇教程&#xff0c;笔者将讲述如何使用Magicodes.IE的导入导出筛选器。在开始之前&#xff0c;我们需要先了解Magicodes.IE目前支持的筛选…

谈了千百遍的缓存数据的一致性问题

“灵魂拷问保证缓存和数据库的一致性很简单吗&#xff1f;有哪些方式能保证缓存和数据库的一致性呢&#xff1f;如果发生了缓存和数据库数据不一致的情况怎么办呢&#xff1f;在上篇文章我们介绍了缓存的定义分类以及优缺点等&#xff0c;如果还没看的同学可以移步这里听说你会…

BS作业 基于springboot + Thymeleaf +mybatis 实现的书城管理系统

一:项目背景 项目描述 一个基本功能较为完整的后台管理项目。项目主要功能有&#xff1a;登录验证&#xff0c;登录功能还加入了随机验证码的验证&#xff1b; 用户注册&#xff0c;注册中密码基于srping 安全框架提供的加密(自动加盐)的密码储存方式&#xff0c;对注册重名进…

Istio Pilot 源码分析(二)

张海东&#xff0c; ‍多点生活&#xff08;成都&#xff09;云原生开发工程师。本篇主要介绍 Pilot 源码中的 ServiceEntryStore 及其推送 xDS 的流程。本文为 Istio Pilot 源码分析系列的第二篇文章。Istio Pilot 源码分析&#xff08;一&#xff09;了解了 Pilot 源码的基本…

Pytorch中的 torch.Tensor() 和 torch.tensor() 的区别

直接在搜索引擎里进行搜索&#xff0c;可以看到官方文档中两者对应的页面&#xff1a; 分别点击进去&#xff0c;第一个链接解释了什么是 torch.Tensor&#xff1a; torch.Tensor 是一个包含单一数据类型元素的多维矩阵&#xff08;数组&#xff09;。 正因为 torch.Tensor 只包…