Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Core use Configuration to bind to Options with Array

Using the .NET Core Microsoft.Extensions.Configuration is it possible to bind to a Configuration to an object that contains an array?

ConfigurationBinder has a method BindArray, so I'd assume it would work.

But when I try it out I get an exception:

System.NotSupportedException: ArrayConverter cannot convert from System.String.

Here's my slimmed down code:

public class Test
{
   private class ExampleOption
   { 
      public int[] Array {get;set;}
   }

   [Test]
   public void CanBindArray()
   {
       // ARRANGE
       var config =
            new ConfigurationBuilder()
            .AddInMemoryCollection(new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("Array", "[1,2,3]")
            })
            .Build();

        var exampleOption= new ExampleOption();

        // ACT
        config.Bind(complexOptions); // throws exception

       // ASSERT
       exampleOption.ShouldContain(1);
   }
}
like image 499
Philip Pittle Avatar asked Jun 15 '16 02:06

Philip Pittle


3 Answers

The error is in your input definition. The sample sets a key "Array" to a string value of "[1,2,3]" (in the C# based InMemoryCollection) and makes the assumption it is parsed JSON style. That is wrong. It is just not parsed.

The encoding convention of array values in the config system is by repeating the key with a colon and an index behind it. The following sample works like you intend to do:

var config = new ConfigurationBuilder()
        .AddInMemoryCollection(new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string, string>("Array:0", "1"),
            new KeyValuePair<string, string>("Array:1", "2"),
            new KeyValuePair<string, string>("Array:2", "3")
        })
        .Build();

The colon-key-repeating scheme happens also if JSON file is used (here by an additional call to AddJsonFile) ...

{
  "mySecondArray":  [1, 2, 3]
}

the resulting combined configuration will contain the keys which follow the same pattern as illustrated for in-memory usage above:

Count = 8
[0]: {[mySecondArray, ]}
[1]: {[mySecondArray:2, 3]}
[2]: {[mySecondArray:1, 2]}
[3]: {[mySecondArray:0, 1]}
[4]: {[Array, ]}
[5]: {[Array:2, 3]}
[6]: {[Array:1, 2]}
[7]: {[Array:0, 1]}

The config system is agnostic to storage formats like JSON/INI/XML/... and is essentially just a string->string dictionary with colon making up a hierarchy within the key.

Bind is then able to interpret some of the hierarchy by conventions and therefore binds also arrays, collections, objects and dictionaries. Interestingly for arrays, it does not care about the numbers behind the colon but just iterate the children of the configuration section (here "Array") and take the values of the children. The sorting of the children again, takes the numbers into consideration but also sorts strings as a second option (OrdinalIgnoreCase).

like image 70
Thomas Avatar answered Nov 15 '22 08:11

Thomas


With recent additions to C# language it is cleaner to use the newer syntax:

var config = new ConfigurationBuilder()
    .AddInMemoryCollection(new Dictionary<string, string>
    {
        { "Array:0", "1" },
        { "Array:1", "2" },
        { "Array:2", "3" },
    })
    .Build();

or you can use other new syntax

var config = new ConfigurationBuilder()
    .AddInMemoryCollection(new Dictionary<string, string>
    {
        ["Array:0"] = "1",
        ["Array:1"] = "2",
        ["Array:2"] = "3",
    })
    .Build();
like image 39
codevision Avatar answered Nov 15 '22 07:11

codevision


You can configure ExampleOptionwith code in ConfigureServices method:

 public void ConfigureServices(IServiceCollection services)
 {
      services.Configure<ExampleOption>(myOptions =>
      {
          myOptions.Array = new int[] { 1, 2, 3 };
      });
 }

or if you want to use json configuration file

appsettings.json:

{
  "ExampleOption": {
     "Array": [1,2,3]
  }
}

ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ExampleOption>(Configuration.GetSection("ExampleOption"));
}
like image 7
adem caglin Avatar answered Nov 15 '22 06:11

adem caglin