在控制台应用程序上使用VS 2017。当我使用Dictionary<string, Guid>
时,一切正常,但是,当我使用Dictionary<Guid, Guid>
时,它不会映射到AppSettings对象(dict count为0)。有没有办法绕过它,而不是使用string
并将其转换为应用程序中的Guid
?
appsetting.json
{
"AppSettings": {
"DictionaryTest": {
"55789653-86A3-485C-95E8-5E23C3219130": "74F87895-4965-447A-B07F-F702573218B7",
"FB1E7891-B3C2-4E83-A6BF-7F321C11BFFA": "FA7892A9-7925-4150-82A7-5DD3213D1242"
}
}
}
Program.cs中的
var appPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Replace(@"file:\", "");
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile($@"{appPath}\appsettings.json");
Configuration = builder.Build();
appSettings = new AppSettings();
Configuration.GetSection("AppSettings").Bind(appSettings);
当我使用Dictionary<string, Guid>
方案时, appSettings.DictionaryTest
变量中有2项。
但是当我使用Dictionary<Guid, Guid>
方案时, appSettings.DictionaryTest
变量中有0项。
Dictionary<string, Guid>
方案:
AppSettings.cs
public class AppSettings
{
public Dictionary<string, Guid> DictionaryTest { get; set; }
}
Dictionary<Guid, Guid>
方案:
AppSettings.cs
public class AppSettings
{
public Dictionary<Guid, Guid> DictionaryTest { get; set; }
}
答案 0 :(得分:4)
目前,配置绑定器似乎不支持 Guid
作为键类型。唯一支持的类型是 string
和 Enum
类型。
这实际上在 source code 中有说明:
if (keyType != typeof(string) && !keyTypeIsEnum)
{
// We only support string and enum keys
return;
}
但我认为在其他任何地方都没有记录。
答案 1 :(得分:2)
虽然 Kevin Eaton 是正确的并且官方软件包不支持这一点,但没有什么能阻止您使用 Extension 方法无耻地复制该文件,并且只需几行新代码,自己实现这一点
下面的作品
//Install Nuget Package Microsoft.Extensions.Configuration.Binder
//Install Nuget Package Microsoft.Extensions.Configuration.Json
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder().AddJsonFile($"appsettings.json", true, true);
var configuration = builder.Build();
var appSettings = new AppSettings();
var configurationSection = configuration.GetSection("AppSettings");
configurationSection.BindWhichSupportsGuidAsKey(appSettings);
Console.WriteLine("Finished!");
}
}
public class AppSettings
{
public Dictionary<Guid, Guid> DictionaryTest { get; set; }
}
}
我所做的只是
将类重命名为 CustomConfigurationBinderExtesions
以便方法不会冲突
将 Bind
方法重命名为 BindWhichSupportsGuidAsKey
将if (keyType != typeof(string) && !keyTypeIsEnum)
改为
if (keyType != typeof(string) && keyType != typeof(Guid) && !keyTypeIsEnum)
添加了这个
else if (keyType == typeof(Guid))
{
Guid key = Guid.Parse(child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
以下是完整文件(原为here)
(你可以在这里删除大部分代码,只保留你需要的,但我懒得这样做:))
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// Static helper class that allows binding strongly typed objects to configuration values.
/// </summary>
public static class CustomConfigurationBinderExtesions
{
private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <typeparam name="T">The type of the new instance to bind.</typeparam>
/// <param name="configuration">The configuration instance to bind.</param>
/// <returns>The new instance of T if successful, default(T) otherwise.</returns>
//public static T Get<T>(this IConfiguration configuration)
// => configuration.Get<T>(_ => { });
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <typeparam name="T">The type of the new instance to bind.</typeparam>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
/// <returns>The new instance of T if successful, default(T) otherwise.</returns>
//public static T Get<T>(this IConfiguration configuration, Action<BinderOptions> configureOptions)
//{
// if (configuration == null)
// {
// throw new ArgumentNullException(nameof(configuration));
// }
// object result = configuration.Get(typeof(T), configureOptions);
// if (result == null)
// {
// return default(T);
// }
// return (T)result;
//}
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="type">The type of the new instance to bind.</param>
/// <returns>The new instance if successful, null otherwise.</returns>
//public static object Get(this IConfiguration configuration, Type type)
// => configuration.Get(type, _ => { });
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="type">The type of the new instance to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
/// <returns>The new instance if successful, null otherwise.</returns>
public static object Get(this IConfiguration configuration, Type type, Action<BinderOptions> configureOptions)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
var options = new BinderOptions();
configureOptions?.Invoke(options);
return BindInstance(type, instance: null, config: configuration, options: options);
}
/// <summary>
/// Attempts to bind the given object instance to the configuration section specified by the key by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="key">The key of the configuration section to bind.</param>
/// <param name="instance">The object to bind.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, string key, object instance)
=> configuration.GetSection(key).BindWhichSupportsGuidAsKey(instance);
/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, object instance)
=> configuration.BindWhichSupportsGuidAsKey(instance, o => { });
/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, object instance, Action<BinderOptions> configureOptions)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (instance != null)
{
var options = new BinderOptions();
configureOptions?.Invoke(options);
BindInstance(instance.GetType(), instance, configuration, options);
}
}
/// <summary>
/// Extracts the value with the specified key and converts it to type T.
/// </summary>
/// <typeparam name="T">The type to convert the value to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <returns>The converted value.</returns>
public static T GetValue<T>(this IConfiguration configuration, string key)
{
return GetValue(configuration, key, default(T));
}
/// <summary>
/// Extracts the value with the specified key and converts it to type T.
/// </summary>
/// <typeparam name="T">The type to convert the value to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <param name="defaultValue">The default value to use if no value is found.</param>
/// <returns>The converted value.</returns>
public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue)
{
return (T)GetValue(configuration, typeof(T), key, defaultValue);
}
/// <summary>
/// Extracts the value with the specified key and converts it to the specified type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="type">The type to convert the value to.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <returns>The converted value.</returns>
public static object GetValue(this IConfiguration configuration, Type type, string key)
{
return GetValue(configuration, type, key, defaultValue: null);
}
/// <summary>
/// Extracts the value with the specified key and converts it to the specified type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="type">The type to convert the value to.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <param name="defaultValue">The default value to use if no value is found.</param>
/// <returns>The converted value.</returns>
public static object GetValue(this IConfiguration configuration, Type type, string key, object defaultValue)
{
IConfigurationSection section = configuration.GetSection(key);
string value = section.Value;
if (value != null)
{
return ConvertValue(type, value, section.Path);
}
return defaultValue;
}
private static void BindNonScalar(this IConfiguration configuration, object instance, BinderOptions options)
{
if (instance != null)
{
foreach (PropertyInfo property in GetAllProperties(instance.GetType()))
{
BindProperty(property, instance, configuration, options);
}
}
}
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options)
{
// We don't support set only, non public, or indexer properties
if (property.GetMethod == null ||
(!options.BindNonPublicProperties && !property.GetMethod.IsPublic) ||
property.GetMethod.GetParameters().Length > 0)
{
return;
}
object propertyValue = property.GetValue(instance);
bool hasSetter = property.SetMethod != null && (property.SetMethod.IsPublic || options.BindNonPublicProperties);
if (propertyValue == null && !hasSetter)
{
// Property doesn't have a value and we cannot set it so there is no
// point in going further down the graph
return;
}
propertyValue = BindInstance(property.PropertyType, propertyValue, config.GetSection(property.Name), options);
if (propertyValue != null && hasSetter)
{
property.SetValue(instance, propertyValue);
}
}
private static object BindToCollection(Type type, IConfiguration config, BinderOptions options)
{
Type genericType = typeof(List<>).MakeGenericType(type.GenericTypeArguments[0]);
object instance = Activator.CreateInstance(genericType);
BindCollection(instance, genericType, config, options);
return instance;
}
// Try to create an array/dictionary instance to back various collection interfaces
private static object AttemptBindToCollectionInterfaces(Type type, IConfiguration config, BinderOptions options)
{
if (!type.IsInterface)
{
return null;
}
Type collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyList<>), type);
if (collectionInterface != null)
{
// IEnumerable<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyDictionary<,>), type);
if (collectionInterface != null)
{
Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(type.GenericTypeArguments[0], type.GenericTypeArguments[1]);
object instance = Activator.CreateInstance(dictionaryType);
BindDictionary(instance, dictionaryType, config, options);
return instance;
}
collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
object instance = Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(type.GenericTypeArguments[0], type.GenericTypeArguments[1]));
BindDictionary(instance, collectionInterface, config, options);
return instance;
}
collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyCollection<>), type);
if (collectionInterface != null)
{
// IReadOnlyCollection<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
// ICollection<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(IEnumerable<>), type);
if (collectionInterface != null)
{
// IEnumerable<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
return null;
}
private static object BindInstance(Type type, object instance, IConfiguration config, BinderOptions options)
{
// if binding IConfigurationSection, break early
if (type == typeof(IConfigurationSection))
{
return config;
}
var section = config as IConfigurationSection;
string configValue = section?.Value;
object convertedValue;
Exception error;
if (configValue != null && TryConvertValue(type, configValue, section.Path, out convertedValue, out error))
{
if (error != null)
{
throw error;
}
// Leaf nodes are always reinitialized
return convertedValue;
}
if (config != null && config.GetChildren().Any())
{
// If we don't have an instance, try to create one
if (instance == null)
{
// We are already done if binding to a new collection instance worked
instance = AttemptBindToCollectionInterfaces(type, config, options);
if (instance != null)
{
return instance;
}
instance = CreateInstance(type);
}
// See if its a Dictionary
Type collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
BindDictionary(instance, collectionInterface, config, options);
}
else if (type.IsArray)
{
instance = BindArray((Array)instance, config, options);
}
else
{
// See if its an ICollection
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
BindCollection(instance, collectionInterface, config, options);
}
// Something else
else
{
BindNonScalar(config, instance, options);
}
}
}
return instance;
}
private static object CreateInstance(Type type)
{
if (type.IsInterface || type.IsAbstract)
{
throw new InvalidOperationException("SR.Format(SR.Error_CannotActivateAbstractOrInterface, type)");
}
if (type.IsArray)
{
if (type.GetArrayRank() > 1)
{
throw new InvalidOperationException("SR.Format(SR.Error_UnsupportedMultidimensionalArray, type)");
}
return Array.CreateInstance(type.GetElementType(), 0);
}
if (!type.IsValueType)
{
bool hasDefaultConstructor = type.GetConstructors(DeclaredOnlyLookup).Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
if (!hasDefaultConstructor)
{
throw new InvalidOperationException("SR.Format(SR.Error_MissingParameterlessConstructor, type)");
}
}
try
{
return Activator.CreateInstance(type);
}
catch (Exception ex)
{
throw new InvalidOperationException("SR.Format(SR.Error_FailedToActivate, type), ex");
}
}
private static void BindDictionary(object dictionary, Type dictionaryType, IConfiguration config, BinderOptions options)
{
// IDictionary<K,V> is guaranteed to have exactly two parameters
Type keyType = dictionaryType.GenericTypeArguments[0];
Type valueType = dictionaryType.GenericTypeArguments[1];
bool keyTypeIsEnum = keyType.IsEnum;
if (keyType != typeof(string) && keyType != typeof(Guid) && !keyTypeIsEnum)
{
// We only support string and enum keys
return;
}
PropertyInfo setter = dictionaryType.GetProperty("Item", DeclaredOnlyLookup);
foreach (IConfigurationSection child in config.GetChildren())
{
object item = BindInstance(
type: valueType,
instance: null,
config: child,
options: options);
if (item != null)
{
if (keyType == typeof(string))
{
string key = child.Key;
setter.SetValue(dictionary, item, new object[] { key });
}
else if (keyType == typeof(Guid))
{
Guid key = Guid.Parse(child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
else if (keyTypeIsEnum)
{
object key = Enum.Parse(keyType, child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
}
}
}
private static void BindCollection(object collection, Type collectionType, IConfiguration config, BinderOptions options)
{
// ICollection<T> is guaranteed to have exactly one parameter
Type itemType = collectionType.GenericTypeArguments[0];
MethodInfo addMethod = collectionType.GetMethod("Add", DeclaredOnlyLookup);
foreach (IConfigurationSection section in config.GetChildren())
{
try
{
object item = BindInstance(
type: itemType,
instance: null,
config: section,
options: options);
if (item != null)
{
addMethod.Invoke(collection, new[] { item });
}
}
catch
{
}
}
}
private static Array BindArray(Array source, IConfiguration config, BinderOptions options)
{
IConfigurationSection[] children = config.GetChildren().ToArray();
int arrayLength = source.Length;
Type elementType = source.GetType().GetElementType();
var newArray = Array.CreateInstance(elementType, arrayLength + children.Length);
// binding to array has to preserve already initialized arrays with values
if (arrayLength > 0)
{
Array.Copy(source, newArray, arrayLength);
}
for (int i = 0; i < children.Length; i++)
{
try
{
object item = BindInstance(
type: elementType,
instance: null,
config: children[i],
options: options);
if (item != null)
{
newArray.SetValue(item, arrayLength + i);
}
}
catch
{
}
}
return newArray;
}
private static bool TryConvertValue(Type type, string value, string path, out object result, out Exception error)
{
error = null;
result = null;
if (type == typeof(object))
{
result = value;
return true;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (string.IsNullOrEmpty(value))
{
return true;
}
return TryConvertValue(Nullable.GetUnderlyingType(type), value, path, out result, out error);
}
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter.CanConvertFrom(typeof(string)))
{
try
{
result = converter.ConvertFromInvariantString(value);
}
catch (Exception ex)
{
error = new InvalidOperationException("SR.Format(SR.Error_FailedBinding, path, type), ex");
}
return true;
}
if (type == typeof(byte[]))
{
try
{
result = Convert.FromBase64String(value);
}
catch (FormatException ex)
{
error = new InvalidOperationException("SR.Format(SR.Error_FailedBinding, path, type), ex");
}
return true;
}
return false;
}
private static object ConvertValue(Type type, string value, string path)
{
object result;
Exception error;
TryConvertValue(type, value, path, out result, out error);
if (error != null)
{
throw error;
}
return result;
}
private static Type FindOpenGenericInterface(Type expected, Type actual)
{
if (actual.IsGenericType &&
actual.GetGenericTypeDefinition() == expected)
{
return actual;
}
Type[] interfaces = actual.GetInterfaces();
foreach (Type interfaceType in interfaces)
{
if (interfaceType.IsGenericType &&
interfaceType.GetGenericTypeDefinition() == expected)
{
return interfaceType;
}
}
return null;
}
private static IEnumerable<PropertyInfo> GetAllProperties(Type type)
{
var allProperties = new List<PropertyInfo>();
do
{
allProperties.AddRange(type.GetProperties(DeclaredOnlyLookup));
type = type.BaseType;
}
while (type != typeof(object));
return allProperties;
}
}
}
答案 2 :(得分:1)
尝试将 DictionaryTest 作为 JSON 并使用 JSON 库手动将其转换为 Dictionary
public class AppSettings
{
public JObject DictionaryTest { get; set; }
public Dictionary<Guid, Guid> DictionaryTestConvert
{
get { return JObject.toObject<Dictionary<Guid, Guid>>(DictionaryTestJson); }
}
}