Topshelf+Quartz在.Net Core框架下的實現

  • 2019 年 11 月 6 日
  • 筆記

  在我們日常開發工作中,經常會運用到Quartz+Topshelf組件的組合來開發一些定時任務。那麼在.Net Core下如何去使用呢?我自己嘗試搭建了一個測試項目,過程中遇到了以下一些問題:

  • Quartz 配置文件及版本問題。我們知道Quartz有2個配置文件,quartz.config和quartz.job.xml。前者負責組件初始化配置,後者負責job和triggle的配置。剛開始我是直接把framework下的配置文件直接拿過來用的,啟動直接報錯。主要問題在quartz.config
    quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz  

    這一段上。原因Quartz最新版本已經將Plugin模組單獨剝離出一個獨立的DLL,這裡的引用也要變化。需要Nuget上下載Quartz.Plugins組件,並將上一段改成

    quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins  

     

  • DI問題。為了貼合.Net Core DI精神,我們也要來實現Console程式的DI功能。第一個問題是Job如何DI?首先我們需要自己去實現JobFactory

        public class NewJobFactory : IJobFactory      {          private readonly IServiceProvider _serviceProvider;            public NewJobFactory(IServiceProvider serviceProvider)          {              _serviceProvider = serviceProvider;          }          public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)          {              return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;          }            public void ReturnJob(IJob job)          {              var disposable = job as IDisposable;              disposable?.Dispose();          }      }  

      注入方式

    IServiceCollection services = new ServiceCollection();  services.AddScoped<IJobFactory, NewJobFactory>();  services.AddSingleton(service =>  {       var scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;       scheduler.JobFactory = service.GetService<IJobFactory>();       return scheduler;  });

     

  • Console程式的配置文件獲取以及注入問題。眾所周知,.Net Core下建立的Console程式就是一塊白板,什麼都沒有。配置文件我們還得自己去建一個.json文件。並且需要自己從Nuget上下載的組件包(見後面項目結構截圖)。載入方式如下
  •         private IConfiguration ConfigureConfiguration()          {              //配置文件              var builder = new ConfigurationBuilder()                  .SetBasePath(Directory.GetCurrentDirectory())                  .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);              return builder.Build();          }  

      注入方式如下

                if (configuration != null)              {                  //iconfiguration注入                  services.AddSingleton<IConfiguration>(configuration);              }                //自定義 option方式注入              services.Configure<AppSetting>(configuration);  

      這樣就可以在後續的類程式碼中 直接通過DI方式獲取IConfiguration對象或者你自己的Option對象了

  貼上完整程式碼。

  Program.cs

        static void Main(string[] args)          {              HostFactory.Run(x =>              {                  x.Service<ServiceRunner>();                  x.SetDescription("gt.dotnetcore.consolesample");                  x.SetDisplayName("gt.dotnetcore.consolesample");                  x.SetServiceName("gt.dotnetcore.consolesample");                    x.StartAutomatically();              });          }  

  ServiceRunner.cs 啟動的核心

    public class ServiceRunner : ServiceControl      {          //private readonly IScheduler _scheduler;            private IServiceProvider _serviceProvider;          public ServiceRunner()          {              var configurationRoot = ConfigureConfiguration();              _serviceProvider = ConfigureServices(configurationRoot);          }            private IConfiguration ConfigureConfiguration()          {              //配置文件              var builder = new ConfigurationBuilder()                  .SetBasePath(Directory.GetCurrentDirectory())                  .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);              return builder.Build();          }          private IServiceProvider ConfigureServices(IConfiguration configuration)          {              //依賴注入              IServiceCollection services = new ServiceCollection();              //後續需要使用log的話,這裡需要注入              services.AddTransient<ILoggerFactory, LoggerFactory>();              services.AddTransient<ITest, TestBiz>();              services.AddScoped<IJobFactory, NewJobFactory>();              if (configuration != null)              {                  //iconfiguration注入                  services.AddSingleton<IConfiguration>(configuration);              }                //自定義 option方式注入              services.Configure<AppSetting>(configuration);              //這裡注意Job的注入方式,不要強制指定IJob實現方式!!              services.AddScoped<TestJob>();              services.AddScoped<Test2Job>();              services.AddSingleton(service =>              {                  var scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;                  scheduler.JobFactory = service.GetService<IJobFactory>();                  return scheduler;              });              //構建容器              return services.BuildServiceProvider();          }            public bool Start(HostControl hostControl)          {              var scheduler = _serviceProvider.GetService(typeof(IScheduler)) as IScheduler;              scheduler.Start();              return true;          }            public bool Stop(HostControl hostControl)          {              var scheduler = _serviceProvider.GetService(typeof(IScheduler)) as IScheduler;              scheduler.Shutdown(true);              return true;          }      }  

  Test2Job.cs

    public class Test2Job : IJob      {          private ITest _testService;          private AppSetting _appsetting;          private IConfiguration _configuration;            public Test2Job(ITest testService, IOptionsMonitor<AppSetting> appSettingAccessor, IConfiguration configuration)          {              _testService = testService;              _appsetting = appSettingAccessor.CurrentValue;              _configuration = configuration;          }          public Task Execute(IJobExecutionContext context)          {              Console.WriteLine($"job2222222222222 started:{_appsetting.TestCN}");              var t = _testService.Dowork(1);              t.Wait();              Console.WriteLine($"job2222222222222 ended:{_configuration["TestCN"]}");                return Task.CompletedTask;          }      }  

  

  項目結構截圖

附項目源碼 https://gitee.com/gt1987/gt.dotnetcore