[總結]讀取應用程序/類庫配置文件(比如***.dll.config)的方法小結

0.引子

讀取應用程序配置文件(比如***.exe.config)的方法有很多, .NET自帶的ConfigurationManager也很方便. 這篇文章主要探討類庫生成的dll文件的配置信息的讀取——特別地, 用戶自定義SectionGroupName和SectionName(這裏是指不同於appSettings的形式), 這種方式配置信息的讀取. 配置文件格式舉例(其實通過添加"設置文件", 注意:不是"應用程序配置文件")

代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<configSections>
    
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
      
<section name="TestDllConfig.Test" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
    
</sectionGroup>
    
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
      
<section name="TestDllConfig.Test" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    
</sectionGroup>
  
</configSections>
  
<userSettings>
    
<TestDllConfig.Test>
      
<setting name="ACCESSORY_TYPE" serializeAs="String">
        
<value>ACCESSORY</value>
      
</setting>
    
</TestDllConfig.Test>
  
</userSettings>
  
<applicationSettings>
    
<TestDllConfig.Test>
      
<setting name="APPLICATION_NAME" serializeAs="String">
        
<value>JGPT</value>
      
</setting>
      
<setting name="DATABASE_NAME" serializeAs="String">
        
<value>JGPT</value>
      
</setting>
      
<setting name="APP_ATTACHMENT_PATH" serializeAs="String">
        
<value>~//APP_ATTACHMENT//</value>
      
</setting>
    
</TestDllConfig.Test>
  
</applicationSettings>
</configuration>


首先, 交代一些東西. 1.不討論爲類庫dll文件設置配置文件是否有必要, 我主要的出發點是, 一些參數可以通過讀取配置文件得到, 從而不用重新編譯類庫; 2.本文實現的環境是: visual studio 2005/.net 2.0;3.dll配置信息類庫內部要用到, 外部也可能用到.

 

1.首先, 新建一個空的項目解決方案. 並添加一個類庫項目(空的即可, 示例中命名爲TestDllConfig). 然後再添加一個空的控制檯項目(測試輸出用途, 示例中命名爲ReadDllConfig).

到目前爲止, 做好了準備工作. 接下來爲類庫添加配置文件. 右鍵類庫項目, 選擇 添加-新建項-選擇"設置文件"-命名爲Test.settings. 如下圖所示:

 

 

接着, 雙擊新添加的設置文件, 添加參數和值. 如下圖:

 

 

都是測試用的參數數據. 例子中用到的範圍爲Application, 範圍中Application和User的選擇會導致SectionGroup不一樣. 保存, .NET就會自動生成一個app.config. 另外可以看到有個Test.Designer.cs 文件, 是保存的時候自動生成的(這個文件後面會說到). 打開app.config, 會看到和上面的xml代碼一樣.

 

到目前爲止, 我們已經完成了搭架子的工作, 我們還沒手工寫一行代碼. 下面就通過往類庫裏添加讀取配置信息的代碼, 和測試項目中控制檯輸出測試結果代碼.

 

2. 往類庫中添加讀取配置信息的代碼

首先大家可以讀下園子裏這兩篇博文:

http://www.cnblogs.com/bearhand/archive/2008/09/07/1279087.html

http://www.cnblogs.com/rosanshao/archive/2008/09/07/1286191.html

這兩篇說到了.NET 2.0下新增讀取配置文件的方法: ConfigurationManager類

下面先給出讀取配置文件路徑的幫助方法(至於爲什麼要封裝一遍, 註釋裏有寫; 同時爲了測試方便):

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->        /// <summary>
        
/// 封裝多一遍, 確保調用本方法的方法爲Dll內部方法, 從而取得正確的Dll配置文件路徑
        
/// 否則可能取得的是執行程序(主程序)的路徑
        
/// 詳細參考Assembly.GetCallingAssembly()的原理
        
/// </summary>
        private static string DllConfigFilePath
        {
            
get
            {
                Assembly t_assembly 
= Assembly.GetCallingAssembly();
                Uri t_uri 
= new Uri(Path.GetDirectoryName(t_assembly.CodeBase));

                
//NOTE: 這個時候讀取的路徑是應用程序/網站下面的bin目錄, 或者應用程序/網站編譯運行dll的目錄(自定義輸出路徑的情況)
                
//而不是類庫的生成dll的輸出路徑! 
                
//所以, 如果採用引用項目的方式引用, 要將配置文件一同複製到和dll同個目錄(bin目錄), 否則讀取不到配置信息; 修改配置
                
//文件後同樣記得此目錄下覆蓋原文件
                return Path.Combine(t_uri.LocalPath, t_assembly.GetName().Name + ".dll.config");
            }
        }
