Injecting Configuration Settings in ASP.NET Core
ASP.NET Core has a very robust Dependency Injection (DI) framework. Configuring the application is always necessary at some level. Most applications have a need to inject configuration settings into the DI container for other classes to use. Let me show one way that has worked well for me.
In this example, a class is responsible for returning a set of values. The number of values returned by the class is controlled via a configuration setting:
public class ValueSettings
{
public int ValuesToReturn { get; set; }
}
public class ValueService
{
private readonly ValueSettings _settings;
public ValueService(ValueSettings settings)
{
_settings = settings;
}
}
To inject the ValueSettings
, first you need to load the settings from the configuration. Here is one way:
services.Configure<ValueSettings>(configuration.GetSection(nameof(ValueSettings)));
services.AddSingleton(e => e.GetService<IOptions<ValueSettings>>().Value);
NOTE: configuration
is the instance of IConfiguration
injected into the Startup
class by the ASP.NET Core hosting environment.
The first statement uses the built-in IOptions
framework that implements the Options pattern.
It injects an instance of IOptions<ValueSettings>
into the DI Container.
The ValueService
requires the DI Container to provide ValueSettings
rather than IOptions<ValueSettings>
. The
second statement creates an additional entry in the DI container for ValueSettings
.
One might wonder why the ValueService
doesn’t accept IOptions<ValueSettings>
from the DI Container? Well, there are
a couple of reasons. First, it may not be possible to modify the source code that defines ValueService
. It may be contained in
an assembly outside of your control. Second, accepting IOptions<ValueSettings>
from the DI Container requires your service
class to accept an additional dependency (on IOptions
). This is what I’d call a ‘framework dependency’ which is not something you typically want to include in your core domain model. Regardless of your reasons, injecting the ValueSettings
can be a desirable design.
If you also want to avoid a dependency of the DI container on the IOptions
here is an alternate way to load the settings:
var settings = new ValueSettings();
configuration.Bind(nameof(ValueSettings), settings);
services.AddSingleton(settings);
I prefer to create an extension method to help make loading configuration more readable:
public static void ConfigureSettings<TSettings>(this IServiceCollection services, IConfiguration configuration)
where TSettings : class, new()
{
var settings = BindSection<TSettings>(configuration, typeof(TSettings).Name);
services.AddSingleton(settings);
}
public static TConfig BindSection<TConfig>(this IConfiguration configuration, string section) where TConfig : class, new()
{
var config = new TConfig();
configuration.Bind(section, config);
return config;
}
...
services.ConfigureSettings<ValueSettings>(Configuration);