在.net core 3.0 BackgroundService应用程序中,为什么作为服务而不是控制台应用程序运行时我的配置对象为空?

我有一个.net Core 3.0 BackgroundService应用程序,该应用程序在控制台模式下运行时运行良好,但是一旦将其部署为服务,应从appsettings.json加载的配置对象为空。有什么作用?

Program.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类

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中加载了配置。当我将其部署为服务时,配置对象为空。

注意:正在读取appsettings文件,我可以通过更改其名称来告知这一点,并且它为未找到的文件引发异常。 appsettings文件也不为空。

hanhjj 回答:在.net core 3.0 BackgroundService应用程序中,为什么作为服务而不是控制台应用程序运行时我的配置对象为空?

这似乎是XY problem,值得重构

创建强类型模型以保存所需的设置

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进行的第一次滴答未加载,但通过第二次被加载。如果遇到该异常,我的服务就会死掉,所以我再也没有收到它的信号。

本文链接:https://www.f2er.com/3150897.html

大家都在问