StackExchange.Redis.Extensions.Core 原始碼解讀之 Configuration用法

丶Pz發表於2016-09-02

前言

  之前接觸到Redis,然後選用了對StackExchange.Redis又一層封裝的StackExchange.Redis.Extensions.Core類庫。閱讀原始碼的過程中發現了他使用Configuration實現讀取自定義配置的方法。特此學習並記錄。在我們日常開發中,最常用的自定義配置讀取方式莫過於如下兩種方式,尤其是連線資料庫。

           //讀取appsetting
                var appSettingValue = ConfigurationManager.AppSettings["KEY"];
                //讀取connectionstring
                var connectionValue = ConfigurationManager.ConnectionStrings["KEY"];

  而在使用這個類庫的時候它的配置形式是這樣的:

<configSections>
    <section name="redisCacheClient"
           type="StackExchange.Redis.Extensions.Core.Configuration.RedisCachingSectionHandler, StackExchange.Redis.Extensions.Core" />
  </configSections>

  <redisCacheClient allowAdmin="true" ssl="false" connectTimeout="5000" database="0" password="">
    <hosts>
      <add host="127.0.0.1" cachePort="6379"/>
<add host="127.0.0.1" cachePort="6380"/>
  </hosts>
</redisCacheClient>

  沒錯,就是自定義section,然後在讀取section中詳細的配置資訊,如上述程式碼所示,sectionName=redisCacheClient,allowAdmin,ssl等都是它的配置資訊。<hosts>節點比較特殊,它是一系列配置資訊的集合。

程式碼解讀

  先看一下自定義配置介面,裡面就包含了 redisCacheClient中的各種屬性定義

  