複製代碼


下面這段代碼是常用的讀取自定義配置信息的方法, 返回Configuration類型:

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->/// <summary>
        
/// 讀取dll的配置信息的方法
        
/// </summary>
        
/// <returns></returns>
        private static Configuration CurrentDllConfiguration
        {
            
get
            {
                ExeConfigurationFileMap configFile 
= new ExeConfigurationFileMap();

                
//NOTE: 這個時候讀取的路徑是應用程序/網站下面的bin目錄, 或者應用程序/網站編譯運行dll的目錄(自定義輸出路徑的情況)
                
//而不是類庫的生成dll的輸出路徑! 
                
//所以, 如果採用引用項目的方式引用, 要將配置文件一同複製到和dll同個目錄(bin目錄), 否則讀取不到配置信息; 修改配置
                
//文件後同樣記得此目錄下覆蓋原文件
                configFile.ExeConfigFilename = DllConfigFilePath;
                
return ConfigurationManager.OpenMappedExeConfiguration(configFile, ConfigurationUserLevel.None);

                
//也可以使用以下方法讀取配置信息
                
//Configuration t_appConfig =
                ////ConfigurationManager.OpenExeConfiguration(Path.Combine(t_uri.LocalPath, t_assembly.GetName().Name));
                //ConfigurationManager.OpenExeConfiguration(Path.Combine(t_uri.LocalPath, t_assembly.GetName().Name + ".dll"));
                ////ConfigurationManager.OpenExeConfiguration(Path.Combine(t_uri.LocalPath, t_assembly.GetName().Name + ".dll.config"));
                //return t_appConfig;
            }
        }
複製代碼


通過查看程序自動生成的配置文件, 知道SectionGroupName和SectionName, 定義屬性:

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->        /// <summary>
        
/// SectionGroup名
        
/// </summary>
        private static string SectionGroupName
        {
            
get
            {
                
return "applicationSettings";
            }
        }
        
/// <summary>
        
/// Section名
        
/// </summary>
        private static string SectionName
        {
            
get
            {
                
return "TestDllConfig.Test";
            }
        }

        
private static string FullSectionName
        {
            
get
            {
                
return SectionGroupName + "/" + SectionName;
            }
        }
複製代碼


對於以上代碼, 網上常見都是如此處理(.NET 1.1一般XML操作, 後面也會說到).  現在開始我們的各種情況說明了.

 

2.1 直接使用Configuration.AppSettings方式讀取配置的情況

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->#region 直接使用appConfig.AppSettings方式讀取配置的情況
        
//NOTE: 下面直接使用appConfig.AppSettings方式讀取數據, 必須按照以下格式寫配置文件
        
//<appSettings>
        
//    <add key="DATABASE_NAME" value="JGPT"/>
        
//</appSettings>
        
//如果配置文件中沒有<appSettings></appSettings>或沒有在該節點配置信息
        
//那麼可以debug到以下情況, 說明AppSettings默認讀取的是<appSettings></appSettings>內的配置信息
        
//CurrentDllConfiguration.AppSettings.Settings 不爲null
        
//但是CurrentDllConfiguration.AppSettings.Settings.Count == 0
        
// NOTE2: 讀取指定key的值value必須使用appConfig.AppSettings.Settings["APPLICATION_NAME"].Value方式;
        
//     不能使用appConfig.AppSettings["APPLICATION_NAME"]方式, 否則編譯器會提示
        
// "錯誤“System.Configuration.ConfigurationElement.this[System.Configuration.ConfigurationProperty]”不可訪問,因爲它受保護級別限制"
        
//這與平常讀取Web.Config配置信息不太一樣
        public static readonly string DATABASE_NAME_By_AppSettings = CurrentDllConfiguration.AppSettings.Settings["DATABASE_NAME_By_AppSettings"].Value;
        
#endregion
複製代碼


說明: 上面的參數名"DATABASE_NAME_By_AppSettings" 直接拷貝項目代碼, 大家可以換成自動生成的設置文件中有的名稱:

