I have the following extension method to add themes support to my application:
public static void AddThemes(this IServiceCollection services, Action<ThemesOptions> setup) {
services.Configure(setup);
}
Where ThemesOptions
is defined as:
public class ThemesOptions {
public IEnumerable<string> Themes { get; set; }
}
Now in my application's startup ConfigureServices
method I can say:
services.AddThemes(options => {
options.Themes = Configuration.GetSection("Themes").Get<ThemesOptions>().Themes;
});
I'm not sure that I like that I have to set every property for the options. Alternatively I tried:
services.AddThemes(options => {
options = Configuration.GetSection("Themes").Get<ThemesOptions>();
});
And:
services.AddThemes(options => Configuration.GetSection("Themes"));
However when I inject IOptions<ThemesOptions>
the Themes
property is null.
An alternative to I changed my extension method to:
public static void AddThemes(this IServiceCollection services, IConfiguration configuration) {
services.Configure<ThemesOptions>(configuration.GetSection("Themes"));
}
Now I can say the following within my application's Startup ConfigureServices
method:
services.AddThemes(Configuration);
This worked fine, however, I feel the problem with this approach is that the extension method only allows the options to be set from the configuration.
I'd appreciate it if someone could confirm whether my first solution is correct and if it can be improved upon.
2 Answers 2
Instead of doing
services.AddThemes(options => {
options = Configuration.GetSection("Themes").Get<ThemesOptions>();
});
you could use
services.AddThemes(options => {
Configuration.GetSection("Themes").Bind(options);
});
which will programatically set each value of options based on what's in the configuration.
-
\$\begingroup\$ Thanks, I've changed your answer to be the accepted one as it specifically addresses the original question. \$\endgroup\$nfplee– nfplee2019年06月24日 16:35:38 +00:00Commented Jun 24, 2019 at 16:35
With your ThemesOptions
defined as:
public class ThemesOptions {
public IEnumerable<string> Themes { get; set; }
}
and Reference #1 Configure simple options with a delegate
public static void AddThemes(this IServiceCollection services, Action<ThemesOptions> configureOptions) {
//Options bound and configured by a delegate
services.Configure<ThemesOptions>(configureOptions);
}
Will allow the simple delegate to be used when configuring options services.
//Options bound and configured by a delegate
services.AddThemes(option => {
option.Themes = new [] { "Theme1", "Theme2" /*, "SomeOtherTheme" */};
});
Now assuming an app settings json file like
{
"option1": "value1_from_json",
"option2": -1,
"Themes": [
"Theme1", "Theme2"
]
}
and Reference #2 Suboptions configuration
you defined the following
public static void AddThemes(this IServiceCollection services, IConfiguration configuration) {
// Bind options using a sub-section of the appsettings.json file.
services.Configure<ThemesOptions>(configuration.GetSection("Themes"));
}
and called like
services.AddThemes(Configuration);
As you rightly stated, it works. Yes. That is because the ThemesOptions
class defines a property Themes
that holds a collection of strings which would match what GetSection("Themes")
would return and thus bind.
Now let's look at the ones that did not work and why they don't
In both cases
services.AddThemes(options => {
options = Configuration.GetSection("Themes").Get<ThemesOptions>();
});
And:
services.AddThemes(options => Configuration.GetSection("Themes"));
you are trying to set the value of a provided argument to the delegate which change the value of the object and in the second one, nothing is done with the option provided. When using the delegate option the value from the settings file are overridden by the configured delegate which is why Themes
property of injected IOptions<ThemesOptions>
is null
services.AddThemes(options => {
options.Themes = Configuration.GetSection("Themes").Get<ThemesOptions>().Themes;
});
works because you are populating the members of the passed parameter which was initialized by the options builder to the delegate.
If this configuration section has a value, that will be used. Otherwise binding by matching property names against configuration keys recursively
Each call to
Configure<TOptions>
adds anIConfigureOptions<TOptions>
service to the service container. When more than one configuration service is enabled, the last configuration source specified wins and sets the configuration value.
-
\$\begingroup\$ Is there no way to set the whole configuration without setting each property individually? \$\endgroup\$nfplee– nfplee2018年01月24日 21:47:44 +00:00Commented Jan 24, 2018 at 21:47
-
\$\begingroup\$ @nfplee clarify what you mean by the
whole configuration
? are you talking about the one classTheme
or all other options in the settings file? . \$\endgroup\$Nkosi– Nkosi2018年01月24日 21:51:56 +00:00Commented Jan 24, 2018 at 21:51 -
\$\begingroup\$ I'm using this as an example of a pattern. If in the example above ThemeOptions contained more than one property I'd like to be able to set every property in one line (ideally without using reflection). I had hoped that this was a common scenario. \$\endgroup\$nfplee– nfplee2018年01月25日 11:57:15 +00:00Commented Jan 25, 2018 at 11:57
-
1\$\begingroup\$ @nfplee if you look at the examples in the links provided you will see that is the convention used by core for options. If themes has multiple properties, the sub options route would populate all the matching properties on the options object \$\endgroup\$Nkosi– Nkosi2018年01月25日 11:59:54 +00:00Commented Jan 25, 2018 at 11:59
Explore related questions
See similar questions with these tags.