[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]);
        }

 


免責聲明!

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



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