簡體   English   中英

.Net Core 上帶有 Topshelf 的 Windows 服務,它也有一個 Kestrel API - 依賴注入問題

[英]Windows Service with Topshelf on .Net Core that also has a Kestrel API - Dependency Injection Issues

我們在將一些 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.IPPackageRegistrationService”類型的服務。 在 Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean 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.CreateDefaultBuilders.AfterStartingService(創建的通用主機具有新的服務提供者(新的依賴注入容器)。它沒有來自Main創建的服務提供者的注冊。

在 .NET Core 中,您不需要 Topshelf 將應用程序作為 Windows 服務運行。 使用Microsoft.Extensions.Hosting.WindowsServices nuget,您可以創建可作為 Windows 服務運行的通用主機,並將 Kestrel 作為托管服務之一。

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

您可以在主機構建器的StartupConfigureServices方法中注冊您的類型/服務/依賴項。

關於來自文檔的UseWindowsService

將主機生存期設置為 WindowsServiceLifetime,設置內容根,並啟用將應用程序名稱作為默認源名稱記錄到事件日志中。 這是上下文感知的,只有在檢測到進程作為 Windows 服務運行時才會激活。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM