ASP.NET Core MVC應用程式中的後臺工作計劃任務
在應用程式的記憶體中快取常見資料(如查詢)可以顯著提高您的MVC Web應用程式效能和響應時間。當然,這些資料必須定期重新整理。
當然你可以使用任何方法來更新資料,例如Redis中就提供了設定快取物件的生命時間,那麼對於這種單物件的更新的做法我覺得是不符合我的程式設計習慣的,我們可以使用QuartZ.NET 框架來進行任務排程,我們在任務計劃中進行統一的快取更新,也就達到了我們的目的。
Quartz受到良好支援,跨平臺庫可用於在應用程式內部排程任務。由於ASP.NET Core架構和開箱即用的中介軟體支援,在.NET Core MVC Web應用程式中使用它有點不同。在本文中,我將嘗試使用ASP.NET核心應用程式中的Quartz來解釋後臺工作者計劃任務的簡單計劃。
我使用簡單的ASP.NET Core WebApi專案模板作為示例專案的基礎。由於我們不會關注端點的實際響應,而是關注Startup.cs依賴注入部分和中介軟體,預設的WebApi專案模板就好了。
作為第一步我們應該引用Quartz框架,我將使用Nuget管理器,我現在使用的工具是Visual Studio 2019 體驗版。
Install-Package Quartz -Version 3.0.7
如果你在Linux或Mac Os上進行開發,那麼請安裝.net core sdk 在使用.net 腳手架來進行引入。
dotnet add package Quartz --version 3.0.7
由於ASP.NET Core具有開箱即用的依賴注入支援,我們需要在啟動時設定我們的解析器,但在我們這樣做之前,我們需要編寫我們將用於設定排程的一些Quartz介面的實現我們專案中的任務。
我們首先需要編寫我們的任務,即將按特定時間表執行的程式碼單元。為此,我們需要在我們的作業類中實現 Quartz.IJob介面。
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Quartz; using System; using System.Threading.Tasks;namespace Schedule.WebApiCore.Sample.Schedule { public class ScheduledJob : IJob { private readonly IConfiguration configuration; private readonly ILogger<ScheduledJob> logger; public ScheduledJob(IConfiguration configuration, ILogger<ScheduledJob> logger) { this.logger = logger; this.configuration = configuration; } public async Task Execute(IJobExecutionContext context) { this.logger.LogWarning($"Hello from scheduled task {DateTime.Now.ToLongTimeString()}"); await Task.CompletedTask; } } }
由於它只是一個示例應用程式,因此每次作業執行時,我只會在輸出中寫入當前時間的訊息。將從Startup.cs類方法注入Microsoft.Extensions.Configuration.IConfiguration和Microsoft.Extensions.Logging.ILogger介面實現的例項
我們需要實現的下一個介面是Quartz.Spi.IjobFactory。
using Quartz; using Quartz.Spi; using System; namespace Schedule.WebApiCore.Sample.Schedule { public class ScheduledJobFactory : IJobFactory { private readonly IServiceProvider serviceProvider; public ScheduledJobFactory(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { return serviceProvider.GetService(typeof(IJob)) as IJob; } public void ReturnJob(IJob job) { var disposable = job as IDisposable; if (disposable != null) { disposable.Dispose(); } } } }
我們將IJobFactory介面實現的例項分配給我們的IScheduler例項,該例項將用於在每個排程觸發器上例項化作業例項。現在在我們的Startup.cs中設定依賴注入解析器。
配置
using System; using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Quartz; using Quartz.Impl; using Quartz.Spi; using Schedule.WebApiCore.Sample.Schedule; namespace Schedule.WebApiCore.Sample { public class Startup { public IConfiguration Configuration { get; } public IHostingEnvironment HostingEnvironment { get; } public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment) { this.HostingEnvironment = hostingEnvironment; this.Configuration = configuration; } public void ConfigureServices(IServiceCollection services) { services.AddLogging(); services.AddSingleton<IConfiguration>(new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{this.HostingEnvironment.EnvironmentName.ToLower()}.json") .Build()); #region Configure Quartz DI services.Add(new ServiceDescriptor(typeof(IJob), typeof(ScheduledJob), ServiceLifetime.Transient)); services.AddSingleton<IJobFactory, ScheduledJobFactory>(); services.AddSingleton<IJobDetail>(provider => { return JobBuilder.Create<ScheduledJob>() .WithIdentity("Sample.job", "group1") .Build(); }); services.AddSingleton<ITrigger>(provider => { return TriggerBuilder.Create() .WithIdentity($"Sample.trigger", "group1") .StartNow() .WithSimpleSchedule (s => s.WithInterval(TimeSpan.FromSeconds(5)) .RepeatForever() ) .Build(); }); services.AddSingleton<IScheduler>(provider => { var schedulerFactory = new StdSchedulerFactory(); var scheduler = schedulerFactory.GetScheduler().Result; scheduler.JobFactory = provider.GetService<IJobFactory>(); scheduler.Start(); return scheduler; }); #endregion services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, IScheduler scheduler) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } scheduler.ScheduleJob(app.ApplicationServices.GetService<IJobDetail>(), app.ApplicationServices.GetService<ITrigger>()); app.UseMvc(); } } }
修改Startup.cs
啟動專案沒有什麼問題~但當你將所有部件放在Startup.cs中的一個地方時,這樣更容易解釋整個DI設定,在那裡你可以看到所有的依賴關係和介面是如何解決的,但是從長遠來看,這個Startup.cs應該是凌亂的它最終將成為維持的噩夢。出於這個原因,我將整個DI包裝到擴充套件方法中。
綜上所述,我們的.Net Core為我們提供瞭解決方案,那麼把Quartz的程式碼全部放到一個靜態類中,它將使用Quartz的Microsoft依賴注入語句和管道的Microsoft.AspNetCore.Builder.IApplicationBuilder擴充套件 Microsoft.Extensions.DependencyInjection.IServiceCollection 。
就我個人而言,我喜歡在專案中建立一個Extensions資料夾,然後在其中寫入類,再去Startup.cs中進行配置!QuartzExtensions定義如下:
public static class QuartzExtensions { public static void AddQuartz(this IServiceCollection services, Type jobType) { services.Add(new ServiceDescriptor(typeof(IJob), jobType, ServiceLifetime.Transient)); services.AddSingleton<IJobFactory, ScheduledJobFactory>(); services.AddSingleton<IJobDetail>(provider => { return JobBuilder.Create<ScheduledJob>() .WithIdentity("Sample.job", "group1") .Build(); }); services.AddSingleton<ITrigger>(provider => { return TriggerBuilder.Create() .WithIdentity($"Sample.trigger", "group1") .StartNow() .WithSimpleSchedule (s => s.WithInterval(TimeSpan.FromSeconds(5)) .RepeatForever() ) .Build(); }); services.AddSingleton<IScheduler>(provider => { var schedulerFactory = new StdSchedulerFactory(); var scheduler = schedulerFactory.GetScheduler().Result; scheduler.JobFactory = provider.GetService<IJobFactory>(); scheduler.Start(); return scheduler; }); } public static void UseQuartz(this IApplicationBuilder app) { app.ApplicationServices.GetService<IScheduler>() .ScheduleJob(app.ApplicationServices.GetService<IJobDetail>(), app.ApplicationServices.GetService<ITrigger>() ); } }
現在我們的Startup.cs更清潔,更容易維護和擴充套件!
public class Startup { public IConfiguration Configuration { get; } public IHostingEnvironment HostingEnvironment { get; } public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment) { this.HostingEnvironment = hostingEnvironment; this.Configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddLogging(); services.AddSingleton<IConfiguration>(new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{this.HostingEnvironment.EnvironmentName.ToLower()}.json") .Build()); services.AddQuartz(typeof(ScheduledJob)); services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseQuartz(); app.UseMvc(); } }
再次啟動專案,ok 通過~
祝.Net Core 越來越好!