[Asp.net 5] Configuration-新一代的配置文件(神奇的Binder)


關於配置文件的目錄:[Asp.net 5] Configuration-新一代的配置文件

之前看過MVC4.0的源碼,里面就有Binder。作用是將前台頁面傳遞過來的鍵值對/字典表綁定到特定的對象。此處的Binder幾乎是同樣的作用——將IConfiguration映射為特定的類型<T>.

我們進入正文之前還是看一個例子比較好,這樣能充分了解Binder。

public class ComplexOptions
{
    public ComplexOptions()
    {
        Virtual = "complex";
    }
    public int Integer { get; set; }
    public bool Boolean { get; set; }
    public virtual string Virtual { get; set; }
}

public class Test
{
    public IConfiguration BuildConfiguration()
    {
        var dic = new Dictionary<string, string>
                 {
                     {"Integer", "-2"},
                     {"Boolean", "TRUe"},
                     {"Nested:Integer", "11"}
                };
        var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
        return config = builder.Build();
    }
    public void BinderTest()
    {
        var config = BuildConfiguration();
        var options = ConfigurationBinder.Bind<ComplexOptions>(config);
        Assert.True(options.Boolean);
        Assert.Equal(-2, options.Integer);
    }
}

上面例子比較簡單,實際上對象可能是復合對象(對象的屬性還是對象),也可能是數組、枚舉、結構體等。所以我們創建的過程中需要對屬性遍歷,如果是復合對象,還涉及到遞歸的過程,所以我把整個過程繪制成類似流程圖的圖標。如下圖所示:

 

下面我們正式介紹工程的源碼,按照慣例還是上工程結構圖(就那么一個文件,搞什么搞)

 

