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