原文:Running async tasks on app startup in ASP.NET Core (Part 2)
作者:Andrew Lock
译者:Lamond Lu
在我的上一篇博客中,我介绍了如何在ASP.NET Core应用程序启动时运行一些一次性异步任务。本篇博客将继续讨论上一篇的内容,如果你还没有读过,我建议你先读一下前一篇。
在本篇博客中,我将展示上一篇博文中提出的“在Program.cs
中手动运行异步任务”的实现方法。该实现会使用一些简单的接口和类来封装应用程序启动时的运行任务逻辑。我还会展示一个替代方法,这个替代方法是在Kestral服务器启动时,使用IServer
接口。
在应用程序启动时运行异步任务
这里我们先回顾一下上一遍博客内容,在上一篇中,我们试图寻找一种方案,允许我们在ASP.NET Core应用程序启动时执行一些异步任务。这些任务应该是在ASP.NET Core应用程序启动之前执行,但是由于这些任务可能需要读取配置或者使用服务,所以它们只能在ASP.NET Core的依赖注入容器配置完成后执行。数据库迁移,填充缓存都可以这种异步任务的使用场景。
我们在一篇文章的末尾提出了一个相对完善的解决方案,这个方案是在Program.cs
中“手动”运行任务。运行任务的时机是在IWebHostBuilder.Build()
和IWebHost.RunAsync()
之间。
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>();
}
这种实现方式是可行的,但是有点乱。这里我们将许多不应该属于Program.cs
职责的代码放在了Program.cs
中,让它看起来有点臃肿了,所以这里我们需要将数据库迁移相关的代码移到另外一个类中。
这里更麻烦的问题是,我们必须要手动调用任务。如果你在多个应用程序中使用相同的模式,那么最好能改成自动调用任务。
在依赖注入容器中注册启动任务
这里我将使用基于IStartupFilter
和IHostService
使用的模式。它们允许你在依赖注入容器中注册它们的实现类,并在应用程序启动前获取到这些接口的所有实现类,并依次执行它们。
所以,这里首先我们创建一个简单的接口来启动任务。
public interface IStartupTask
{
Task ExecuteAsync(CancellationToken cancellationToken = default);
}
并且创建一个在依赖注入容器中注册任务的便捷方法。
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddStartupTask<T>(this IServiceCollection services)
where T : class, IStartupTask
=> services.AddTransient<IStartupTask, T>();
}
最后,我们添加一个扩展方法,在应用程序启动时找到所有已注册的IStartupTasks,按顺序运行它们,然后启动IWebHost:
public static class StartupTaskWebHostExtensions
{
public static async Task RunWithTasksAsync(this IWebHost webHost, CancellationToken cancellationToken = default)
{
var startupTasks = webHost.Services.GetServices<IStartupTask>();
foreach