133

I have an appsettings.json file which looks like this:

{
    "someSetting": {
        "subSettings": [
            "one",
            "two",
            "three"
         ]
    }
}

When I build my configuration root, and do something like config["someSetting:subSettings"] it returns null and the actual settings available are something like this:

config["someSettings:subSettings:0"]

Is there a better way of retrieving the contents of someSettings:subSettings as a list?

Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
devlife
  • 15,275
  • 27
  • 77
  • 131
  • May be this works.. https://weblog.west-wind.com/posts/2016/may/23/strongly-typed-configuration-settings-in-aspnet-core – Venkata Dorisala Aug 26 '16 at 15:37
  • Maybe. I'm using a console app which isn't asp.net but I'll see if i can get ahold of the services collection. – devlife Aug 26 '16 at 16:07
  • Yes, that works in console apps too. It's nothing asp.net specific – Victor Hurdugaci Aug 26 '16 at 17:16
  • I ask only because I'm getting the following: `Could not load file or assembly 'Microsoft.Extensions.Configuration.Binder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified` – devlife Aug 29 '16 at 19:15
  • You also can use a DTO class for parsing the config – VMAtm Oct 02 '17 at 23:02

6 Answers6

229

Assuming your appsettings.json looks like this:

{
  "foo": {
    "bar": [
      "1",
      "2",
      "3"
    ]
  }
}

You can extract the list items like so:

Configuration.GetSection("foo:bar").Get<List<string>>()
Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
  • 18
    This worked for me but I had to install the "Microsoft.Extensions.Configuration.Binder" NuGet package first, as described [here](https://stackoverflow.com/a/46541699/4751173). – Glorfindel Jul 05 '18 at 13:27
  • 1
    and to get a key value object pair you would create the C# classes with json2csharp, then use Configuration.GetSection( "foo" ).Get>() – Markus Mar 27 '19 at 12:04
  • 1
    This should be the anwer. Thanks @Glorfindel for the extra tip! – Casey Crookston May 22 '21 at 00:21
69

In .NetCore this is what I did:

Normal Setup:

In your appsettings.json create a configuration section for your custom definitions:

    "IDP": [
    {
      "Server": "asdfsd",
      "Authority": "asdfasd",
      "Audience": "asdfadf"
    },
    {
      "Server": "aaaaaa",
      "Authority": "aaaaaa",
      "Audience": "aaaa"
    }
  ]

Create a class to model the objects:

public class IDP
{
    public String Server { get; set; }
    public String Authority { get; set; }
    public String Audience { get; set; }

}

in your Startup -> ConfigureServices

services.Configure<List<IDP>>(Configuration.GetSection("IDP"));

Note: if you need to immediately access your list within your ConfigureServices method you can use...

var subSettings = Configuration.GetSection("IDP").Get<List<IDP>>();

Then in your controller something like this:

Public class AccountController: Controller
{
    private readonly IOptions<List<IDP>> _IDPs;
    public AccountController(IOptions<List<Defined>> IDPs)
    {
        _IDPs = IDPs;
    }
  ...
}

just as an example I used it elsewhere in the above controller like this:

       _IDPs.Value.ForEach(x => {
            // do something with x
        });

Edge Case

In the case that you need multiple configs but they can't be in an array and you have no idea how many sub-settings you will have at any one time. Use the following method.

appsettings.json

"IDP": {
    "0": {
      "Description": "idp01_test",
      "IDPServer": "https://intapi.somedomain.com/testing/idp01/v1.0",
      "IDPClient": "someapi",
      "Format": "IDP"
    },
    "1": {
      "Description": "idpb2c_test",
      "IDPServer": "https://intapi.somedomain.com/testing/idpb2c",
      "IDPClient": "api1",
      "Format": "IDP"
    },
    "2": {
      "Description": "MyApp",
      "Instance": "https://sts.windows.net/",
      "ClientId": "https://somedomain.com/12345678-5191-1111-bcdf-782d958de2b3",
      "Domain": "somedomain.com",
      "TenantId": "87654321-a10f-499f-9b5f-6de6ef439787",
      "Format": "AzureAD"
    }
  }

Model

public class IDP
{
    public String Description { get; set; }
    public String IDPServer { get; set; }
    public String IDPClient { get; set; }
    public String Format { get; set; }
    public String Instance { get; set; }
    public String ClientId { get; set; }
    public String Domain { get; set; }
    public String TenantId { get; set; }
}

Create Extension for Expando Object

public static class ExpandObjectExtension
    {
        public static TObject ToObject<TObject>(this IDictionary<string, object> someSource, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public)
               where TObject : class, new()
        {
            Contract.Requires(someSource != null);
            TObject targetObject = new TObject();
            Type targetObjectType = typeof(TObject);

            // Go through all bound target object type properties...
            foreach (PropertyInfo property in
                        targetObjectType.GetProperties(bindingFlags))
            {
                // ...and check that both the target type property name and its type matches
                // its counterpart in the ExpandoObject
                if (someSource.ContainsKey(property.Name)
                    && property.PropertyType == someSource[property.Name].GetType())
                {
                    property.SetValue(targetObject, someSource[property.Name]);
                }
            }

            return targetObject;
        }
    }

ConfigureServices

var subSettings = Configuration.GetSection("IDP").Get<List<ExpandoObject>>();

var idx = 0;
foreach (var pair in subSettings)
{

    IDP scheme = ((ExpandoObject)pair).ToObject<IDP>();
    if (scheme.Format == "AzureAD")
    {
        // this is why I couldn't use an array, AddProtecedWebApi requires a path to a config section
        var section = $"IDP:{idx.ToString()}";
        services.AddProtectedWebApi(Configuration, section, scheme.Description);
        // ... do more stuff
        
    }
    idx++;
}
Dhrutika Rathod
  • 540
  • 2
  • 6
  • 22
Post Impatica
  • 14,999
  • 9
  • 67
  • 78
  • I created a class to bind to `public class Definitions : List {}`. ` { "Definitions": [ { "Name": "somename", "Title": "sometitle", "Image": "some image url" }, { "Name": "somename", "Title": "sometitle", "Image": "some image url" } ] }` – devmb Sep 23 '19 at 07:59
53

You can use the Configuration binder to get a strong type representation of the configuration sources.

This is an example from a test that I wrote before, hope it helps:

    [Fact]
    public void BindList()
    {
        var input = new Dictionary<string, string>
        {
            {"StringList:0", "val0"},
            {"StringList:1", "val1"},
            {"StringList:2", "val2"},
            {"StringList:x", "valx"}
        };

        var configurationBuilder = new ConfigurationBuilder();
        configurationBuilder.AddInMemoryCollection(input);
        var config = configurationBuilder.Build();

        var list = new List<string>();
        config.GetSection("StringList").Bind(list);

        Assert.Equal(4, list.Count);

        Assert.Equal("val0", list[0]);
        Assert.Equal("val1", list[1]);
        Assert.Equal("val2", list[2]);
        Assert.Equal("valx", list[3]);
    }

The important part is the call to Bind.

The test and more examples are on GitHub

Nick D
  • 531
  • 4
  • 15
Victor Hurdugaci
  • 28,177
  • 5
  • 87
  • 103
12
var settingsSection = config.GetSection["someSettings:subSettings"];
var subSettings = new List<string>;

foreach (var section in settingsSection.GetChildren())
{
    subSettings.Add(section.Value);
}

This should give you the values you need, stored in subSettings

Apologies for bringing up a semi-old thread. I had difficulty finding an answer as a good amount of methods are deprecated, like Get and GetValue. This should be fine if you only need a simple solution without the configuration binder. :)

Marc Shepherd
  • 143
  • 1
  • 6
6

In my case configuration

 services.Configure<List<ApiKey>>(Configuration.GetSection("ApiKeysList"));

wasn't loaded because the properties were read-only and there were no default constructor

In other case a class had public fields instead of properties.

//Not working

  public class ApiKey : IApiKey
    {
        public ApiKey(string key, string owner)
        {
            Key = key;
            OwnerName = owner;
        }
        public string Key { get;  }
        public string OwnerName { get;}
    } 

//Working

    public class ApiKey : IApiKey
    {
        public ApiKey(){}//Added default constructor
        public ApiKey(string key, string owner)
        {
            Key = key;
            OwnerName = owner;
        }
        public string Key { get; set; }        //Added set property
        public string OwnerName { get; set; }  //Added set property
    } 
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
0

Just getting the whole section will populate the List property; in a settings class.

services.Configure<Settings>(configuration.GetSection("Another:Some:Example"));

But... do remember that if defaults are set in the settings class for that List... that the configuration settings will be additive and so not overwriting the original values.

So these defaults will remain and so are really "no way you can delete them via any additional configuration"

 public List<string> NonEditableStuff { get; set; } = new() { "XYZ1", "LTOY3" };

Also, if you also have turned on the Ini file provider might be be handy to know that to specify the list there the keys do not really matter as long as they are unique, so it makes sense to keep the key and the values there the same to end up in the list.

[Another:Some:Example:NonEditableStuff]
value=value
whatever2=whatever2
edelwater
  • 2,650
  • 8
  • 39
  • 67