.Net Core上具有Topshelf的Windows Service也具有Kestrel API-依赖项注入问题

问题描述

在将某些Windows服务从Framework迁移到.Net Core 3.1时,我们遇到了一些问题。

我们目前最苦苦挣扎的问题是,因为我们实际上有2个级别的依赖注入。一个在Topshelf服务级别,一个在Topshelf.AfterStartingService / Kestrel API级别。

这意味着当我们使用在ServiceA中注册的服务访问ServiceA控制器时,出现错误,表明它无法解析IPackageRegistrationService

我们是否缺少更好的设计模式?有没有一种方法可以将这些依赖项传递到Common.Startup类中,而不必将Common项目中的循环依赖项返回给ServiceA?

错误看起来像

system.invalidOperationException:尝试激活“ ServiceA.Console.Registration.Controllers.PackageRegistrationController”时,无法解析类型为“ ServiceA.Console.Registration.Service.IPackageRegistrationService”的服务。 在Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(类型为serviceType,类型为ImplementationType,CallSiteChain,callSiteChain,ParameterInfo []参数,布尔型throwIfCallSiteNotFound)

ServiceA.Main()像这样注册所有依赖项

private static void Main()
        {
            DbProviderFactories.RegisterFactory("System.Data.sqlClient",sqlClientFactory.Instance);

            var configuration = ConfigurationManager.InitialiseConfigurationBuilder().Build();
            var mainRabbitQueue = new MonitoredMessageQueue(new RabbitMessageQueue());

            // Dependency Injection
            var serviceProvider = new ServiceCollection()
                // Services
                .AddTransient<IReprocessExecuter,ReprocessExecuter>()
                .AddTransient<IFileNameBuilder,FileNameBuilder>()
                .AddTransient<IRuleRunner,RuleRunner>()
                .AddTransient<ITimeReporter,TimeReporter>()
                .AddTransient<IPackageRegistrationService,PackageRegistrationService>()

                .AddTransient<NetCore.Console.Common.ServiceHost<BatchProcessorService>,NetCore.Console.Common.ServiceHost<BatchProcessorService>>()

                // Console.Common
                .AddSingleton<IMessageQueue>(mainRabbitQueue)
                .AddSingleton<IMessageQueueStats>(mainRabbitQueue)
                .AddSingleton<IServiceLogger>(new ServiceEventLogger(mainRabbitQueue))
                .AddTransient<IHeartBeatService,HeartBeatService>()
                .AddTransient<IStatusService,StatusService>()

                .BuildServiceProvider();                      


            using (var serviceHost = serviceProvider.GetService<NetCore.Console.Common.ServiceHost<BatchProcessorService>>())
            {
                serviceHost.Run();                
            }

ServiceCommon.Run()

public void Run()
        {
            HostFactory.Run(x =>
            {
                x.Service<TService>(s =>
                {                    
                    s.AfterStartingService(c =>
                    {
                        _healthChecker.Start();
                        if (!string.IsNullOrEmpty(serviceSettings.ApiUri))
                        {
                            Startup.ServiceName = serviceSettings.ServiceName;
                            Startup.StatusService = _statusService;                            
                            _apiService = CreateHostBuilder().Build();
                            _apiService.Start();
                            _log.Loginformation($"{serviceSettings.ServiceName} API is running,access health check at {serviceSettings.ApiUri}/healthcheck");
                        }
                    });                                    
                });               
            });
        }

CreateHostBuilder的外观

        public static IHostBuilder CreateHostBuilder(string[] args = null)
        {
            
            return Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)                
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .UseStartup<Startup>()
                        .UseUrls(ServiceSettings.Default.ApiUri)
                        .UseSetting(WebHostDefaults.ApplicationKey,Assembly.GetExecutingAssembly().GetName().Name);
                });
        }

并且StartUp.ConfigureServices看起来像这样

        public void ConfigureServices(IServiceCollection services)
        {   
            var assembly = Assembly.Load(ServiceName);
            services.AddMvc(options =>
            {
                options.EnableEndpointRouting = false;
            }).AddApplicationPart(assembly)
            .AddControllersAsServices();

            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1",new OpenApiInfo
                {
                    Title = $"{ServiceName}",Version = "v1",Description = $"{ServiceName}"
                });
            });

            var mainRabbitQueue = new MonitoredMessageQueue(new RabbitMessageQueue());
            services.AddSingleton<IMessageQueue>(mainRabbitQueue)
                .AddSingleton<IMessageQueueStats>(mainRabbitQueue)
                .AddTransient<IHeartBeatService,HeartBeatService>()
                .AddSingleton<IStatusService>(StatusService);
        }

解决方法

Host.CreateDefaultBuilder内部s.AfterStartingService(创建的通用主机具有新的服务提供程序(新的依赖项注入容器)。它没有在Main中创建的服务提供商的注册。

在.NET Core中,您不需要Topshelf即可将应用程序作为Windows服务运行。借助Microsoft.Extensions.Hosting.WindowsServices nuget,您可以创建一个通用主机,该主机可以作为Windows服务运行,而Kestrel作为其中一种托管服务。

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    })
    .UseWindowsService()

您可以在主机构建器的StartupConfigureServices方法中注册类型/服务/依赖项。

关于docs中的UseWindowsService

将主机生存期设置为WindowsServiceLifetime,设置内容根目录,并启用使用应用程序名称作为默认源名称的事件日志记录。这是上下文感知的,只有在它检测到该进程正在作为Windows服务运行时才会激活。