Pure.Krome
Pure.Krome

Reputation: 86957

How should I use appsettings.json config key/values in my ConfigurationServices ASP.NET Core 2.0 on startup?

I'm trying to configure my services for an ASP.NET Core 2.0 app/website.

I wish to reference some key/values from my appsettings.json file, in this method.

I'm not sure if what I'm going is OK or not:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore()
            .AddJsonFormatters()
            .AddCors();

    var applicationSettings = Configuration.GetSection("Settings").Get<ApplicationSettings>();
    services.AddSingleton(applicationSettings);

    // ** THIS IS WHAT I ORIGINALLY HAD, BUT IT'S ONLY SETTING
    //    THE VALUE IN DI/IOC.
    //services.Configure<ApplicationSettings>(options => Configuration.GetSection("Settings")
    //                                                                .Bind(options));

    var foo = new Foo(applicationSettings.SomeSetting);
    services.AddSingleton(foo);  
}

See how i'm manually adding a singleton and then later, referring a value from the app settings instance?

vs

just configuring ...

So, is either way OK or is there a specific reason for either/or?

Remember -> i'm going to need to inject my settings into controllers, etc...

Upvotes: 2

Views: 1784

Answers (2)

poke
poke

Reputation: 387677

Technically, you can do either. In both cases, you have the configuration registered and available through dependency injection, so everything can depend on it and will get the configuration instance.

You are also using the centrally set up Configuration there, so you have all the benefits from the configuration stack there, e.g. multiple providers or environment specific overrides.

However, the favor has definitely moved to the IOptions way of consuming custom configuration. It’s the “state of the art” and used throughout ASP.NET Core for literally everything. It also allows you to switch to options that can be updated at runtime. That’s very powerful and might become useful eventually (not necessarily for your specific situation with the singleton, but maybe for something else).

It’s also really easy to set this up, actually shorter than what you tried:

services.Configure<ApplicationSettings>(Configuration.GetSection("Settings"));

services.AddSingleton<Foo>();

Note that, even for singletons, you shouldn’t explicitly create a new instance of it, but let DI handle that. If your class has the correct constructor, dependencies will be automatically injected anyway:

public class Foo
{
    private readonly ApplicationSettings _settings;

    public Foo (IOptions<ApplicationSettings> settings)
    {
        _settings = settings.Value;
    }
}

Of course, Foo can also have more dependencies here. Since it’s going to be constructed by DI, you can just add more dependencies in the constructor, without having to update some new call somewhere.


If you need to configure certain services with settings that depend on your configuration, you still should not bind your configuration there directly. All of configuration is DI-based, so you just need to inject the right thing; a IConfigureOptions<T>. That’s basically the thing that provides the IOptions<T> to services later. In your JWT case, this could look like this:

// instead of passing an option configuration delegate here…
services.AddAuthentication().AddJwtBearer();

// … we register a IConfigureOptions<JwtBearerOptions> instead
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>();

// … ConfigureJwtBearerOptions could look like this:
class ConfigureJwtBearerOptions : IConfigureOptions<JwtBearerOptions>
{
    private readonly ApplicationSettings _settings;

    public ConfigureJwtBearerOptions(IOptions<ApplicationSettings> settings)
    {
        _settings = settings.Value;
    }

    public void Configure(JwtBearerOptions options)
    {
        // configure JwtBearerOptions here, and use your ApplicationSettings
        options.MetadataAddress = _settings.JwtMetadataAddress;
    }
}

This might seem unnecessarily verbose compared to just passing a delegate to AddJwtBearer() but note that this is exactly what happens under the hood when you pass that delegate: An IConfigureOptions<JwtBearerOptions> object will be created that calls your delegate in the Configure() call. So this is really just the same.

Note that for authentication schemes, you might actually set up a IConfigureNamedOptions<T> instead, which is almost the same thing except it can configure the options based on a name. For authentication schemes, that is the scheme name, so basically you check the scheme name in Configure() and then decide how to configure your options.


As for creating singleton instances, especially expensive ones, I would argue that ConfigureServices is the wrong place for such a thing. ConfigureServices is called very early in the application startup phase, when the whole DI infrastructure does not exist yet. So you could not rely on anything when creating your instance. I would also argue that it is still not your job to create the object but you should DI handle the creation of it and as such give it also control over its lifecycle.

If you absolutely need to control when the instance is created, I would suggest you to use the lifecycle events for this: Basically, after the application has set up properly but before a first request comes in, you request the instance of your services and initialize it. That way, you can still have it fully depend on DI, and it won’t be created lazily with the first request.

You can register lifecycle handlers in the Configure method:

public void Configure(IApplicationBuilder app, IApplicationLifetime applicationLifetime)
{
    applicationLifetime.ApplicationStarted.Register(() =>
    {
        // application has started, request the singleton here to trigger DI to
        // create the instance
        app.ApplicationServices.GetService<ExpensiveSingleton>();
    });

    // …
}


}

Upvotes: 4

Mark Priem
Mark Priem

Reputation: 397

Well the problem with that approach is that it will be impossible to load multiple configuration sections through DI. The Configuration API has many features, such as pluggable configuration provides, snapshots, etc. I would suggest you at least use a class to bind you configuration section against, so DI can inject it based on its type. If you further down the line have need to another configuration class you won't run into issues.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration

Upvotes: 0

Related Questions