I was trying to call GetSection
from injected configuration in the Startup.cs
.
The Value was null
, while indexer
to a concrete section value returns non-null
value. It seems to me a bug behind the GetSection
method or I am wrong with it?
appsettings.json:
{ "MyConfig": { "ConfigA": "valueA", "ConfigB": "valueB" } }
Program.cs:
public static void Main(string[] args)
{
var host = BuildWebHost(args);
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var mySection = this.Configuration.GetSection("MyConfig");
var myVal = this.Configuration["MyConfig:ConfigA"];
Configuration settings are contained within sections that group similar settings together for convenience. The GetSection method retrieves a configuration section by its name.
Add Json File After adding the file, right click on appsettings. json and select properties. Then set “Copy to Ouptut Directory” option to Copy Always. Add few settings to json file, so that you can verify that those settings are loaded.
I first checked to see if there were any changes between 1.1.1 and 2.x of JsonConfigurationProvider.cs
, the internal code that ultimately constructs retrievable values from your JSON file. There were no changes to this or any of the other code that ultimately gets called by your this.Configuration.GetSection("MyConfig");
.
The way retrieving values works is that the Configuration
will look for your key MyConfig
in each config provider in reverse order as defined in code until a value is found. In your example, providers (json, envionment variables, command line args) are provided within Webhost.CreateDefaultBuilder()
(see here).
Looking at the code for JsonConfigurationFileParser.cs, it builds a Dictionary<string, string>
for keys and values, but only for primitive values. That is, no key is stored for MyConfig
(at this level it is an object), but there will be a key for MyConfig:ConfigA
, and array values would look like MyConfig:ConfigA:0
, MyConfig:ConfigA:1
, etc.
Lastly, you will find that Configuration.GetSection("MyConfig")
always returns you a newly constructed ConfigurationSection
that is never null, and at the very worst will have a Value
property of null
.
So what happens when you hover over a ConfigurationSection
with Intellisense and look at the Value
property is that every config provider had been searched and none was found to have a key of "MyConfig" with a primitive value converted to string to return.
You will at the very least need to call:
services.Configure<MyConfigOptions>(configuration.GetSection("MyConfig"));
services.AddSingleton(cfg => cfg.GetService<IOptions<MyConfigOptions>>().Value);
to have it injected throughout your app as a C# object. Otherwise, call individual values with the colon ["MyConfig:ConfigA"]
separator syntax or with var mySection = this.Configuration.GetSection("MyConfig")["ConfigA"];
, which is redundant but illustrates it is only used to retrieve primitives.
To bind to C# objects and inject them, I created the following extension method:
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddConfigOptions<TOptions>(this IServiceCollection services,
IConfiguration configuration, string section) where TOptions : class, new()
{
services.Configure<TOptions>(configuration.GetSection(section));
services.AddSingleton(cfg => cfg.GetService<IOptions<TOptions>>().Value);
return services;
}
}
which can be called like this:
public class Startup
{
public Startup(IConfiguration configuration) => Configuration = configuration;
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddConfigOptions<EmailOptions>(Configuration, "Email")
and injected like this:
public class EmailSender : IEmailSender
{
private EmailOptions _emailOptions;
public EmailSender(EmailOptions options) => _emailOptions = options;
In my case, I was missing a package:
Microsoft.Extensions.Configuration.Binder
It's in fact documented as a subtle code comment in here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With