Display Name
Display Name

Reputation: 15101

Comparing several ways to inject configuration

I am learning Dependency Injection provided by .net core. So far, I have known 5 ways as follows.

// 0
sc.AddSingleton(configuration);

//1
sc.Configure<ServiceOption>(_ => configuration.GetSection(nameof(ServiceOption)).Bind(_));

//2
sc.Configure<ServiceOption>(configuration.GetSection(nameof(ServiceOption)));

//3
sc.AddOptions<ServiceOption>()
.Bind(configuration.GetSection(nameof(ServiceOption)));

//4
sc.Configure<ServiceOption>(_ => _.ID = "from hard code");

Question

The last one (#4) is not so interesting to me, so let ignore it. I want to know, when do we need to use each of the remaining ones?

Here is the source code.

appsettings.json file:

{
  "ServiceOption": {
    "ID": "from appsettings.json"
  }
}

Code:

interface IService
{
    void Do();
}

class Service : IService
{
    readonly ServiceOption option;
    readonly IConfigurationRoot configuration;

    public Service(IOptionsMonitor<ServiceOption> option)
    {
        this.option = option.CurrentValue;
    }

    public Service(IConfigurationRoot configuration)
    {
        this.configuration = configuration;
    }

    public void Do(){}
}

class ServiceOption
{
    public string ID { get; set; }
}



class Program
{
    private static readonly IConfigurationRoot configuration;

    static Program()
    {
        configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true)
            .Build();
    }

    static void Main()
    {
        using (ServiceProvider sp = RegisterServices())
        {
            sp.GetRequiredService<IService>().Do();
        }
    }

    static ServiceProvider RegisterServices()
    {
        ServiceCollection sc = new ServiceCollection();
        sc.AddTransient<IService, Service>();

        int selector = 0;

        switch (selector)
        {
            case 0:
                sc.AddSingleton(configuration);
                break;
            case 1:
                sc.Configure<ServiceOption>(_ => configuration.GetSection(nameof(ServiceOption)).Bind(_));
                break;
            case 2:
                sc.Configure<ServiceOption>(configuration.GetSection(nameof(ServiceOption)));
                break;
            case 3:
                sc.AddOptions<ServiceOption>()
                .Bind(configuration.GetSection(nameof(ServiceOption)));
                break;
            default:
                sc.Configure<ServiceOption>(_ => _.ID = "from hard code");
                break;
        }

        return sc.BuildServiceProvider();
    }
}

Upvotes: 0

Views: 279

Answers (2)

Nkosi
Nkosi

Reputation: 247153

This answer is to add an additional option that was excluded

//bind to an object graph using `ConfigurationBinder.Get<T>`
ServiceOption setting = configuration.GetSection(nameof(ServiceOption)).Get<ServiceOption>();
//adding that to the service collection as needed
sc.AddSingleton(setting);

ConfigurationBinder.Get<T> binds and returns the specified type. Get<T> is more convenient than using Bind.

In the above the option can be explicitly injected into dependents

public Service(ServiceOption option) {
    this.option = option;
}

Its reasoning follows similar to an already provided answer if you do not want to couple your dependent code directly to framework related concerns.

Reference Configuration in ASP.NET Core: Bind to an object graph

Ideally you wouldn't want to be injecting IConfiguration or IOptions if you do not want your application code taking on unnecessary dependencies on framework abstractions.

Upvotes: 2

Camilo Terevinto
Camilo Terevinto

Reputation: 32068

  1. sc.AddSingleton(configuration);

    This registers a single instance of the class. This is useful when you don't want the consuming code to have to know about Microsoft.Extensions.Options. Another common scenario is when you are not going to use the features provided by the Options pattern.

  2. sc.Configure<ServiceOption>(_ => configuration.GetSection(nameof(ServiceOption)).Bind(_));

    This allows you to use a function for the configuration of the options. The syntax you used, however, is quite weird since you are just binding to the already built Configuration.

  3. sc.Configure<ServiceOption>(configuration.GetSection(nameof(ServiceOption)));

    This does the same as above, but binds directly to the Configuration instance.

  4. sc.AddOptions<ServiceOption>().Bind(configuration.GetSection(nameof(ServiceOption)));

    This just gives you an OptionBuilder<T> which you can use to call Bind, Configure and PostConfigure in a single fluent call.

Upvotes: 2

Related Questions