整個工程只有ConfigurationBinder這一個類,調用過程上面“流程圖”已經畫出來了。

 public static class ConfigurationBinder
    {
        public static TModel Bind<TModel>(IConfiguration configuration) where TModel : new()
        {
            var model = new TModel();
            Bind(model, configuration);
            return model;
        }

        public static void Bind(object model, IConfiguration configuration)
        {
            if (model == null)
            {
                return;
            }

            BindObjectProperties(model, configuration);
        }

        private static void BindObjectProperties(object obj, IConfiguration configuration)
        {
            foreach (var property in GetAllProperties(obj.GetType().GetTypeInfo()))
            {
                BindProperty(property, obj, configuration);
            }
        }

        private static void BindProperty(PropertyInfo property, object propertyOwner, IConfiguration configuration)
        {
            configuration = configuration.GetConfigurationSection(property.Name);

            if (property.GetMethod == null || !property.GetMethod.IsPublic)
            {
                // We don't support set only properties
                return;
            }

            var propertyValue = property.GetValue(propertyOwner);
            var hasPublicSetter = property.SetMethod != null && property.SetMethod.IsPublic;

            if (propertyValue == null && !hasPublicSetter)
            {
                // Property doesn't have a value and we cannot set it so there is no
                // point in going further down the graph
                return;
            }

            propertyValue = BindType(
                property.PropertyType,
                propertyValue,
                configuration);

            if (propertyValue != null && hasPublicSetter)
            {
                property.SetValue(propertyOwner, propertyValue);
            }
        }

        private static object BindType(Type type, object typeInstance, IConfiguration configuration)
        {
            var configValue = configuration.Get(null);
            var typeInfo = type.GetTypeInfo();

            if (configValue != null)
            {
                // Leaf nodes are always reinitialized
                return CreateValueFromConfiguration(type, configValue, configuration);
            }
            else
            {
                var subkeys = configuration.GetConfigurationSections();
                if (subkeys.Count() != 0)
                {
                    if (typeInstance == null)
                    {
                        if (typeInfo.IsInterface || typeInfo.IsAbstract)
                        {
                            throw new InvalidOperationException(Resources.FormatError_CannotActivateAbstractOrInterface(type));
                        }

                        bool hasParameterlessConstructor = typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
                        if (!hasParameterlessConstructor)
                        {
                            throw new InvalidOperationException(Resources.FormatError_MissingParameterlessConstructor(type));
                        }

                        try
                        {
                            typeInstance = Activator.CreateInstance(type);
                        }
                        catch (Exception ex)
                        {
                            throw new InvalidOperationException(Resources.FormatError_FailedToActivate(type), ex);
                        }
                    }

                    var collectionInterface = GetGenericOpenInterfaceImplementation(typeof(IDictionary<,>), type);
                    if (collectionInterface != null)
                    {
                        // Dictionary
                        BindDictionary(typeInstance, collectionInterface, configuration);
                    }
                    else
                    {
                        collectionInterface = GetGenericOpenInterfaceImplementation(typeof(ICollection<>), type);
                        if (collectionInterface != null)
                        {
                            // ICollection
                            BindCollection(typeInstance, collectionInterface, configuration);
                        }
                        else
                        {
                            // Something else
                            BindObjectProperties(typeInstance, configuration);
                        }
                    }
                }
                return typeInstance;
            }
        }

        private static void BindDictionary(object dictionary, Type iDictionaryType, IConfiguration configuration)
        {
            var iDictionaryTypeInfo = iDictionaryType.GetTypeInfo();

            // It is guaranteed to have a two and only two parameters
            // because this is an IDictionary<K,V>
            var keyType = iDictionaryTypeInfo.GenericTypeArguments[0];
            var valueType = iDictionaryTypeInfo.GenericTypeArguments[1];

            if (keyType != typeof(string))
            {
                // We only support string keys
                return;
            }

            var addMethod = iDictionaryTypeInfo.GetDeclaredMethod("Add");
            var subkeys = configuration.GetConfigurationSections().ToList();

            foreach (var keyProperty in subkeys)
            {
                var keyConfiguration = keyProperty.Value;

                var item = BindType(
                    type: valueType,
                    typeInstance: null,
                    configuration: keyConfiguration);
                if (item != null)
                {
                    addMethod.Invoke(dictionary, new[] { keyProperty.Key, item });
                }
            }
        }

        private static void BindCollection(object collection, Type iCollectionType, IConfiguration configuration)
        {
            var iCollectionTypeInfo = iCollectionType.GetTypeInfo();

            // It is guaranteed to have a one and only one parameter
            // because this is an ICollection<T>
            var itemType = iCollectionTypeInfo.GenericTypeArguments[0];

            var addMethod = iCollectionTypeInfo.GetDeclaredMethod("Add");
            var subkeys = configuration.GetConfigurationSections().ToList();

            foreach (var keyProperty in subkeys)
            {
                var keyConfiguration = keyProperty.Value;

                try
                {
                    var item = BindType(
                        type: itemType,
                        typeInstance: null,
                        configuration: keyConfiguration);
                    if (item != null)
                    {
                        addMethod.Invoke(collection, new[] { item });
                    }
                }
                catch
                {
                }
            }
        }

        private static object CreateValueFromConfiguration(Type type, string value, IConfiguration configuration)
        {
            var typeInfo = type.GetTypeInfo();

            if (typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                return CreateValueFromConfiguration(Nullable.GetUnderlyingType(type), value, configuration);
            }

            var configurationValue = configuration.Get(key: null);

            try
            {
                if (typeInfo.IsEnum)
                {
                    return Enum.Parse(type, configurationValue);
                }
                else
                {
                    return Convert.ChangeType(configurationValue, type);
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(Resources.FormatError_FailedBinding(configurationValue, type), ex);
            }
        }

        private static Type GetGenericOpenInterfaceImplementation(Type expectedOpenGeneric, Type actual)
        {
            var interfaces = actual.GetTypeInfo().ImplementedInterfaces;
            foreach (var interfaceType in interfaces)
            {
                if (interfaceType.GetTypeInfo().IsGenericType &&
                    interfaceType.GetGenericTypeDefinition() == expectedOpenGeneric)
                {
                    return interfaceType;
                }
            }

            return null;
        }

        private static IEnumerable<PropertyInfo> GetAllProperties(TypeInfo type)
        {
            var allProperties = new List<PropertyInfo>();

            do
            {
                allProperties.AddRange(type.DeclaredProperties);
                type = type.BaseType.GetTypeInfo();
            }
            while (type != typeof(object).GetTypeInfo());

            return allProperties;
        }
    }
ConfigurationBinder

我們對其中幾個方法,進行簡單的說明:

  • GetAllProperties。系統獲取屬性的時候,不光要獲取當前類的屬性也要獲取基類的屬性。
  • 綁定屬性時,將需要被綁定的對象作為參數傳入進去,由於是引用類型,所以不用返回值也能更改其屬性、類似的還有ArrayList等。
    • BindProperty(PropertyInfo property, object propertyOwner, IConfiguration configuration)。此處的propertyOwner值會被調用方法中修改。
  • 將字符串轉換成枚舉的方法:
    • Enum.Parse(type, configurationValue);、
  • 將對象轉變類型的方法:
    • Convert.ChangeType(configurationValue, type);
  • 判斷泛型的方法
    •   
      private static Type GetGenericOpenInterfaceImplementation(Type expectedOpenGeneric, Type actual)
              {
                  var interfaces = actual.GetTypeInfo().ImplementedInterfaces;
                  foreach (var interfaceType in interfaces)
                  {
                      if (interfaceType.GetTypeInfo().IsGenericType &&
                          interfaceType.GetGenericTypeDefinition() == expectedOpenGeneric)
                      {
                          return interfaceType;
                      }
                  }
      
                  return null;
              }

       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM