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

之前看过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;
              }
时间: 2024-08-06 03:45:05

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

[Asp.net 5] Configuration-新一代的配置文件

微软新一代asp.net(vnext),也叫asp.net 5,开源代码都放在网址https://github.com/aspnet下. 本文介绍的是Configuration工程,下载路径为https://github.com/aspnet/Configuration. 新一代的配置文件支持json形式.xml形式.甚至支持命令行.配置文件.ini文件,以及一切自己想扩展的格式:并且可以来源不止一个或者一类,可以选择使用json和xml类的配置文件一起使用:更重要的是,新的配置文件架构,非常清

[Asp.net 5] Configuration-新一代的配置文件(ConfigurationSource的多种实现)

关于配置文件的目录:[Asp.net 5] Configuration-新一代的配置文件 在前面我们介绍了,系统中用IConfigurationSource表示不同配置文件的来源,起到读取.设置.加载配置文件的作用.而虚拟类ConfigurationSource继承接口IConfigurationSource,其他类又由ConfigurationSource派生(当然我们也可以写继承自接口IConfigurationSource类,但是没什么必要).下面是实现不同配置方式的工程: 下面我们主要以

ASP.NET 5 Configuration

原文:https://docs.asp.net/en/latest/fundamentals/configuration.html ASP.NET 5支持多种配置选项. 应用的配置文件可以是JSON,XML,INI格式,也可以是来自系统环境变量.而且你还可以写自己的自定义configuration provider. 下载本文示例代码 获取设置configuration ASP.NET 5的重构了之前依赖于System.Configuration和xml格式的配置文件(如web.config).

利用XML序列化和Asp.Net Web缓存实现站点配置文件

我们经常会遇到这样的场景: 今天来了个业务,需要加一个字段,但是考虑的以后可能有变动,需要配成“活”的. 一般最初的做法就是加一个配置到Web.Config文件的AppSettings中去.但是这样有一个问题,那就是改一下配置节点,AppDomain就需要重启,很是不爽. 变通一点的会搞出一个xml文件,利用序列化去动态的读取.但是,哥!每次都读文件不觉得太耗IO吗?尤其是使用频率高话? 下面上代码吧,懒的废话了,关键地方都注释了,也不是什么高深的技术: 先来配置文件(注意Config路径要自己

asp.net中为什么修改了配置文件后我们不需要重启IIS

本文转载:http://blog.itpub.net/12639172/viewspace-659819/ 大家知道,asp.net中,如果我们修改了配置文件只要把它保存之后,就会立刻反应到程序中, 并不需要我们重启IIS.甚至我们可以在不停止IIS的情况下,直接替换应用程序下的文件,包括我们 编译好的dll文件等,你需要做的只是替换你变换了的文件而已.那么.net是怎么做到的呢? 这要归功于.net的应用程序域机制,应用程序域是比进程小的程序元单位,也就是说一个 进程中可以包含多个应用程序域.

ASP.NET、WinForm、C# - 配置文件信息读取 [ Web.config || Appconfig ]

<configuration> <appSettings> <add key="name" value="HF_Ultrastrong"/> </appSettings> <connectionStrings> <add name="ConString" connectionString="server = ***; DATABASE = DB_News; integra

Accessing the ASP.NET Web Configuration Tool in Visual Studio 2013

Open the Command Prompt (not as administrator) Navigate to the folder where IIS Express is installed on your machine. In the command line spin up a IISExpress site with the following prompt: "iisexpress.exe /path: C:\Windows\Microsoft.NET\Framework\v

Mybatis之Configuration初始化(配置文件.xml的解析)

源码解读第一步我觉着应该从Mybatis如何解析配置文件开始. 1.先不看跟Spring集成如何解析,先看从SqlSessionFactoryBuilder如果解析的. 1 String resouce = "conf.xml"; 2 InputStream is = Resources.getResourceAsStream(resouce); 3 4 // 构建sqlSession工厂 5 SqlSessionFactory sqlSessionFactory = new SqlS

ASP.NET Core读取appsettings.json配置文件信息

1.在配置文件appsettings.json里新增AppSettings节点 { "Logging": { "LogLevel": { "Default": "Warning" } }, "AppSettings": { "HttpUrl": "http://www.ehongcn.com", "Copyright": "山南远宏科技有