Chris
Chris

Reputation: 8432

ServiceCollection returns null for IOptions even though GetSection is working

I'm having trouble manually constructing a IServiceProvider that will allow my unit tests to use it to pull in shared test configuration using GetService<IOptions<MyOptions>>

I created some unit tests to illustrate my problems, also the repo for this can be found here if it's useful in answering the question.

The JSON

{
  "Test": {
    "ItemOne":  "yes"
  }
}

The Options Class

public class TestOptions
{
    public string ItemOne { get; set; }
}

The Tests

Out of these tests ConfigureWithBindMethod and ConfigureWithBindMethod both fail, where SectionIsAvailable passes. So the section is being consumed as expected from the JSON file as far as I can tell.

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void ConfigureWithoutBindMethod()
    {
        var collection = new ServiceCollection();

        var config = new ConfigurationBuilder()
            .AddJsonFile("test.json", optional: false)
            .Build();

        collection.Configure<TestOptions>(config.GetSection("Test"));

        var services = collection.BuildServiceProvider();

        var options = services.GetService<IOptions<TestOptions>>();

        Assert.IsNotNull(options);
    }

    [TestMethod]
    public void ConfigureWithBindMethod()
    {
        var collection = new ServiceCollection();

        var config = new ConfigurationBuilder()
            .AddJsonFile("test.json", optional: false)
            .Build();

        collection.Configure<TestOptions>(o => config.GetSection("Test").Bind(o));

        var services = collection.BuildServiceProvider();

        var options = services.GetService<IOptions<TestOptions>>();

        Assert.IsNotNull(options);
    }

    [TestMethod]
    public void SectionIsAvailable()
    {
        var collection = new ServiceCollection();

        var config = new ConfigurationBuilder()
            .AddJsonFile("test.json", optional: false)
            .Build();

        var section = config.GetSection("Test");
        Assert.IsNotNull(section);
        Assert.AreEqual("yes", section["ItemOne"]);
    }
}

Possibly useful to point out

When calling config.GetSection("Test") in the immediate window, I get this value

{Microsoft.Extensions.Configuration.ConfigurationSection}
    Key: "Test"
    Path: "Test"
    Value: null

At face value I'd have assumed Value should not be null, which is leading me to think I may be missing something obvious here, so if anyone can spot what I'm doing wrong that'd be genius.

Upvotes: 18

Views: 14314

Answers (2)

granadaCoder
granadaCoder

Reputation: 27894

First off, upvote kloarubeek's answer. I was stuck before I got through it.

I have a different twist.

This did not work (emphasis on 'AddSingleton')

        MyCustomConfigurationPoco concreteMyCustomConfigurationPoco =
            new MyCustomConfigurationPoco();
        configuration.GetSection("MyJsonName")
            .Bind(concreteMyCustomConfigurationPoco);

        servColl.AddSingleton<MyCustomConfigurationPoco>(concreteMyCustomConfigurationPoco);

But changing to this did work:

        MyCustomConfigurationPoco concreteMyCustomConfigurationPoco =
            new MyCustomConfigurationPoco();
        configuration.GetSection("MyJsonName")
            .Bind(concreteMyCustomConfigurationPoco);

/* my caveat here */

        servColl.Configure<MyCustomConfigurationPoco>(option =>
        {
            option.SomeScalarPropertyOne = concreteMyCustomConfigurationPoco.SomeScalarPropertyOne;
            option.SomeScalarPropertyTwo = concreteMyCustomConfigurationPoco.SomeScalarPropertyTwo;
        });

You might be asking "How is this different from the other answer?"

I actually had to set a value from somewhere besides .json before injecting it into the IoC.

So where I have above.

/* my caveat here */

imagine code like this

option.SomeScalarPropertyTwo = AnotherThing.GetSomeScalarPropertyTwo();

So I had to manipulate the MyCustomConfigurationPoco (instantiation) before injecting it into the IoC.

But now my "getter" code works as seen below:

            IOptions<MyCustomConfigurationPoco> concreteMyCustomConfigurationPocoOptions =
                servProv.GetService<IOptions<MyCustomConfigurationPoco>>();

            if (null == concreteMyCustomConfigurationPocoOptions)
            {
                throw new ArgumentNullException(nameof(concreteMyCustomConfigurationPocoOptions));
            }

            MyCustomConfigurationPoco concreteMyCustomConfigurationPoco =
                concreteMyCustomConfigurationPocoOptions.Value;

            if (null == concreteMyCustomConfigurationPoco)
            {
                throw new ArgumentNullException(nameof(concreteMyCustomConfigurationPoco));
            }

Upvotes: 1

kloarubeek
kloarubeek

Reputation: 2854

To use options in your service collection, you need to add the service required for using options collection.AddOptions();

This should do the trick:

[TestMethod]
public void ConfigureWithoutBindMethod()
{
    var collection = new ServiceCollection();
    collection.AddOptions();

    var config = new ConfigurationBuilder()
        .AddJsonFile("test.json", optional: false)
        .Build();

    collection.Configure<TestOptions>(config.GetSection("Test"));

    var services = collection.BuildServiceProvider();

    var options = services.GetService<IOptions<TestOptions>>();

    Assert.IsNotNull(options);
}

Upvotes: 31

Related Questions