Reputation: 16145
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?
Upvotes: 147
Views: 143197
Reputation: 16373
Better solution is now here
MS talking about in in .net 8 here
In .NetCore this is what I did:
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
});
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++;
}
Upvotes: 71
Reputation: 69928
You can use this with options pattern as well.
{
"Foo": {
"Bar": [
"1",
"2",
"3"
]
}
}
Class:
public class Foo
{
public required List<string> Bar { get; set; }
}
Program.cs:
builder.Services.Configure<Foo>(builder.Configuration.GetSection("Foo"));
Usage:
public class Test2Model : PageModel
{
private readonly Foo _options;
public Test2Model(IOptions<Foo> options)
{
_options = options.Value;
}
}
Upvotes: 0
Reputation: 28425
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
}
Upvotes: 7
Reputation: 2802
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
Upvotes: 0
Reputation: 162
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. :)
Upvotes: 14
Reputation: 28425
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
Upvotes: 54
Reputation: 43791
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>>()
Upvotes: 251