.NET 6中BackgroundService依赖注入时默认appsettings.json生成问题
解决方案:Windows服务启动前生成默认appsettings.json配置
问题根源
使用Host.CreateDefaultBuilder时,主机构建阶段就会尝试加载appsettings.json并绑定OpcSettings单例。如果配置文件不存在,绑定过程会直接失败,导致依赖注入容器初始化出错——服务还没进入OpcUaWorker的ExecuteAsync方法就启动超时,触发Service Control Manager的7000错误。
可行解决方法
1. 主机构建前提前检查并生成配置文件
在调用Host.CreateDefaultBuilder之前,先判断配置文件是否存在,不存在则直接生成默认配置写入文件。这样主机加载配置时文件已经存在,就能正常完成绑定。
代码示例:
var configFilePath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"); // 检查配置文件是否存在,不存在则生成默认配置 if (!File.Exists(configFilePath)) { // 构造默认配置对象 var defaultOpcSettings = new OpcSettings { ServerUrl = "opc.tcp://localhost:4840", ScanInterval = 5000, LogDirectory = "./logs", // 补充你的其他配置项默认值 }; // 序列化为格式化的JSON并写入文件 var jsonContent = JsonSerializer.Serialize(defaultOpcSettings, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(configFilePath, jsonContent); } // 后续正常构建主机 var host = Host.CreateDefaultBuilder(args) .UseWindowsService() .ConfigureServices((context, services) => { services.AddSingleton<IOpcLogger, OpcLogger>(); // 绑定配置到OpcSettings services.Configure<OpcSettings>(context.Configuration.GetSection("OpcSettings")); services.AddHostedService<OpcUaWorker>(); }) .Build(); await host.RunAsync();
2. 配置加载时添加默认值 fallback
如果不想提前生成文件,可以将appsettings.json设置为可选加载,同时添加内存配置作为默认值。这样即使文件不存在,OpcSettings也能绑定到默认值,服务可以正常启动,之后还能在ExecuteAsync里判断是否使用了默认值,再生成配置文件。
代码示例:
var host = Host.CreateDefaultBuilder(args) .UseWindowsService() .ConfigureAppConfiguration((context, configBuilder) => { configBuilder .SetBasePath(Directory.GetCurrentDirectory()) // 设置配置文件为可选,不存在时不报错 .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) // 添加内存配置作为默认值 .AddInMemoryCollection(new Dictionary<string, string> { ["OpcSettings:ServerUrl"] = "opc.tcp://localhost:4840", ["OpcSettings:ScanInterval"] = "5000", ["OpcSettings:LogDirectory"] = "./logs" // 对应你的配置项键值对 }); }) .ConfigureServices((context, services) => { services.AddSingleton<IOpcLogger, OpcLogger>(); services.Configure<OpcSettings>(context.Configuration.GetSection("OpcSettings")); services.AddHostedService<OpcUaWorker>(); }) .Build(); await host.RunAsync();
然后在OpcUaWorker的ExecuteAsync中补全配置文件生成逻辑:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var settings = _options.Value; var configFilePath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"); // 如果配置文件不存在,生成文件 if (!File.Exists(configFilePath)) { var jsonContent = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(configFilePath, jsonContent); _logger.LogInformation("已生成默认配置文件"); } // 后续的OPC UA采集逻辑 }
3. 延迟加载配置(备选方案)
如果不想修改主机构建逻辑,可以避免在OpcUaWorker的构造函数中直接注入OpcSettings,改用注入IOptionsMonitor<OpcSettings>或IServiceProvider,在ExecuteAsync中再获取配置。这样服务启动时不会立即解析配置,而是在运行时再处理,此时可以先检查并生成配置文件。
代码示例:
public class OpcUaWorker : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly IOpcLogger _logger; public OpcUaWorker(IServiceProvider serviceProvider, IOpcLogger logger) { _serviceProvider = serviceProvider; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var configFilePath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"); if (!File.Exists(configFilePath)) { // 生成默认配置文件 var defaultSettings = new OpcSettings { /* 默认值 */ }; var json = JsonSerializer.Serialize(defaultSettings, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(configFilePath, json); // 重新加载配置,确保后续获取的是最新配置 var config = _serviceProvider.GetRequiredService<IConfiguration>(); ((IConfigurationRoot)config).Reload(); } // 此时再获取配置 var settings = _serviceProvider.GetRequiredService<IOptions<OpcSettings>>().Value; // 后续逻辑 } }
内容的提问来源于stack exchange,提问作者Anthony Voronkov
相关产品推荐
相关产品推荐