/// <summary>
    /// 自定義配置介面
    /// </summary>
    public interface IRedisCachingConfiguration
    {
        /// <summary>
        /// Redis Server的服務埠配置
        /// </summary>
        /// <value>
        /// IP地址或者伺服器名稱
        /// </value>
        RedisHostCollection RedisHosts { get; }

        /// <summary>
        /// The strategy to use when executing server wide commands
        /// </summary>
        ServerEnumerationStrategy ServerEnumerationStrategy { get; }

        /// <summary>
        /// 定義是否該連線可以使用管理員許可權操作,比如flush database
        /// </summary>
        /// <value>
        ///   <c>true</c> 可以使用管理員許可權; 否則, <c>false</c>.
        /// </value>
        bool AllowAdmin { get; }

        /// <summary>
        /// 是否SSL安全加密
        /// </summary>
        /// <value>
        ///   <c>true</c> if is secure; otherwise, <c>false</c>.
        /// </value>
        bool Ssl { get; }

        /// <summary>
        /// 連線超時時間
        /// </summary>
        int ConnectTimeout { get; }

        /// <summary>
        /// 沒有服務可用的時候,不會建立新連線
        /// </summary>
        bool AbortOnConnectFail { get; }

        /// <summary>
        /// Database Id
        /// </summary>
        /// <value>
        /// database的ID,預設為0
        /// </value>
        int Database { get; }


        /// <summary>
        /// 密碼
        /// </summary>
        string Password { get; }
    }

  我們看一下類的具體實現,首先要繼承自定義的介面,還要繼承ConfigurationSection,這樣當我們取比如說 allowAdmin的值的時候可以在內部直接呼叫 this["allowAdmin"]取到值了。

  /// <summary>
    /// 繼承自定義介面,並且繼承ConfigurationSection<see cref="IRedisCachingConfiguration"/>
    /// </summary>
    public class RedisCachingSectionHandler : ConfigurationSection, IRedisCachingConfiguration
    {
      
        
        //這裡就只拿allowAdmin舉例,預設是string型別,那麼我們就要根據自己的需求進行資料型別轉換了。這裡就把string型別轉換為我們想要的bool型別
        [ConfigurationProperty("allowAdmin")]
        public bool AllowAdmin
        {
            get
            {
              

                bool result = false;
                var config = this["allowAdmin"];

                if (config != null)
                {
                    var value = config.ToString();

                    if (!string.IsNullOrEmpty(value))
                    {
                        if (bool.TryParse(value, out result))
                        {
                            return result;
                        }
                    }
                }

                return result;
            }
        }

       //其他程式碼
    ...
    ...
/// <summary> /// 讀取配置資訊,外部呼叫主方法 /// </summary> /// <returns></returns> public static RedisCachingSectionHandler GetConfig() { return ConfigurationManager.GetSection("redisCacheClient") as RedisCachingSectionHandler; } }

  下面我們在看一下元素集合的使用,單節點RedisHost程式碼如下:

/// <summary>
    /// RedisHost的配置元素
    /// </summary>
    public class RedisHost : ConfigurationElement
    {
        /// <summary>
        /// Gets the Redis host.
        /// </summary>
        /// <value>
        ///獲取host節點值
        /// </value>
        [ConfigurationProperty("host", IsRequired = true)]
        public string Host
        {
            get
            {
                return this["host"] as string;
            }
        }

        /// <summary>
        /// Gets the port.
        /// </summary>
        /// <value>
        /// 獲取cachePort的值
        /// </value>
        [ConfigurationProperty("cachePort", IsRequired = true)]
        public int CachePort
        {
            get
            {
                var config = this["cachePort"];
                if (config != null)
                {
                    var value = config.ToString();

                    if (!string.IsNullOrEmpty(value))
                    {
                        int result;

                        if (int.TryParse(value, out result))
                        {
                            return result;
                        }
                    }
                }


                throw new Exception("Redis Cahe port must be number.");
            }
        }
    }

  還需要定義一個Collection將Hosts中的內容收集起來。類似List

/// <summary>
    /// Configuration Element Collection for <see cref="RedisHost"/>
    /// </summary>
    public class RedisHostCollection : ConfigurationElementCollection
    {
        /// <summary>
        /// Gets or sets the <see cref="RedisHost"/> at the specified index.
        /// </summary>
        /// <value>
        /// The <see cref="RedisHost"/>.
        /// </value>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public RedisHost this[int index]
        {
            //BaseGet,BaseRemoveAt,BaseAdd都是 ConfigurationElementCollection 中定義的方法
            get
            {
                //呼叫BaseGet方法獲取節點資訊
                return BaseGet(index) as RedisHost;
            }
            set
            {
                //設定的時候先刪掉,在新增
                if (BaseGet(index) != null)
                {
                    BaseRemoveAt(index);
                }

                BaseAdd(index, value);
            }
        }

        /// <summary>
        ///此方法需要重寫,返回一個新節點
        /// </summary>
        /// <returns></returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return new RedisHost();
        }

        /// <summary>
        /// 重寫此方法,獲取元素key
        /// </summary>
        /// <param name="element">元素</param>
        /// <returns></returns>
        protected override object GetElementKey(ConfigurationElement element) 
            //這裡可以看到,這個key就是 Host:Port
            => $"{((RedisHost) element).Host}:{((RedisHost) element).CachePort}";
    }

  經過一層層的包裝之後,Handler中後去Host的節點方法就很簡單了。

/// <summary>
        /// The host of Redis Server
        /// </summary>
        /// <value>
        /// The ip or name
        /// </value>
        [ConfigurationProperty("hosts")]
        public RedisHostCollection RedisHosts
            => this["hosts"] as RedisHostCollection;

  我們執行程式讀取一下試試:

         RedisCachingSectionHandler config = RedisCachingSectionHandler.GetConfig();
            Console.WriteLine("config中的allowAdmin:" + config.AllowAdmin);
            Console.WriteLine("config中的ssl:" + config.Ssl);
            Console.WriteLine("config中的password:" + config.Password);
            Console.WriteLine("config中的database:" + config.Database);
            Console.WriteLine();
            Console.WriteLine("讀取Host資訊如下:");
            foreach (RedisHost host in config.RedisHosts)
            {
                Console.WriteLine($"{host.Host}:{host.CachePort}");
            }
            Console.Read();

  執行結果:

  

  config中的資訊已經正常讀取到。那麼我們可以用這種方式實現讀取自己自定義的配置資訊啦。當然,簡單的配置還是直接用 <add key="key" value="value"/>

總結

  微軟庫已經給我們提供了太多的方法,在不知情的情況下我們往往會自己去實現。多看看開原始碼對自己知識的提升還有有幫助的。這不,學會了這種配置方法,我們就可使用不僅僅ConfigurationManager這個類了。

相關文章