如何高逼格讀取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初學者學習的東西分享出來,讓讀者們在注孤生的道路上越走越遠:)


免責聲明!

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



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