You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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();
        }
    }
}

问题原因

  1. 未将配置注册到DI容器:在ConfigureAppConfiguration中手动绑定的配置实例未被添加到依赖注入容器,导致FTPService注入的是未绑定配置的默认IOptions<FTPSettingsOptions>实例。
  2. 变量遮蔽问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 16:20:47