zoukankan      html  css  js  c++  java
  • [Asp.net 5] Configuration-新一代的配置文件(接口定义与基础实现)

    关于配置文件的目录:[Asp.net 5] Configuration-新一代的配置文件

    本系列文章讲的是asp.net 5(Asp.net VNext)中的配置文件部分,工程下载地址为:https://github.com/aspnet/Configuration

    本节讲的是Configuration解决方案中的Microsoft.Framework.Configuration和Microsoft.Framework.Configuration.Abstractions俩个工程。

    Abstractions

    首先我们看下Configuration.Abstractions这个工程的详情:

    该工程中只定义了三个接口:IConfiguration、IConfigurationBuilder、IConfigurationSource,是完全为了抽象而设计的工程。

    我们在依赖注入(DependencyInjection)篇中也接触过名字为“Abstractions”的工程(链接地址:http://www.cnblogs.com/watermoon2/p/4511269.html),也是只包含必须的接口定义,我们可以推测,微软的命名规则是对于XXXX类工程:

    • Microsoft.Framework.XXXX.Abstractions:定义微软XXXX的必须的抽象
    • Microsoft.Framework.XXXX:定义微软的XXXX的基础实现,内部类多实现Microsoft.Framework.XXXX.Abstractions中接口

    配置文件中,肯定少不了配置文件类的基础接口定义:IConfiguration;我们知道新的配置文件实现,支持配置文件有多个来源,可以来自xml、可以来自json、也可以既有部分来自xml,又有部分来自json,所以接口中定义了“IConfigurationSource”接口,用于标示配置文件的来源;而IConfigurationBuilder是IConfiguration的构造器。

    这个工程代码比较少,下面我就将接口定义罗列如下:

    public interface IConfigurationSource
        {
            bool TryGet(string key, out string value);
    
            void Set(string key, string value);
    
            void Load();
    
            IEnumerable<string> ProduceConfigurationSections(
                IEnumerable<string> earlierKeys,
                string prefix,
                string delimiter);
        }
     public interface IConfigurationBuilder
        {
            string BasePath { get; }
    
            IEnumerable<IConfigurationSource> Sources { get; }
    
            IConfigurationBuilder Add(IConfigurationSource configurationSource);
    
            IConfiguration Build();
        }
    
    public interface IConfiguration
        {
            string this[string key] { get; set; }
    
            string Get(string key);
    
            bool TryGet(string key, out string value);
    
            IConfiguration GetConfigurationSection(string key);
    
            IEnumerable<KeyValuePair<string, IConfiguration>> GetConfigurationSections();
    
            IEnumerable<KeyValuePair<string, IConfiguration>> GetConfigurationSections(string key);
    
            void Set(string key, string value);
    
            void Reload();
        }
    接口定义

    Configuration

    我们还是将工程的详情列出:

    工程中一共八个cs文件:

    1,IConfigurationSource实现类:ConfigurationSource、MemoryConfigurationSource

    2,IConfigurationBuilder实现类:ConfigurationBuilder;IConfigurationBuilder扩展方法:ConfigurationHelper

    3,IConfiguration实现类:ConfigurationSection、ConfigurationFocus

    4,帮助辅助类:ConfigurationKeyComparer、Constants。

    一个约定:":"

    我们知道配置文件不都是线性的,可能有层次结构(比如传统的配置文件、json的、xml的)。我们读取配置文件的key值就需要有一定的逻辑。现在的逻辑是:

    • 根节点对象:“当前key”
    • 非根节点对象:“前缀”+“分隔符”+“当前key"(前缀是当前节点父节点的key值)

    所以对于如下的json格式{"root1":"r1","root2":{"sub1":"s2"}},想要获取值是“s2”,所使用的key值是“root2:sub1”;“root2”是父节点的key,“:”是分隔符,“sub1”是当前key。

    在这里的分隔符,其实就是定义在Constants类中,public static readonly string KeyDelimiter = ":"; 不过源文件中其他部分并未都直接使用该处定义,在IConfigurationSource的派生类也都是自己定义的“:”;所以想修改分隔符,在现有代码中不是能够只修改Constants中这个全局变量就可以的。所以在源码还有问题的时候,我们还是把分隔符=“:”,作为一个约定(不要试图把分隔符该城其他字符串)。

    特殊的排序方式

    由于当前key值得字符串可能是由存数字组成,我们希望key值为“1”,“2”,“10”的顺序是“1”,“2”,“10” 而不是“1”,“10”,“2”(字符串默认排序的顺序),所以系统在排序的时候使用了IComparer<string>接口。而IComparer<string>接口的实现类就是ConfigurationKeyComparer

    public class ConfigurationKeyComparer : IComparer<string>
        {
            private const char Separator = ':';
    
            public static ConfigurationKeyComparer Instance { get; } = new ConfigurationKeyComparer();
    
            public int Compare(string x, string y)
            {
                var xParts = x?.Split(Separator) ?? new string[0];
                var yParts = y?.Split(Separator) ?? new string[0];
    
                // Compare each part until we get two parts that are not equal
                for (int i = 0; i < Math.Min(xParts.Length, yParts.Length); i++)
                {
                    x = xParts[i];
                    y = yParts[i];
    
                    var value1 = 0;
                    var value2 = 0;
    
                    var xIsInt = x != null && int.TryParse(x, out value1);
                    var yIsInt = y != null && int.TryParse(y, out value2);
    
                    int result = 0;
    
                    if (!xIsInt && !yIsInt)
                    {
                        // Both are strings
                        result = string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
                    }
                    else if (xIsInt && yIsInt)
                    {
                        // Both are int 
                        result = value1 - value2;
                    }
                    else
                    {
                        // Only one of them is int
                        result = xIsInt ? -1 : 1;
                    }
    
                    if (result != 0)
                    {
                        // One of them is different
                        return result;
                    }
                }
    
                // If we get here, the common parts are equal.
                // If they are of the same length, then they are totally identical
                return xParts.Length - yParts.Length;
            }
        }
    ConfigurationKeyComparer

    前面的铺垫已经讲完,下面我们进入正文:
    ConfigurationBuilder以及ConfigurationHelper

    ConfigurationBuilder的功能主要有四点:

    • 能够设置加载的IConfigurationSource源路径目录
    • 能够管理的IConfigurationSource列表
    • 能够加载IConfigurationSource
    • 能够创建IConfiguration

    代码中需要注意的也就只有一点:添加新的IConfigurationSource时,首先加载,之后再将IConfigurationSource对象添加到内部IConfigurationSource列表中。

    ConfigurationHelper是ConfigurationBuilder的扩展,作用只有一个:

    • 将如果传入路径是相对路径,将IConfigurationSource源路径目录和传入路径进行合并。

    ConfigurationBuilder以及ConfigurationHelper源码如下:

    public class ConfigurationBuilder : IConfigurationBuilder
        {
            private readonly IList<IConfigurationSource> _sources = new List<IConfigurationSource>();
    
            public ConfigurationBuilder(params IConfigurationSource[] sources)
                : this(null, sources)
            {
            }
    
            public ConfigurationBuilder(string basePath, params IConfigurationSource[] sources)
            {
                if (sources != null)
                {
                    foreach (var singleSource in sources)
                    {
                        Add(singleSource);
                    }
                }
    
                BasePath = basePath;
            }
    
            public IEnumerable<IConfigurationSource> Sources
            {
                get
                {
                    return _sources;
                }
            }
    
            public string BasePath
            {
                get;
            }
    
            public IConfigurationBuilder Add(IConfigurationSource configurationSource)
            {
                return Add(configurationSource, load: true);
            }
    
            public IConfigurationBuilder Add(IConfigurationSource configurationSource, bool load)
            {
                if (load)
                {
                    configurationSource.Load();
                }
                _sources.Add(configurationSource);
                return this;
            }
    
            public IConfiguration Build()
            {
                return new ConfigurationSection(_sources);
            }
        }
    ConfigurationBuilder
     public static class ConfigurationHelper
        {
            public static string ResolveConfigurationFilePath(IConfigurationBuilder configuration, string path)
            {
                if (!Path.IsPathRooted(path))
                {
                    if (configuration.BasePath == null)
                    {
                        throw new InvalidOperationException(Resources.FormatError_MissingBasePath(
                            path,
                            typeof(IConfigurationBuilder).Name,
                            nameof(configuration.BasePath)));
                    }
                    else
                    {
                        path = Path.Combine(configuration.BasePath, path);
                    }
                }
    
                return path;
            }
        }
    ConfigurationHelper

    ConfigurationSource和MemoryConfigurationSource

    ConfigurationSource实现了IConfigurationSource接口,是其他具体的IConfigurationSource父类,该类是抽象类,不能直接实例化。

    该类主要提供以下几个功能:

    • 用字典表保存key,value;并且提供get/set方法
    • 提供load方法(该类中是空的虚方法)
    • 给定制定前缀,获取该前缀下的子key(如:对于key值包含如下{“p1”,“p1:p2”,“p1:p3:p4”,“s1”},则通过“p1”可以获取到p2、p3)

    MemoryConfigurationSource类继承自ConfigurationSource,提供了额外的方法:获取整个字典表。

    [ConfigurationSource是扩展配置文件类型的基类,系统中就是通过继承自该类,实现xml以及json格式的配置文件类型]

    public abstract class ConfigurationSource : IConfigurationSource
        {
            protected ConfigurationSource()
            {
                Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            }
    
            protected IDictionary<string, string> Data { get; set; }
    
            public virtual bool TryGet(string key, out string value)
            {
                return Data.TryGetValue(key, out value);
            }
    
            public virtual void Set(string key, string value)
            {
                Data[key] = value;
            }
    
            public virtual void Load()
            {
            }
           
            public virtual IEnumerable<string> ProduceConfigurationSections(
                IEnumerable<string> earlierKeys,
                string prefix,
                string delimiter)
            {
                return Data
                    .Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                    .Select(kv => Segment(kv.Key, prefix, delimiter))
                    .Concat(earlierKeys)
                    .OrderBy(k => k, ConfigurationKeyComparer.Instance);
            }
    
            private static string Segment(string key, string prefix, string delimiter)
            {
                var indexOf = key.IndexOf(delimiter, prefix.Length, StringComparison.OrdinalIgnoreCase);
                return indexOf < 0 ? key.Substring(prefix.Length) : key.Substring(prefix.Length, indexOf - prefix.Length);
            }
        }
    ConfigurationSource
    public class MemoryConfigurationSource : 
            ConfigurationSource, 
            IEnumerable<KeyValuePair<string,string>>
        {
            public MemoryConfigurationSource()
            {
            }
    
            public MemoryConfigurationSource(IEnumerable<KeyValuePair<string, string>> initialData)
            {
                foreach (var pair in initialData)
                {
                    Data.Add(pair.Key, pair.Value);
                }
            }
    
            public void Add(string key, string value)
            {
                Data.Add(key, value);
            }
    
            public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
            {
                return Data.GetEnumerator();
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
    MemoryConfigurationSource

    ConfigurationSection和ConfigurationFocus

    这两个类是双生类,使用了代理模式:通过GetConfigurationSection等获取IConfiguration方法返回的是ConfigurationFocus代理的ConfigurationSection(ConfigurationBuilder创建的是ConfigurationSection类)。简单的类关系图如下所示:(ConfigurationFocus不会和ConfigurationSource产生关联,会通过ConfigurationSection进行访问)

     

    ConfigurationSection类实现的主要功能:

    • 根据key值获取配置信息(包含key值的IConfigurationSource中,ConfigurationBuilder最后添加的IConfigurationSource对象的key值所对应的value值)
    • 根据key值设置配置信息(所有IConfigurationSource文件都会被更新,最后信息不是保存在ConfigurationSection中,而是直接反应在IConfigurationSource上)
    • 重新加载配置源(IConfigurationSource)
    • 根据key值(可以为空)获取<string, IConfiguration>对应的字典表。(系统构建的IConfiguration就是ConfigurationFocus类型)

    ConfigurationFocus实现的主要功能:

    • 内部封装ConfigurationSection对象
    • 内部封装当前的前缀信息
    • 根据内部封装的前缀信息+key构造新的key值,之后通过ConfigurationSection获取新key值配置信息/设置新key配置信息
    • 根据内部封装的前缀信息+key构造新的key值,之后通过ConfigurationSection获取新key配置信息。(当前配置信息下一级为key的配置信息)
    • 根据内部封装的前缀信息+key构造新的key值,之后通过ConfigurationSection获取子<string, IConfiguration>对应的字典表。(当先配置信息下一级为key的配置信息的所有子配置信息)
    public class ConfigurationSection : IConfiguration
        {
            private readonly IList<IConfigurationSource> _sources = new List<IConfigurationSource>();
    
            public ConfigurationSection(IList<IConfigurationSource> sources)
            {
                _sources = sources;
            }
    
            public string this[string key]
            {
                get
                {
                    return Get(key);
                }
                set
                {
                    Set(key, value);
                }
            }
    
            public IEnumerable<IConfigurationSource> Sources
            {
                get
                {
                    return _sources;
                }
            }
    
            public string Get([NotNull] string key)
            {
                string value;
                return TryGet(key, out value) ? value : null;
            }
    
            public bool TryGet([NotNull] string key, out string value)
            {
                // If a key in the newly added configuration source is identical to a key in a 
                // formerly added configuration source, the new one overrides the former one.
                // So we search in reverse order, starting with latest configuration source.
                foreach (var src in _sources.Reverse())
                {
                    if (src.TryGet(key, out value))
                    {
                        return true;
                    }
                }
                value = null;
                return false;
            }
    
            public void Set([NotNull] string key, [NotNull] string value)
            {
                if (!_sources.Any())
                {
                    throw new InvalidOperationException(Resources.Error_NoSources);
                }
                foreach (var src in _sources)
                {
                    src.Set(key, value);
                }
            }
    
            public void Reload()
            {
                foreach (var src in _sources)
                {
                    src.Load();
                }
            }
    
            public IConfiguration GetConfigurationSection(string key)
            {
                return new ConfigurationFocus(this, key + Constants.KeyDelimiter);
            }
    
            public IEnumerable<KeyValuePair<string, IConfiguration>> GetConfigurationSections()
            {
                return GetConfigurationSectionsImplementation(string.Empty);
            }
    
            public IEnumerable<KeyValuePair<string, IConfiguration>> GetConfigurationSections([NotNull] string key)
            {
                return GetConfigurationSectionsImplementation(key + Constants.KeyDelimiter);
            }
    
            private IEnumerable<KeyValuePair<string, IConfiguration>> GetConfigurationSectionsImplementation(string prefix)
            {
                var segments = _sources.Aggregate(
                    Enumerable.Empty<string>(),
                    (seed, source) => source.ProduceConfigurationSections(seed, prefix, Constants.KeyDelimiter));
    
                var distinctSegments = segments.Distinct();
    
                return distinctSegments.Select(segment => CreateConfigurationFocus(prefix, segment));
            }
    
            private KeyValuePair<string, IConfiguration> CreateConfigurationFocus(string prefix, string segment)
            {
                return new KeyValuePair<string, IConfiguration>(
                    segment,
                    new ConfigurationFocus(this, prefix + segment + Constants.KeyDelimiter));
            }
        }
    ConfigurationSection
    public class ConfigurationFocus : IConfiguration
        {
            private readonly string _prefix;
            private readonly IConfiguration _root;
    
            public ConfigurationFocus(IConfiguration root, string prefix)
            {
                _prefix = prefix;
                _root = root;
            }
    
            public string this[string key]
            {
                get
                {
                    return Get(key);
                }
                set
                {
                    Set(key, value);
                }
            }
    
            public string Get(string key)
            {
                // Null key indicates that the prefix passed to ctor should be used as a key
                if (key == null)
                {
                    // Strip off the trailing colon to get a valid key
                    var defaultKey = _prefix.Substring(0, _prefix.Length - 1);
                    return _root.Get(defaultKey);
                }
    
                return _root.Get(_prefix + key);
            }
    
            public bool TryGet(string key, out string value)
            {
                // Null key indicates that the prefix passed to ctor should be used as a key
                if (key == null)
                {
                    // Strip off the trailing colon to get a valid key
                    var defaultKey = _prefix.Substring(0, _prefix.Length - 1);
                    return _root.TryGet(defaultKey, out value);
                }
                return _root.TryGet(_prefix + key, out value);
            }
    
            public IConfiguration GetConfigurationSection(string key)
            {
                return _root.GetConfigurationSection(_prefix + key);
            }
    
            public void Set(string key, string value)
            {
                _root.Set(_prefix + key, value);
            }
    
            public IEnumerable<KeyValuePair<string, IConfiguration>> GetConfigurationSections()
            {
                return _root.GetConfigurationSections(_prefix.Substring(0, _prefix.Length - 1));
            }
    
            public IEnumerable<KeyValuePair<string, IConfiguration>> GetConfigurationSections(string key)
            {
                return _root.GetConfigurationSections(_prefix + key);
            }
    
            public void Reload()
            {
                throw new InvalidOperationException(Resources.Error_InvalidReload);
            }
        }
    ConfigurationFocus

    最后我们将ConfigurationSection和ConfigurationFocus的测试代码贴出,以便能够更好的理解俩个类的关系。

    public void CanGetConfigurationSection()
            {
                // Arrange
                var dic1 = new Dictionary<string, string>()
                    {
                        {"Data:DB1:Connection1", "MemVal1"},
                        {"Data:DB1:Connection2", "MemVal2"}
                    };
                var dic2 = new Dictionary<string, string>()
                    {
                        {"DataSource:DB2:Connection", "MemVal3"}
                    };
                var dic3 = new Dictionary<string, string>()
                    {
                        {"Data", "MemVal4"}
                    };
                var memConfigSrc1 = new MemoryConfigurationSource(dic1);
                var memConfigSrc2 = new MemoryConfigurationSource(dic2);
                var memConfigSrc3 = new MemoryConfigurationSource(dic3);
    
                var builder = new ConfigurationBuilder();
                builder.Add(memConfigSrc1, load: false);
                builder.Add(memConfigSrc2, load: false);
                builder.Add(memConfigSrc3, load: false);
    
                var config = builder.Build();
    
                string memVal1, memVal2, memVal3, memVal4, memVal5;
                bool memRet1, memRet2, memRet3, memRet4, memRet5;
    
                // Act
                var configFocus = config.GetConfigurationSection("Data");
    
                memRet1 = configFocus.TryGet("DB1:Connection1", out memVal1);
                memRet2 = configFocus.TryGet("DB1:Connection2", out memVal2);
                memRet3 = configFocus.TryGet("DB2:Connection", out memVal3);
                memRet4 = configFocus.TryGet("Source:DB2:Connection", out memVal4);
                memRet5 = configFocus.TryGet(null, out memVal5);
    
                // Assert
                Assert.True(memRet1);
                Assert.True(memRet2);
                Assert.False(memRet3);
                Assert.False(memRet4);
                Assert.True(memRet5);
    
                Assert.Equal("MemVal1", memVal1);
                Assert.Equal("MemVal2", memVal2);
                Assert.Equal("MemVal4", memVal5);
    
                Assert.Equal("MemVal1", configFocus.Get("DB1:Connection1"));
                Assert.Equal("MemVal2", configFocus.Get("DB1:Connection2"));
                Assert.Null(configFocus.Get("DB2:Connection"));
                Assert.Null(configFocus.Get("Source:DB2:Connection"));
                Assert.Equal("MemVal4", configFocus.Get(null));
    
                Assert.Equal("MemVal1", configFocus["DB1:Connection1"]);
                Assert.Equal("MemVal2", configFocus["DB1:Connection2"]);
                Assert.Null(configFocus["DB2:Connection"]);
                Assert.Null(configFocus["Source:DB2:Connection"]);
                Assert.Equal("MemVal4", configFocus[null]);
            }
  • 相关阅读:
    使用Task创建任务
    动态,静态库的发布和使用
    gcc编译
    knockout.js的学习笔记2
    网络爬虫基本原理(一)
    .Net项目分层与文件夹结构
    ASP.NET MVC的客户端验证:jQuery的验证
    ASP.NET MVC的客户端验证:jQuery验证在Model验证中的实现
    CMake 手册详解
    一个真实的应用程序 — 精通ASP.NET MVC 3
  • 原文地址:https://www.cnblogs.com/watermoon2/p/4527218.html
Copyright © 2011-2022 走看看