7
\$\begingroup\$

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.

dfhwze
14.1k3 gold badges40 silver badges101 bronze badges
asked Jan 17, 2018 at 11:24
\$\endgroup\$
0

2 Answers 2

7
\$\begingroup\$

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.

answered Jun 14, 2019 at 19:53
\$\endgroup\$
1
  • \$\begingroup\$ Thanks, I've changed your answer to be the accepted one as it specifically addresses the original question. \$\endgroup\$ Commented Jun 24, 2019 at 16:35
4
\$\begingroup\$

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 an IConfigureOptions<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.

answered Jan 22, 2018 at 23:53
\$\endgroup\$
4
  • \$\begingroup\$ Is there no way to set the whole configuration without setting each property individually? \$\endgroup\$ Commented Jan 24, 2018 at 21:47
  • \$\begingroup\$ @nfplee clarify what you mean by the whole configuration? are you talking about the one class Theme or all other options in the settings file? . \$\endgroup\$ Commented 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\$ Commented 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\$ Commented Jan 25, 2018 at 11:59

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.