在應用程序的內存中緩存常見數據(如查找)可以顯着提高您的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 越來越好!