zoukankan      html  css  js  c++  java
  • 如何高逼格读取Web.config中的AppSettings

    http://edi.wang/post/2015/4/22/how-to-read-webconfig-appsettings-with-bigiblity

    先插句题外话,下版本的ASP.NET貌似把web.config撸掉了,都变成json了。所以本文讨论的内容可能以后用不到了,但是一些设计思想还是可以用的~

    直接进入正题,在ASP.NET网站里(也包括其他有web.config, app.config)的.NET工程里,读AppSettings的值是个很常见的场景。比如:

    <add key="EnableAzureWebTrace" value="true"/>

    在代码里读的时候就会用到:

    ConfigurationManager.AppSettings["EnableAzureWebTrace"];

    这个[]索引器返回的是string类型。所以下一步我们通常需要类型转换才能在代码里拿来用。比如这个例子里,我们就要转换成bool。其他时候,可能要转换为int等类型。

    string enableAzureWebTraceConfig = ConfigurationManager.AppSettings["EnableAzureWebTrace"];
    bool enableAzureWebTrace = bool.Parse(enableAzureWebTraceConfig);
    if(enableAzureWebTrace)
    {
    // do some logic
    }

    但问题是,config文件的值对于我们代码来说是不稳定因素,不可控的,这里拿回来的string未必能正确转换格式。所以通常我们会用TryParse方法来防爆:

    string enableAzureWebTraceConfig = ConfigurationManager.AppSettings["EnableAzureWebTrace"];
    bool enableAzureWebTrace = false;
    if (bool.TryParse(enableAzureWebTraceConfig, out enableAzureWebTrace) && enableAzureWebTrace)
    {
        // do some logic
    }
    else
    {
        throw new ConfigurationException("EnableAzureWebTrace value must be true of false.");
    }

    当然,不要忘了一点。读出来的string有可能首先就是空的。所以又得加上对string的判断,并且考虑到ConfigurationManager.AppSettings[]索引器本身可能会爆,所以还得加try-catch,最终代码就变成这样了:

    try
    {
        string enableAzureWebTraceConfig = ConfigurationManager.AppSettings["EnableAzureWebTrace"];
        if (!string.IsNullOrEmpty(enableAzureWebTraceConfig))
        {
            bool enableAzureWebTrace = false;
            if (bool.TryParse(enableAzureWebTraceConfig, out enableAzureWebTrace) && enableAzureWebTrace)
            {
                // do some logic
            }
            else
            {
                throw new ConfigurationException("EnableAzureWebTrace value must be true of false.");
            }
        }
    
    }
    catch (ConfigurationException ce)
    {
        // error handling logic
        throw;
    }

    这样的代码非常没有逼格,重用性很差,如果你的config里面AppSettings比较多,或者一个settings在程序里到处被用,显然不应该每次都把这样的代码到处复制。所以封装一下呗:

    public bool IsEnableAzureWebTrace()
    {
        try
        {
            bool enableAzureWebTrace = false;
            string enableAzureWebTraceConfig = ConfigurationManager.AppSettings["EnableAzureWebTrace"];
            if (!string.IsNullOrEmpty(enableAzureWebTraceConfig))
            {
                if (!bool.TryParse(enableAzureWebTraceConfig, out enableAzureWebTrace))
                {
                    throw new ConfigurationException("EnableAzureWebTrace value must be true of false.");
                }
            }
            return enableAzureWebTrace;
        }
        catch (ConfigurationException ce)
        {
            // error handling logic
            return false;
        }
    }

    现在要用到EnableAzureWebTrace的地方都只要调用public bool IsEnableAzureWebTrace()就行了,我们就把如何读config的逻辑抽离了。重构的目的是,万一以后读config的机制变了,只要改这一处。不用到处改。但是,我们重构的粒度还不够。这个方法只能用来读EnableAzureWebTrace这一个设置。我们要通用一下,让它也能读其他bool类型的设置。把key单独的抽出来变成参数:

    public bool GetBooleanConfiguration(string key)
    {
        try
        {
            bool val = false;
            string rawConfigValue = ConfigurationManager.AppSettings[key];
            if (!string.IsNullOrEmpty(rawConfigValue))
            {
                if (!bool.TryParse(rawConfigValue, out val))
                {
                    throw new ConfigurationException(string.Format("{0} value must be true of false.", key));
                }
            }
            return val;
        }
        catch (ConfigurationException ce)
        {
            // error handling logic
            return false;
        }
    }

    但是这还不够,因为这个方法只能满足于bool类型的config,我们希望有个公用的方法,能读取其他类型。这时候就需要用泛型了。把返回类型给抽离出来。

    难点在于,每种数据类型的类型转换写法不一样。比如bool类型是bool.TryParse,int类型是int.TryParse,怎么把这部分逻辑抽象出来呢?

    一种办法是用C#本身的类型转换:

    (T) Convert.ChangeType(rawConfigValue, typeof (T));

    另一种是把类型转换的逻辑作为委托加在方法的参数里,这样就用lambda表达式去传,我比较偏向这种方法,因为方法的调用者能非常清晰的知道“该干嘛,该怎么干”。

    这时候,如果因为非法类型转换爆,是得让调用者知道的。所以我偏向把TryParse改为Parse,死就要死个明白。

    public T GetConfiguration<T>(Func<string, T> parseFunc, string key)
    {
        try
        {
            T val = default(T);
            string rawConfigValue = ConfigurationManager.AppSettings[key];
            if (!string.IsNullOrEmpty(rawConfigValue))
            {
                return parseFunc(rawConfigValue);
            }
            return val;
        }
        catch (ConfigurationException ce)
        {
            // error handling logic
            return default(T);
        }
    }

    现在,调用这个方法就能这样去写:

    GetConfiguration<bool>(bool.Parse, "EnableAzureWebTrace");

    看起来已经很牛逼了。但其实还不够。考虑到之前说的config值为空字符串的问题,安全一点的做法是,当遇到空字符串时候,返回一个默认值。因为这种错误,并不是key不存在的错误,而是key存在,但是值没填。非法值是应该认为错误的。但是空值我个人认为更应该处理为一种“警告”,是应该有fallback的策略的,而非不可饶恕的错误。为了返回默认值,我们可以多加一个委托。

    public T GetConfiguration<T>(Func<string, T> parseFunc, Func<T> defaultTValueFunc, string key)
    {
        try
        {
            string rawConfigValue = ConfigurationManager.AppSettings[key];
            return !string.IsNullOrEmpty(rawConfigValue) ? 
                    parseFunc(rawConfigValue) : 
                    defaultTValueFunc();
        }
        catch (ConfigurationException ce)
        {
            // error handling logic
            return default(T);
        }
    }

    现在,调用者就能灵活处理遇到config为空时候的默认值了:

    GetConfiguration<bool>(bool.Parse, () => false, "EnableAzureWebTrace");

    但是如果每次都在条件判断里写上面那样的语句是挺麻烦的,在一般的系统开发中,我们常常会用一个管理配置的Settings类来对应Web.config里的设置表,维护这个关系。为了使用方便,我们会把每个Settings的名字,也就是key,作为属性去暴露给调用者,于是你就能这样写:

    public bool EnableAzureWebTrace
    {
        get
        {
            return GetConfiguration<bool>(bool.Parse, () => false, "EnableAzureWebTrace");
        }
    }

    你以为装逼结束了吗?当然不行!你没发现,属性名称和传进去的string类型的key名称是重复的吗?这样写代码是不是有点蛋疼?而且最惨的是,在VS2015,C#6.0之前(也就是下版本的C#),string这种东西,要是写错了是编译不出来的,所以我们应该尽量避免用string传key。经常会发生改了属性名,没有一起改string值的悲剧。比如MVVM框架的RaisePropertyChanged(string)就经常坑爹(题外话)。。。

    好在,.NET4.5有个CallerMemberName特性,意思是”调用我的方法叫什么名字”,就能帮我们把这个string参数撸掉。

    所以,我们只需要把方法签名里的string key改成:

    public T GetConfiguration<T>(Func<string, T> parseFunc, Func<T> defaultTValueFunc, [CallerMemberName]string key = "")

    这样这个方法被调用的时候,key就会自动赋值为调用它的方法或属性名。然后,刚才的那个属性就能够这样去写:

    public bool EnableAzureWebTrace
    {
        get
        {
            return GetConfiguration<bool>(bool.Parse, () => false);
        }
    }

    你以为装逼真的结束了吗?还有最后一步。万一要是碰到有些情况,属性名真的和appSettings里的key名字不一样怎么办?为了灵活处理这种边缘情况,还可以加个参数,强撸这种名称不一样的情况,如果这个参数被赋值了(下面的supressKey),就用它去读config而不用传入 的key。

    下面给出我博客里读AppSettings的通用代码:

    private T TryGetValueFromConfig<T>(Func<string, T> parseFunc, Func<T> defaultTValueFunc,
        [CallerMemberName]string key = "", string supressKey = "")
    {
        try
        {
            if (!supressKey.IsNullOrEmptyOrWhiteSpace())
            {
                key = supressKey;
            }
    
            var node = ConfigurationManager.AppSettings[key];
            return !string.IsNullOrEmpty(node) ? parseFunc(node) : defaultTValueFunc();
        }
        catch (Exception ex)
        {
            Logger.Error(string.Format("Error Reading web.config on AppSettings node: {0}", key), ex);
            return default(T);
        }
    }

    现在,你就能灵活装逼了,给几个例子:

    string类型,属性名和key不一样,默认值“FileSystemImageProvider”:

    public string PostImageProvider
    {
        get
        {
            return TryGetValueFromConfig(_ => _, () => "FileSystemImageProvider", supressKey: "ImageProvider");
        }
    }

    bool类型,默认值想要true

    public bool IncludeSiteDomainForImageUploadUrl
    {
        get
        {
            return TryGetValueFromConfig(bool.Parse, () => true);
        }
    }

    int类型,默认值为20

    public int CacheSlideExpireTimeSpanFallbackMinutes
    {
        get
        {
            return TryGetValueFromConfig(int.Parse, () => 20);
        }
    }

    ASP.NET5和EF7马上来了,我的博客代码准备来一次脱胎换骨的重写,所以我陆续把以前博客代码里一些自认为非常适合.NET初学者学习的东西分享出来,让读者们在注孤生的道路上越走越远:)

  • 相关阅读:
    tyvj 1031 热浪 最短路
    【bzoj2005】 [Noi2010]能量采集 数学结论(gcd)
    hdu 1394 Minimum Inversion Number 逆序数/树状数组
    HDU 1698 just a hook 线段树,区间定值,求和
    ZeptoLab Code Rush 2015 C. Om Nom and Candies 暴力
    ZeptoLab Code Rush 2015 B. Om Nom and Dark Park DFS
    ZeptoLab Code Rush 2015 A. King of Thieves 暴力
    hdoj 5199 Gunner map
    hdoj 5198 Strange Class 水题
    vijos 1659 河蟹王国 线段树区间加、区间查询最大值
  • 原文地址:https://www.cnblogs.com/LuoEast/p/6815311.html
Copyright © 2011-2022 走看看