.NET 6 Worker Service中Options Pattern配置绑定失效问题
.NET 6 Worker Service(Windows服务)中IOptions配置绑定失败问题解决
问题描述
开发托管为Windows服务的.NET 6 Worker Service时,使用FluentFTP库下载文件,尝试通过Options Pattern实现强类型配置,遇到以下问题:
- Program类中手动绑定
FTPSettingsOptions能正常读取配置值 FTPService中注入的IOptions<FTPSettingsOptions>对应的配置值为空,导致FTP连接失败
相关代码片段
Program类代码
public class Program { public static async Task Main(string[] args) { var config = new ConfigurationBuilder().Build(); var logger = LogManager.Setup() .SetupExtensions(ext => ext.RegisterConfigSettings(config)) .GetCurrentClassLogger(); IHost host = Host.CreateDefaultBuilder(args) .UseWindowsService(Options => { Options.ServiceName = "Foo FTP File Process"; }) .ConfigureAppConfiguration((hostingContext, config) => { config.Sources.Clear(); IHostEnvironment env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true); IConfigurationRoot configurationRoot = config.Build(); FTPSettingsOptions options = new(); configurationRoot.GetSection(nameof(FTPSettingsOptions)) .Bind(options); // 此处能正常输出配置值 Console.WriteLine($"FTPSettingsOptions.Enabled={options.FTPUsername}"); }) .ConfigureServices(services => { services.AddOptions(); services.AddSingleton<IFTPService, FTPService>(); services.AddHostedService<Worker>(); }) .ConfigureLogging((hostContext, logging) => { logging.ClearProviders(); logging.AddNLog(hostContext.Configuration, new NLogProviderOptions() { LoggingConfigurationSectionName = "NLog" }); logging.AddConsole(); logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); }) .Build(); await host.RunAsync(); } }
appSettings.Development.json配置
{ "FTPSettingsOptions": { "FTPUri": "ftp://ftp.foo.com", "FTPUsername": "footest", "FTPPassword": "***********", "FTPServerPath": "/home/footest/foo", "CallServerPath": "C:\\Temp\\foo" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.Hosting.Lifetime": "Information" } } }
FTPSettingsOptions POCO类
public class FTPSettingsOptions { public FTPSettingsOptions() { } public const string FTPSettings = "FTPSettings"; public string FTPUri { get; set; } = String.Empty; public string FTPUsername { get; set; } = String.Empty; public string FTPPassword { get; set; } = String.Empty; public string FTPServerPath { get; set; } = String.Empty; public string CallServerPath { get; set; } = String.Empty; }
配置值为空的FTPService代码
public class FTPService : IFTPService { private readonly IOptions<FTPSettingsOptions> _options; public FTPService(IOptions<FTPSettingsOptions> options) { FTPSettingsOptions _options = options.Value; // Connect only once (Singleton scope) Connect(); } public void Connect() { // FTPUsername和FTPPassword无值 using ( var conn = new FtpClient(_options.Value.FTPUri, _options.Value.FTPUsername, _options.Value.FTPPassword)) { ... conn.Connect(); } } }
问题原因
- 未将配置注册到DI容器:在
ConfigureAppConfiguration中手动绑定的配置实例未被添加到依赖注入容器,导致FTPService注入的是未绑定配置的默认IOptions<FTPSettingsOptions>实例。 - 变量遮蔽问题:
FTPService构造函数中重新声明了局部变量_options,遮蔽了类的私有字段,导致Connect方法使用的是未赋值的类字段。
解决方案
1. 移除无效的手动绑定代码
删除ConfigureAppConfiguration中手动绑定配置的代码,这段代码仅用于验证配置读取,对DI容器中的Options无作用:
// 移除以下代码 FTPSettingsOptions options = new(); configurationRoot.GetSection(nameof(FTPSettingsOptions)) .Bind(options); Console.WriteLine($"FTPSettingsOptions.Enabled={options.FTPUsername}");
2. 在ConfigureServices中注册配置绑定
使用Configure<T>方法将配置节绑定到IOptions<T>,让DI容器能正确注入配置好的实例:
.ConfigureServices((hostContext, services) => { services.AddOptions(); // 新增:注册配置绑定 services.Configure<FTPSettingsOptions>(hostContext.Configuration.GetSection(nameof(FTPSettingsOptions))); services.AddSingleton<IFTPService, FTPService>(); services.AddHostedService<Worker>(); })
3. 修复FTPService中的变量遮蔽问题
修改构造函数,给类的私有字段赋值,而非重新声明局部变量:
public class FTPService : IFTPService { private readonly IOptions<FTPSettingsOptions> _options; public FTPService(IOptions<FTPSettingsOptions> options) { // 正确赋值类字段 _options = options; Connect(); } public void Connect() { using (var conn = new FtpClient(_options.Value.FTPUri, _options.Value.FTPUsername, _options.Value.FTPPassword)) { conn.Connect(); // 其他FTP操作 } } }
4. 优化配置加载(可选)
Host.CreateDefaultBuilder已默认加载appsettings.json和环境对应的配置文件,若无需自定义配置源,可简化ConfigureAppConfiguration逻辑,甚至直接删除该部分。
修改后的完整Program类示例
public class Program { public static async Task Main(string[] args) { var config = new ConfigurationBuilder().Build(); var logger = LogManager.Setup() .SetupExtensions(ext => ext.RegisterConfigSettings(config)) .GetCurrentClassLogger(); IHost host = Host.CreateDefaultBuilder(args) .UseWindowsService(options => { options.ServiceName = "Foo FTP File Process"; }) .ConfigureAppConfiguration((hostingContext, config) => { config.Sources.Clear(); IHostEnvironment env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); }) .ConfigureServices((hostContext, services) => { services.AddOptions(); services.Configure<FTPSettingsOptions>(hostContext.Configuration.GetSection(nameof(FTPSettingsOptions))); services.AddSingleton<IFTPService, FTPService>(); services.AddHostedService<Worker>(); }) .ConfigureLogging((hostContext, logging) => { logging.ClearProviders(); logging.AddNLog(hostContext.Configuration, new NLogProviderOptions() { LoggingConfigurationSectionName = "NLog" }); logging.AddConsole(); logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); }) .Build(); await host.RunAsync(); } }
内容的提问来源于stack exchange,提问作者IrishChieftain
相关产品推荐
相关产品推荐