"DATABASE_NAME ". 結果是一樣的.

很顯然, 直接在控制檯項目中

System.Console.WriteLine(TestDllConfig.DllConfig.DATABASE_NAME_By_AppSettings);

Debug可以發現CurrentDllConfiguration.AppSettings.Settings 不爲null, 但是CurrentDllConfiguration.AppSettings.Settings.Count == 0; 通過多次debug以及在配置文件中添加以下代碼:

<appSettings>
    <add key="DATABASE_NAME_By_AppSettings" value="JGPT"/>
</appSettings> 

再次運行就可以讀取到數據了. 說明AppSettings默認讀取的是<appSettings></appSettings>內的配置信息. 如果我們要讀取類似程序自動生成的配置信息怎麼辦呢? 先不急, 先提醒下大家一個小問題. 用這種方式讀取數據, 需要注意和平常讀取Web.Config配置信息不太一樣(平時可以直接通過[]讀取)

 NOTE2: 讀取指定key的值value必須使用appConfig.AppSettings.Settings["APPLICATION_NAME"].Value方式;
     不能使用appConfig.AppSettings["APPLICATION_NAME"]方式, 否則編譯器會提示
 "錯誤“System.Configuration.ConfigurationElement.this[System.Configuration.ConfigurationProperty]”不可訪問,因爲它受保護級別限制"


2.2 Configuration類型轉換爲KeyValueConfigurationCollection 或者 NameValueCollection 類型的情況

 這個沒什麼好說的, 編譯器報錯.

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->        /// <summary>
        
/// 以下方式編譯器會報錯
        
/// cs as KeyValueConfigurationCollection 或者 cs as NameValueCollection 編譯器會提示:
        
/// 錯誤    無法通過內置轉換將類型“System.Configuration.ConfigurationSection”轉換爲 ***類型
        
/// </summary>
        //public static KeyValueConfigurationCollection DllSettings_NameValueCollection
        
//{
        
//    get
        
//    {
        
//        ConfigurationSection cs = CurrentDllConfiguration.GetSection(FullSectionName);
        
//        return cs as KeyValueConfigurationCollection; //或者 cs as NameValueCollection 
        
//    }
        
//
複製代碼


 2.3 Configuration類型轉換爲AppSettingsSection類型的情況

這個和上面稍微有點不同, 編譯無提示錯誤, 但轉換後爲null. 

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->        /// <summary>
        
/// 內置轉換爲AppSettingsSection類型
        
/// 編譯無提示錯誤, 但轉換後爲null
        
/// </summary>
        public static AppSettingsSection DllSettings_AppSettingsSection
        {
            
get
            {
                ConfigurationSection cs 
= CurrentDllConfiguration.GetSection(FullSectionName);
                
return cs as AppSettingsSection;
            }
        }
複製代碼


2.4 Configuration類型轉換爲自定義配置信息數據結構的情況

 這段代碼也是很常見的啦. 不多說了, 結果和轉換爲AppSettingsSection的情形一樣: 編譯無提示錯誤, 但轉換後爲null.

這種做法可以參考

http://stackoverflow.com/questions/204695/storing-values-in-the-web-config-appsettings-or-configsection-which-is-more-e

http://haacked.com/archive/2007/03/12/custom-configuration-sections-in-3-easy-steps.aspx ;

