如何从 .net core 的 appsettings.json 中提取列表

135

我有一个名为appsettings.json的文件,其内容如下:

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

当我构建配置根时,像 config["someSetting:subSettings"] 这样做会返回 null,而实际可用的设置如下:

config["someSettings:subSettings:0"]

有没有更好的方法将 someSettings:subSettings 的内容作为列表检索?


也许这个可行... https://weblog.west-wind.com/posts/2016/may/23/strongly-typed-configuration-settings-in-aspnet-core - Venkata Dorisala
也许可以。我正在使用控制台应用程序,而不是asp.net,但我会看看能否获取服务集合。 - devlife
是的,在控制台应用程序中也可以使用。这与 asp.net 无关。 - Victor Hurdugaci
我之所以问这个问题,是因为我得到了以下错误信息:无法加载文件或程序集'Microsoft.Extensions.Configuration.Binder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'。系统找不到指定的文件 - devlife
你也可以使用DTO类来解析配置。 - VMAtm
6个回答

234
假设你的appsettings.json看起来像这样:
{
  "foo": {
    "bar": [
      "1",
      "2",
      "3"
    ]
  }
}
您可以这样提取列表项:
Configuration.GetSection("foo:bar").Get<List<string>>()

18
这对我有用,但我首先需要安装“Microsoft.Extensions.Configuration.Binder”NuGet包,如此处所述。 - Glorfindel
1
要获取键值对对象,您需要使用json2csharp创建C#类,然后使用Configuration.GetSection("foo").Get<List<Bar>>()。 - Markus
1
这应该是答案。感谢@Glorfindel的额外提示! - Casey Crookston
您也可以手动查看各个部分:Configuration.GetSection("foo").GetSection("bar").Get<List<string>>(). - undefined

69
在 .NetCore 中,我这样做:

普通设置:

在您的 appsettings.json 中创建一个配置部分,用于您的自定义定义:

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

创建一个类来模拟对象:
public class IDP
{
    public String Server { get; set; }
    public String Authority { get; set; }
    public String Audience { get; set; }

}

在你的启动文件中 -> 配置服务。
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>>();
然后在你的控制器中,可以像这样编写代码:
Public class AccountController: Controller
{
    private readonly IOptions<List<IDP>> _IDPs;
    public AccountController(IOptions<List<Defined>> IDPs)
    {
        _IDPs = IDPs;
    }
  ...
}

举个例子,我在上述控制器中的其他地方使用它,如下所示:

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

边缘情况

在需要多个配置但不能使用数组且您无法确定任何时候将有多少子设置的情况下,请使用以下方法。

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"
    }
  }

模型

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; }
}

创建Expando对象的扩展
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;
        }
    }

配置服务

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++;
}

我创建了一个类来绑定到public class Definitions : List<Defined> {}。{ "Definitions": [ { "Name": "某个名称", "Title": "某个标题", "Image": "某个图片链接" }, { "Name": "某个名称", "Title": "某个标题", "Image": "某个图片链接" } ] } - devmb

54

您可以使用配置绑定器获取配置源的强类型表示形式。

这是我之前编写的一个测试示例,希望对您有所帮助:

    [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]);
    }
重要的部分是调用Bind
测试和更多示例在GitHub上。

1
备忘录:依赖于Microsoft.Extensions.Configuration.Binder。 - Stefan Steiger
4
有没有什么技巧可以使用 services.Configure<TOptions> 实现这个?我想从配置字段中注入包含数组的选项。 - Adam

12
var settingsSection = config.GetSection["someSettings:subSettings"];
var subSettings = new List<string>;

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

这应该会给你所需的值,存储在subSettings中。

抱歉,提出一个半古老的主题。我很难找到答案,因为许多方法已被弃用,例如GetGetValue。如果你只需要简单的解决方案而不需要配置绑定器,那么这应该是可以的。:)


1
一个简单的答案,但对我来说需要做一些更改:var settingsSection = config.GetSection("someSettings:subSettings");。换句话说,[]被()所替代。 - ehsan_kabiri_33
这个对我的使用情况来说实际上是最好的。 - Jon Upchurch

6

在我的情况下配置

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

由于属性是只读的且没有默认构造函数,因此未加载。

在另一种情况下,一个类使用了公共字段而不是属性。

//无法工作

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

//正在工作

    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
    } 

0

只需获取整个部分即可填充列表属性,在设置类中。

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

但是...请记住,如果在设置类中为该列表设置了默认值...那么配置设置将是累加的,因此不会覆盖原始值。

因此这些默认值将保留,因此真正的“无法通过任何其他配置方式删除它们”

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

此外,如果你也启用了Ini文件提供程序,可能会很方便知道指定列表时键名并不重要,只要它们是唯一的,因此将键和值保持相同以最终出现在列表中是有意义的。
[Another:Some:Example:NonEditableStuff]
value=value
whatever2=whatever2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接