簡體   English   中英

在 .net 核心 3.0 BackgroundService 應用程序中,為什么我的配置 object 作為服務運行時為空,而不是作為控制台應用程序運行?

[英]In a .net core 3.0 BackgroundService app, why is my configuration object empty when running as service, but not as console app?

我有一個 .net Core 3.0 BackgroundService 應用程序,它在控制台模式下運行時工作正常,但是一旦我將配置 object 部署為服務,應該從 appsettings.Z466DEEC76ECDF5FCA6D38571F6324D4 加載的配置為空。 是什么賦予了?

程序.cs

public class Program
{
    public static async System.Threading.Tasks.Task Main(string[] args)
    {
        var hostbuilder = new HostBuilder()
            .UseWindowsService()
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config
                .SetBasePath(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location))
                .AddJsonFile("appsettings.json");
            })
            .ConfigureLogging(
                options => options.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Information))
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Importer>().Configure<EventLogSettings>(config =>
                {
                    config.LogName = "Application";
                    config.SourceName = "Importer";
                });
            });
#if (DEBUG)
        await hostbuilder.RunConsoleAsync();
#else
        await hostbuilder.RunAsServiceAsync();
#endif
    }
}

IhostBuilder運行服務的擴展方法

public static class ServiceBaseLifetimeHostExtensions
{
    public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
    {
        return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
    }

    public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
    {
        return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
    }
}

ServiceBaseLifetime class 處理服務生命周期

public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
    private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();

    public ServiceBaseLifetime(IHostApplicationLifetime applicationLifetime)
    {
        ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
    }

    private IHostApplicationLifetime ApplicationLifetime { get; }

    public Task WaitForStartAsync(CancellationToken cancellationToken)
    {
        cancellationToken.Register(() => _delayStart.TrySetCanceled());
        ApplicationLifetime.ApplicationStopping.Register(Stop);

        new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
        return _delayStart.Task;
    }

    private void Run()
    {
        try
        {
            Run(this); // This blocks until the service is stopped.
            _delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
        }
        catch (Exception ex)
        {
            _delayStart.TrySetException(ex);
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        Stop();
        return Task.CompletedTask;
    }

    // Called by base.Run when the service is ready to start.
    protected override void OnStart(string[] args)
    {
        _delayStart.TrySetResult(null);
        base.OnStart(args);
    }

    // Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
    // That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
    protected override void OnStop()
    {
        ApplicationLifetime.StopApplication();
        base.OnStop();
    }
}

服務的實際實現與構造函數無關,構造函數通過 DI 獲取記錄器和配置。

private readonly ILogger<Importer> _logger;
private readonly IConfiguration _configuration;

public Importer(IConfiguration configuration, ILogger<Importer> logger)
{
    _logger = logger;
    _configuration = configuration;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation($"Why is {_configuration["Key1"]} empty?");
}

appsettings.json

{
    "Key1":"some value"
}

當我運行調試時,控制台應用程序啟動並運行並記錄並從 appsettings 加載配置。 當我部署為服務時,配置 object 為空。

注意:正在讀取 appsettings 文件,我可以通過更改它的名稱來判斷這一點,它會拋出找不到文件的異常。 appsettings 文件也不為空。

這似乎是一個XY 問題,值得重構

創建一個強類型 model 來保存所需的設置

public class ImporterSettings {
    public string Key1 { get; set; }
}

重構托管服務以依賴於設置,因為在我看來,將服務與IConfiguration緊密耦合是一種代碼味道

private readonly ILogger<Importer> _logger;
private readonly ImporterSettnigs settings;

public Importer(ImporterSettnigs settings, ILogger<Importer> logger) {
    _logger = logger;
    this.settings = settings;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
    _logger.LogInformation($"This is Key1: {settings.Key1}");
}

現在正確配置啟動以使用提供的配置

public class Program {
    public static async System.Threading.Tasks.Task Main(string[] args) {
        var hostbuilder = new HostBuilder()
            .UseWindowsService()
            .ConfigureAppConfiguration((hostingContext, config) => {
                var path = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
                config
                    .SetBasePath(path)
                    .AddJsonFile("appsettings.json");
            })
            .ConfigureLogging(
                options => options.AddFilter<EventLogLoggerProvider>(level => 
                    level >= LogLevel.Information)
            )
            .ConfigureServices((hostContext, services) => {
                //get settings from app configuration.
                ImporterSettings settings = hostContext.Configuration.Get<ImporterSettings>();

                services
                    .AddSingleton(settings) //add to service collection
                    .AddHostedService<Importer>()
                    .Configure<EventLogSettings>(config => {
                        config.LogName = "Application";
                        config.SourceName = "Importer";
                    });
            });
#if (DEBUG)
        await hostbuilder.RunConsoleAsync();
#else
        await hostbuilder..Build().RunAsync();
#endif
    }
}

我的問題似乎是某種異步競爭條件問題(我猜,不是肯定的)。 第一次通過ExecuteAsync勾選配置未加載,但第二次通過它。 如果它遇到那個異常,我的服務就會死掉,所以我再也沒有讓它第二次打勾。

暫無
暫無

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

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