看 以上例子, 發現人家能測試成功,  就是不知道爲什麼我的例子失敗 :-(. anyway, 挫折多點也好, 可以學到更多東西, 特別是對於執着的人.

 

代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->        /// <summary>
        
/// 自定義配置信息數據結構
        
/// </summary>
        public class UserDefinedDllConfig : ConfigurationSection
        {
            
/// <summary>
            
/// 
            
/// </summary>
            public static UserDefinedDllConfig DllSettings
            {
                
get
                {
                    
//TODO: 每次調用CurrentDllConfiguration都會再次讀取配置文件, 效率低下; 改用Cache
                    ConfigurationSection cs = CurrentDllConfiguration.GetSection(FullSectionName);
                    
// 或者CurrentDllConfiguration.GetSectionGroup(SectionGroupName).Sections[SectionName] 

                    
return cs as UserDefinedDllConfig;//轉換後爲null
                }
            }

            
/// <summary>
            
/// 數據庫名
            
/// </summary>
            [ConfigurationProperty("DATABASE_NAME", IsRequired = true)]
            
public string DatabaseName
            {
                
get
                {
                    
return this["DATABASE_NAME"as string;
                }
            }

            
/// <summary>
            
/// 本系統名稱
            
/// </summary>
            [ConfigurationProperty("APPLICATION_NAME", IsRequired = true)]
            
public string ApplicationName
            {
                
get
                {
                    
return this["APPLICATION_NAME"as string;
                }
            }
        }


2.5  Configuration類型轉換爲ClientSettingsSection類型的情況

經過以上測試, 用google查找很多關於dll.config的設置文件讀取的資料, 發現有例子將配置信息類型Configuration轉換爲ClientSettingsSection操作. 但是我測試發現還是有問題, 下面是代碼:

代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->        /// <summary>
        
/// 內置轉換爲ClientSettingsSection類型
        
/// 不提示錯誤而且轉換後也不爲null, 但調用 .Settings["APPLICATION_NAME"] 提示:
        
/// 錯誤    “System.Configuration.ConfigurationElement.this[System.Configuration.ConfigurationProperty]”不可訪問,因爲它受保護級別限制
        
/// </summary>
        public static ClientSettingsSection DllSettings_ClientSettingsSection
        {
            
get
            {
                ConfigurationSection cs = CurrentDllConfiguration.GetSection(FullSectionName);
                
return cs as ClientSettingsSection;
            }
        }

不提示錯誤而且轉換後也不爲null, 但調用 .Settings["APPLICATION_NAME"] 提示:
錯誤    “System.Configuration.ConfigurationElement.this[System.Configuration.ConfigurationProperty]”不可訪問,因爲它受保護級別限制

這個錯誤很鬱悶, 因爲遇到很多次, 上次就是AppSettings["參數名"], 改爲AppSettings.Settings["參數名"].Value就可以, 但是這裏不行. 還是會有錯誤. 到這裏, 再次查找很多資料, 發現不少老外也遇到這個問題沒解決. 今天一個偶然機會, 看到帖子:

http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/ec237df4-f05e-4711-a230-fb089c395c73/?prof=required

(根據微軟網站以往的情況, 這個鏈接很可能以後會改變...)

OK. 下面就提出這個帖子中提到的3種可行的解決方案.

2.6 參考上面帖子nrolland的回覆
接着2.5的問題,

轉換爲ClientSettingsSection類型後, 採用以下方式可以讀取出數據:
System.Console.WriteLine(TestDllConfig.DllConfig.DllSettings_ClientSettingsSection.Settings.Get("APPLICATION_NAME").Value.ValueXml.InnerText); 

很顯然, 這種方案在類似ConfigurationManager的方式下調用, 但是還是有很明顯的XML操作痕跡. 沒辦法, XML纔是老本根, 誰叫設置文件採用xml格式呢.

 

2.7 經典的XML解決方案

上面的帖子回帖中就有人寫了一段代碼: (下面這段基本上是直接手工照敲一遍, 就是處理葉子節點稍微修改了下, 因爲配置文件不一樣嘛)

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->        /// <summary>
        
/// 通過Xml處理取得配置信息
        
/// </summary>
        
/// <param name="sSection"></param>
        
/// <param name="sFilePath"></param>
        
/// <returns></returns>
        private static NameValueCollection GetNameValueCollectionSection(string sSection, string sFilePath)
        {
            
string sFile = sFilePath;
            System.Xml.XmlDocument xDoc 
= new System.Xml.XmlDocument();
            NameValueCollection nRet 
= new NameValueCollection();

            
try
            {
                ExeConfigurationFileMap fMap 
= new ExeConfigurationFileMap();
                fMap.ExeConfigFilename 
= sFile;
                Configuration config 
= ConfigurationManager.OpenMappedExeConfiguration(fMap, ConfigurationUserLevel.None);

                
string sXml = config.GetSection(sSection).SectionInformation.GetRawXml();
                xDoc.LoadXml(sXml);
            }
            
catch (Exception exp)
            {
                
throw new Exception(exp.Message);
            }

            System.Xml.XmlNode xList 
= xDoc.ChildNodes[0];
            
//System.Xml.XmlNode xList = xDoc.GetElementsByTagName(SectionName)[0];
            foreach (System.Xml.XmlNode xNode in xList)
            {
                
//nRet.Add(xNode.Attributes[0].Value, xNode.Attributes[1].Value);
                
//NOTE: Test.settings自動生成的xml格式與<appSettings>不一樣: Value在子節點
                nRet.Add(xNode.Attributes[0].Value, xNode.ChildNodes[0].ChildNodes[0].Value);
            }

            
return nRet;
        }
複製代碼

網上類似的代碼很多. 就是基本的XML操作, 加上ConfigurationManager.

通過調用 return GetNameValueCollectionSection(FullSectionName, DllConfigFilePath); 直接返回NameValueCollection類型數據, 得到NameValueCollection類型數據就沒問題了.

這裏補充說明下爲什麼開頭會將獲取dll.config文件的路徑的方法封裝一遍, 因爲寫這段代碼的時候直接照敲, 結果發現用Assembly讀取到的配置文件路徑不一樣. 查MSDN發現:

 Assembly.GetCallingAssembly返回調用當前正在執行的方法的方法的   Assembly。  
 這就是基礎不紮實的表現...

 

2.8 從自動生成的代碼中讀取配置的情況

 上面帖子最後一篇(當前時間20100301是最後一篇)提到另外一種解決思路. 參考網址: http://www.codeproject.com/KB/cs/UserSettings.aspx. 這篇文章開頭的截圖是不是很像呢? 哈, 自動生成配置文件基本操作都差不多.

還記得不? 開始設置參數後自動生成的那個cs文件嗎? 自動生成的代碼一看就明白.

代碼
複製代碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->#region 從自動生成的代碼中讀取配置的情況
        
/// <summary>
        
/// 可以通過以下方式給外部調用
        
/// </summary>
        public static string m_DllSettings_By_AutoGenerated = TestDllConfig.Test.Default.DATABASE_NAME;
        
/// <summary>
        
/// 從自動生成的代碼中讀取(有默認值)
        
/// NOTE: 如果聲明爲public或protected, 會編譯錯誤: 
        
/// 錯誤    23    可訪問性不一致: 屬性類型“TestDllConfig.Test”比屬性“TestDllConfig.DllConfig.DllSettings_By_AutoGenerated”的可訪問性低
        
/// 所以只能聲明爲private.
        
/// 但是TestDllConfig.Test.Default.DATABASE_NAME卻可以訪問
        
/// NOTE2: 有時候自動生成的cs文件可能會丟失, 或者刪除. 因此這種方法必須要求自動生成的代碼存在.
        
/// </summary>
        private static Test DllSettings_By_AutoGenerated
        {
            
get
            {
                
return TestDllConfig.Test.Default;
            }
        } 
        
#endregion
複製代碼

除了要留意自動生成的類是sealed類型, 需要通過類似

public static string m_DllSettings_By_AutoGenerated = TestDllConfig.Test.Default.DATABASE_NAME;

的方式給外部程序調用(就是測試用的控制檯程序, 如果是類庫自身調用問題就不大了)


3. 小結

本文從頭到尾記錄了本人對這個問題的思考過程. 或許還有更加完美的解決方案, 希望園子裏的大牛們不吝賜教. 文章寫的有點囉嗦了, 記錄下來也是爲了以後自己查找資料方便. 希望這篇文章對有需要的朋友有用. 歡迎各位針對本文提出意見(不希望見到匿名謾罵)


總的來說, 我還是偏向於2.6的解決方案, 比較方便, 而且不容易出錯(通過參數名來讀取值, 而不是索引);

自動生成的代碼是可以刪除的, 某些情況下也可能丟失, 所以不是很可靠.也是可以從設置文件讀數據, 而且可以有默認值

 

另外, 對於爲一個dll文件是否有必要添加設置文件,

http://stackoverflow.com/questions/594298/c-dll-config-file

這裏的最好的答覆有說明(太長了, 沒看完...)

 

最後, 附上本文的測試源代碼:/Files/GuominQiu/SourceCode/20100301_TestDllConfig_SourceCode.7z


PS. 再囉嗦一句, 採用7-ZIP格式壓縮源代碼, 可以比winrar得到更加高的壓縮率, 對比相當明顯. 一般一個50M以上的項目源代碼目錄, 用7zip可以壓縮到大約6M多點.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章