I'm creating a .NET 6 worker service, hosted as a windows service. The service uses the FluentFTP library to download files from a remote server. I'm trying to implement strongly-typed configuration using the Options Pattern but am having difficulties with the binding.
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);
//Working
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"
}
}
}
POCO Class:
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;
}
This is where the options values are coming in as null:
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 and FTPPassword have no values
using ( var conn = new FtpClient("ftp://ftp.foo.com", _options.Value.FTPUsername,
_options.Value.FTPPassword))
{
...
conn.Connect();
}
}
}
CodePudding user response:
You are missing the Configure
call:
.ConfigureServices((ctx, services) =>
{
services.Configure<FTPSettingsOptions>(ctx.Configuration.GetSection(nameof(FTPSettingsOptions)));
// ...
})
Note that explicit AddOptions
call is not needed because Configure
performs it internally.
configurationRoot.GetSection(nameof(FTPSettingsOptions)).Bind(options);
just binds the data to options
instance and does not register anything in the DI (check the docs here or here).