Aspnet Zero中使用Windows service (Topshelf)来承载Quartz.net任务

Aspnet Zero使用Windows service (Topshelf)来承载Quartz.net任务

网上有很多关于如何使用Topshelf创建ABP的Quartz windows服务,但很少(没有)有介绍如何配合Aspnet Zero使用的文章,本文记录集成过程,以供参考。

  1. 在官方Aspnet Zero模板解决方案中建立Console类型的项目,并安装以下nuget package:

    Topshelf

    Abp.Quartz

    Abp.Castle.Log4Net

  2. 添加引用MyCompanyName.AbpZeroTemplate.CoreMyCompanyName.AbpZeroTemplate.EntityFrameworkCore项目

  3. 最终引用如图:

  4. 创建Module文件,

    • 添加相关DependsOn属性;
    • abpZeroTemplateEntityFrameworkCoreModule.SkipDbSeed = true; 这里我们需要禁用数据库初始化动作,因为所有的初始化动作都在Host端完成;
    • 设置数据库连接字符串Configuration.DefaultNameOrConnectionString = _appConfiguration.GetConnectionString( AbpZeroTemplateConsts.ConnectionStringName );

    最终文件如下:

    namespace MyCompanyName.AbpZeroTemplate.WinService
    {
        [DependsOn(typeof(AbpZeroTemplateCoreModule),
            typeof(AbpZeroTemplateEntityFrameworkCoreModule),
            typeof(AbpQuartzModule)
            )]
        public class AbpZeroWinServiceModule : AbpModule
        {
            private readonly IConfigurationRoot _appConfiguration;
    
            public AbpZeroWinServiceModule(AbpZeroTemplateEntityFrameworkCoreModule abpZeroTemplateEntityFrameworkCoreModule)
            {
                abpZeroTemplateEntityFrameworkCoreModule.SkipDbSeed = true;
    
                _appConfiguration = AppConfigurations.Get(
                    typeof(AbpZeroWinServiceModule).GetAssembly().GetDirectoryPathOrNull(),
                    addUserSecrets: true
                );
            }
    
            public override void PreInitialize()
            {
                //Set default connection string
                Configuration.DefaultNameOrConnectionString = _appConfiguration.GetConnectionString(
                    AbpZeroTemplateConsts.ConnectionStringName
                );
            }
    
            public override void Initialize()
            {
                this.IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    
            }
        }
    }
    
  5. 修改Program.cs文件

    • 设置工作目录 Directory.SetCurrentDirectory(currentDirectory);, 目的是为了保证工作在windows service时log文件存放目录正确,如果不设置, 工作在windows service时log文件将会存放在system32目录中
    • 注册IdentityRegistrar以及添加abp依赖,这一步是必须的,否则会出现依赖错误:
      Castle.MicroKernel.Handlers.HandlerException: 'Can't create component 'Portal.Authorization.Users.UserManager' as it has dependencies to be satisfied.
      添加abp依赖的同时我们同时添加log4net和插件支持.(注意路径)
      var services = new ServiceCollection();
      IdentityRegistrar.Register(services);
      
      var abpBootstrapper = AbpBootstrapper.Create<AbpZeroWinServiceModule>(options =>
      {
          options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                              f => f.UseAbpLog4Net().WithConfig(Path.Combine(currentDirectory, $"log4net.config"))
                          );
          options.PlugInSources.AddFolder(Path.Combine(currentDirectory, "Plugins"), SearchOption.AllDirectories);
      });
      
      services.AddSingleton(abpBootstrapper);
      abpBootstrapper.IocManager.IocContainer.AddServices(services);
      

    完整Program.cs代码如下:

    class Program
    {
        static void Main(string[] args)
        {
            var currentDirectory = typeof(Program).GetAssembly().GetDirectoryPathOrNull();
            // 设置工作目录. 保证工作在windows service时log文件存放目录正确
            // 如果不设置, 工作在windows service时log文件将会存放在system32目录中
            Directory.SetCurrentDirectory(currentDirectory);
    
            var services = new ServiceCollection();
            IdentityRegistrar.Register(services);
                        
            var abpBootstrapper = AbpBootstrapper.Create<AbpZeroWinServiceModule>(options =>
            {
                options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                                    f => f.UseAbpLog4Net().WithConfig(Path.Combine(currentDirectory, $"log4net.config"))
                                );
                options.PlugInSources.AddFolder(Path.Combine(currentDirectory, "Plugins"), SearchOption.AllDirectories);
            });
    
            services.AddSingleton(abpBootstrapper);
            abpBootstrapper.IocManager.IocContainer.AddServices(services);
    
            HostFactory.Run(x =>
            {
                x.Service<AbpZeroWinService>(s =>
                {
                    s.ConstructUsing(name => new AbpZeroWinService());
                    s.WhenStarted((tc, hostControl) => tc.Start(hostControl));
                    s.WhenStopped((tc, hostControl) => tc.Stop(hostControl));
                });
    
                x.RunAsLocalSystem();
                x.StartAutomatically();
    
                x.SetDescription("ABP服务测试");
                x.SetDisplayName("ABPTestService");
                x.SetServiceName("ABPTestService");
            });
        }
    }
    
  6. 启动ABP. Windows service启动时会调用s.ConstructUsing(name => new AbpZeroWinService());, 因此我们在AbpZeroWinService中启动ABP.

    • 创建AbpZeroWinService类继承自ServiceControl, 在Start中初始化AbpBootstrapper, 同时在停止Stop中销毁AbpBootstrapper. 代码如下:
    public class AbpZeroWinService : ServiceControl
    {
        private AbpBootstrapper _bootstrapper;
        public bool Start(HostControl hostControl)
        {
            _bootstrapper = IocManager.Instance.Resolve<AbpBootstrapper>();
            _bootstrapper.Initialize();
            return true;
        }
    
        public bool Stop(HostControl hostControl)
        {
            _bootstrapper.Dispose();
            return true;
        }
    }
    
  7. quartz.net的配置quartz.config中必须使用AdoJobStore类型,并且使用和Host一样的Quartz数据库连接,这样才能实现在host上添加任务,最终由windows service来执行任务。
    (请在HostModule的PreInitialize方法中禁用Job执行Configuration.BackgroundJobs.IsJobExecutionEnabled = false;)

  8. 运行前确保Quartz.net数据库已建立,如何建立请参考Quartz.net官方文档

以上如有错误的地方请大家指正! 如果更好的实现方式,也请分享一下。
最后给出WindowService项目的源码,Aspnet Zero的项目自行解决。
source code

posted on 2020-08-12 08:01  Thinking110  阅读(505)  评论(0编辑  收藏  举报

导航