绑定 .NET Core 无法识别的配置属性

Binding .NET Core Unrecognized configuration properties

假设我们有以下配置文件

{
  "TeamRoster:People:0:Name": "Bryan",
  "TeamRoster:People:0:City": "Toronto",
  "TeamRoster:People:0:Interests": "Code",

  "TeamRoster:People:1:Name": "Sebastian",
  "TeamRoster:People:1:City": "Vancouver",
  "TeamRoster:People:1:ApartmentType": "Condo"

}

以及以下代表此配置的 POCO:

public class TeamRoster
{
    public Person[] People { get; set; }
}

public class Person
{
   public string Name { get; set; }
   public string City { get; set; }
}

假设我正在使用 ConfigurationBuilder 来读取 dotnet 核心配置,例如:

IConfiguration config =  
     new ConfigurationBuilder()
           .AddJsonFile("file.json")
           .Build();

var myConfig = config.GetSection("TeamRoster").Get<TeamRoster>();

如何捕获不属于 Person 对象的其他属性(Interests、ApartmentType)?

我的推理超出了上面这个微不足道的例子。在我的模型中,我可能有许多附加属性(例如多态类型),我想捕获它们而不必在配置对象中定义它们。理想情况下,我想像 [JsonExtendedData] 那样做,但据我了解,即使数据表示为 Json 这只是来源。它被读入配置对象然后绑定。

有没有办法自定义 .net 核心绑定?

为了阐明这一点,Microsoft.Extensions.Configuration 中的 IConfigurationRoot 从其底层配置源中提取了配置数据。开箱即用,配置数据可以来自 Json, Azure KeyVault, Command Line, Environment Variables, Xml, Memory, Ini files, User secrets and individual files(每个文件的密钥)。

这些配置源中的每一个都将它们的数据加载到字典中,其中分层值使用“:”分隔。当您从 IConfiguration 对象获取数据时,它会尝试以相反的顺序从每个数据源解析值。因此最后添加的配置源具有最高的优先级。

IConfiguration 对象到 POCO 对象的映射是使用 ConfigurationBinder. While the ConfigurationBinder does support resolution of values using TypeConverters 完成的,这种情况仅有助于映射配置字典中的单个字符串值,而不是整个对象。

ConfigurationBinder原生支持绑定集合,所以理论上下面两种方式都可以用于下面的配置。

将固定值与动态值分开

表示数据,以便动态数据与静态数据分开。

{
   "auth": {
      "providers": [
         {
            "name": "clientsecret",
            "loginUrl": "https://login.microsoftonline.com/mytenant/oauth2",
            "fields": {
               "client_id": "xxx",
               "client_secret": "yyy"
               "resource": "https://management.azure.com",
               "grant_type": "client_credentials"
            }
         },
         ...
      ]
   }
}

将动态字段绑定到 Dictionary<string,string> 时,Binder 将字段映射为 Key/Value 对。这种方法的主要优点是我们能够利用固定字段的类型转换器和类型安全。

public class AuthProvider
{
    // FIXED FIELDS
    public string Name { get; set; }
    public Uri LoginUrl { get; set; }

    // DYNAMIC FIELDS
    public Dictionary<string,string> Fields { get; set; }
}

将所有数据映射为动态

稍微更 hackish 的路线是将数据中的所有字段描述为兄弟,然后将整个数据绑定为字典。

{
   "auth": {
     "providers": [
       {
         "name": "clientsecret",
         "loginurl": "https://somewhere",
         "client_id": "xxx",
         "client_secret": "yyy",
         "scope": "https://url",
         "grant_type": "client_credentials"
       }
     ]
   }
}

我们可以简单地利用直接绑定到字典的优势。请注意,我们使用丢失 Uri 字段的类型转换器:

public class AuthProvider : Dictionary<string,string>
{
  public string Name => this["name"]
  public string LoginUrl => this["loginUrl"]

  public Dictionary<string,string> Fields
  {
     get 
     {
       var propertyNames = GetType().GetProperties().Select(x => x.Name).ToArray();

       var keyPairs = this.Where(kvp => !propertyNames.Contains(kvp.Key));

       return 
          new Dictionary<string,string>(
                keyPairs, 
                StringComparer.OrdinalIgnoreCase);
     }
  }
}