揭开.NET 2.0配置之谜(三)

  1. 云栖社区>
  2. 博客>
  3. 正文

揭开.NET 2.0配置之谜(三)

技术小胖子 2017-11-17 23:06:00 浏览679
展开阅读全文

声明:此文是译文,原文是Jon RistaUnraveling the Mysteries of .NET 2.0 Configuration,由于这篇文章比较长,所以我就分为几部分来翻译,这是此译文的第三部分。若翻译有不当之处,请不吝赐教,以免此译文误导他人,在此谢过。

let's go on!

10、配置技巧和窍门

在我研究和实验配置节的时候,我学到一些技巧,可是使他们更容易使用。自定义配置节的某些方面非常乏味,如所有时间一直通过调用ConfigurationManager.GetSection("someSectionName")获取SomeSectionClass。为了减轻繁琐乏味,我尝试在我的ConfigurationSection类中使用下列方式:


  1. public class SomeConfigurationSection  
  2. {  
  3.     static SomeConfigurationSection()  
  4.     {  
  5.         // Preparation...  
  6.     }  
  7.       
  8.     // Properties...  
  9.       
  10.     #region GetSection Pattern  
  11.     private static SomeConfigurationSection m_section;  
  12.       
  13.     /// <summary>  
  14.     /// Gets the configuration section using the default element name.  
  15.     /// </summary>  
  16.     public static SomeConfigurationSection GetSection()  
  17.     {  
  18.         return GetSection("someConfiguration");  
  19.     }  
  20.       
  21.     /// <summary>  
  22.     /// Gets the configuration section using the specified element name.  
  23.     /// </summary>  
  24.     public static SomeConfigurationSection GetSection(string definedName)  
  25.     {  
  26.         if (m_section == null)  
  27.         {         
  28.             m_section = ConfigurationManager.GetSection(definedName) as   
  29.                         SomeConfigurationSection;  
  30.             if (m_section == null)  
  31.                 throw new ConfigurationException("The <" + definedName +   
  32.                       "> section is not defined in your .config file!");  
  33.         }      
  34.           
  35.         return m_section;  
  36.     }  
  37.     #endregion  

上述的模式增加了一个静态GetSection()方法给每个自定义ConfigurationSection类。它的一个重载方法,以一个字符串作为参数,允许你为.config中元素定义一个不同的名字,如果你喜欢的话。另外,默认的重载是可以使用的。这种模式使用在标准的应用程序(.exe)的配置节下工作的非常好。然而,如果配置节使用在一个web.config文件中,你将需要使用下面的代码:


  1. using System.Web;  
  2. using System.Web.Configuration;  
  3.  
  4. public class SomeConfigurationSection  
  5. {  
  6.     static SomeConfigurationSection()  
  7.     {  
  8.         // Preparation...  
  9.     }  
  10.       
  11.     // Properties...  
  12.       
  13.     #region GetSection Pattern  
  14.     private static SomeConfigurationSection m_section;  
  15.       
  16.     /// <summary>  
  17.     /// Gets the configuration section using the default element name.  
  18.     /// </summary>  
  19.     /// <remarks>  
  20.     /// If an HttpContext exists, uses the WebConfigurationManager  
  21.     /// to get the configuration section from web.config.  
  22.     /// </remarks>  
  23.     public static SomeConfigurationSection GetSection()  
  24.     {  
  25.         return GetSection("someConfiguration");  
  26.     }  
  27.       
  28.     /// <summary>  
  29.     /// Gets the configuration section using the specified element name.  
  30.     /// </summary>  
  31.     /// <remarks>  
  32.     /// If an HttpContext exists, uses the WebConfigurationManager  
  33.     /// to get the configuration section from web.config.  
  34.     /// </remarks>  
  35.     public static SomeConfigurationSection GetSection(string definedName)  
  36.     {  
  37.         if (m_section == null)  
  38.         {  
  39.             string cfgFileName = ".config";  
  40.             if (HttpContext.Current == null)  
  41.             {  
  42.                 m_section = ConfigurationManager.GetSection(definedName)   
  43.                             as SomeConfigurationSection;  
  44.             }  
  45.             else 
  46.             {  
  47.                 m_section = WebConfigurationManager.GetSection(definedName)   
  48.                             as SomeConfigurationSection;  
  49.                 cfgFileName = "web.config";  
  50.             }  
  51.                   
  52.             if (m_section == null)  
  53.                 throw new ConfigurationException("The <" + definedName +   
  54.                   "> section is not defined in your " +   
  55.                   cfgFileName + " file!");  
  56.         }      
  57.           
  58.         return m_section;  
  59.     }  
  60.     #endregion  

正如你看到的,在ASP.NET下访问配置节需要使用System.Web.Configuration.WebConfigurationManager,而不是System.Configuration.ConfigurationManager。检查当前的HttpContext足以确定使用哪个管理器(manager)去获取配置节。还有一个增强,我们对这种模式,使得允许我们可以保存修改。我们知道,必须使用Configuration对象来保存修改,因为管理器(manager)类不提供Save()方法。我们可以在我们的GetSection方法中创建一个Configuration对象,但是最终将缺乏灵活性、效率不高。在最终完全的模式中,我这样做,把Configuration对象作为一个参数:


  1. using System.Web;  
  2. using System.Web.Configuration;  
  3.  
  4. public class SomeConfigurationSection  
  5. {  
  6.     static SomeConfigurationSection()  
  7.     {  
  8.         // Preparation...  
  9.     }  
  10.       
  11.     // Properties...  
  12.       
  13.     #region GetSection Pattern  
  14.     // Dictionary to store cached instances of the configuration object  
  15.     private static Dictionary<string,   
  16.             SomeConfigurationSection> m_sections;  
  17.       
  18.     /// <summary>  
  19.     /// Finds a cached section with the specified defined name.  
  20.     /// </summary>  
  21.     private static SomeConfigurationSection   
  22.             FindCachedSection(string definedName)  
  23.     {  
  24.         if (m_sections == null)  
  25.         {  
  26.             m_sections = new Dictionary<string,   
  27.                              SomeConfigurationSection>();  
  28.             return null;  
  29.         }  
  30.           
  31.         SomeConfigurationSection section;  
  32.         if (m_sections.TryGetValue(definedName, out section))  
  33.         {  
  34.             return section;  
  35.         }  
  36.           
  37.         return null;  
  38.     }  
  39.       
  40.     /// <summary>  
  41.     /// Adds the specified section to the cache under the defined name.  
  42.     /// </summary>  
  43.     private static void AddCachedSection(string definedName,   
  44.                    SomeConfigurationSection section)  
  45.     {  
  46.         if (m_sections != null)  
  47.             m_sections.Add(definedName, section);  
  48.     }  
  49.       
  50.     /// <summary>  
  51.     /// Removes a cached section with the specified defined name.  
  52.     /// </summary>  
  53.     public static void RemoveCachedSection(string definedName)  
  54.     {  
  55.         m_sections.Remove(definedName);  
  56.     }  
  57.       
  58.     /// <summary>  
  59.     /// Gets the configuration section using the default element name.  
  60.     /// </summary>  
  61.     /// <remarks>  
  62.     /// If an HttpContext exists, uses the WebConfigurationManager  
  63.     /// to get the configuration section from web.config. This method  
  64.     /// will cache the instance of this configuration section under the  
  65.     /// specified defined name.  
  66.     /// </remarks>  
  67.     public static SomeConfigurationSection GetSection()  
  68.     {  
  69.         return GetSection("someConfiguration");  
  70.     }  
  71.       
  72.     /// <summary>  
  73.     /// Gets the configuration section using the specified element name.  
  74.     /// </summary>  
  75.     /// <remarks>  
  76.     /// If an HttpContext exists, uses the WebConfigurationManager  
  77.     /// to get the configuration section from web.config. This method  
  78.     /// will cache the instance of this configuration section under the  
  79.     /// specified defined name.  
  80.     /// </remarks>  
  81.     public static SomeConfigurationSection GetSection(string definedName)  
  82.     {  
  83.         if (String.IsNullOrEmpty(definedName))  
  84.             definedName = "someConfiguration";  
  85.               
  86.         SomeConfigurationSection section = FindCachedSection(definedName);  
  87.         if (section == null)  
  88.         {  
  89.             string cfgFileName = ".config";  
  90.             if (HttpContext.Current == null)  
  91.             {  
  92.                 section = ConfigurationManager.GetSection(definedName)   
  93.                           as SomeConfigurationSection;  
  94.             }  
  95.             else 
  96.             {  
  97.                 section = WebConfigurationManager.GetSection(definedName)   
  98.                           as SomeConfigurationSection;  
  99.                 cfgFileName = "web.config";  
  100.             }  
  101.                   
  102.             if (section == null)  
  103.                 throw new ConfigurationException("The <" + definedName +   
  104.                    "> section is not defined in your " + cfgFileName +   
  105.                    " file!");  
  106.                   
  107.             AddCachedSection(definedName, section);  
  108.         }  
  109.           
  110.         return section;  
  111.     }  
  112.       
  113.     /// <summary>  
  114.     /// Gets the configuration section using the default element name   
  115.     /// from the specified Configuration object.  
  116.     /// </summary>  
  117.     /// <remarks>  
  118.     /// If an HttpContext exists, uses the WebConfigurationManager  
  119.     /// to get the configuration section from web.config.  
  120.     /// </remarks>  
  121.     public static SomeConfigurationSection GetSection(Configuration config)  
  122.     {  
  123.         return GetSection(config, "someConfiguration");  
  124.     }  
  125.       
  126.     /// <summary>  
  127.     /// Gets the configuration section using the specified element name   
  128.     /// from the specified Configuration object.  
  129.     /// </summary>  
  130.     /// <remarks>  
  131.     /// If an HttpContext exists, uses the WebConfigurationManager  
  132.     /// to get the configuration section from web.config.  
  133.     /// </remarks>  
  134.     public static SomeConfigurationSection GetSection(Configuration config,   
  135.                                            string definedName)  
  136.     {  
  137.         if (config == null)  
  138.             throw new ArgumentNullException("config",   
  139.                   "The Configuration object can not be null.");  
  140.               
  141.         if (String.IsNullOrEmpty(definedName))  
  142.             definedName = "someConfiguration";  
  143.               
  144.         SomeConfigurationSection section = config.GetSection(definedName)   
  145.                                            as SomeConfigurationSection;  
  146.                   
  147.         if (section == null)  
  148.             throw new ConfigurationException("The <" + definedName +   
  149.                   "> section is not defined in your .config file!");  
  150.           
  151.         return section;  
  152.     }  
  153.     #endregion  

通过传递Configuration对象,一个可保存的配置节实例能在XML文件中检索一个指定名字的配置节。这把我带到另外一个重要的配置节秘诀。配置节元素的名字不一定必须的固定不变,也不一定只有一个配置节的实例。在一个.config文件按中每个配置节可以定义和设置多次,只要给每个实例不同的名字:


  1. <configuration> 
  2.   <configSections> 
  3.     <section name="example1" type="Examples.Configuration.ExampleSection,  
  4.                                       Examples.Configuration" /> 
  5.     <section name="example2" type="Examples.Configuration.ExampleSection,   
  6.                                       Examples.Configuration" /> 
  7.     <section name="example3" type="Examples.Configuration.ExampleSection,   
  8.                                       Examples.Configuration" /> 
  9.   </configSections> 
  10.     
  11.   <example1 /> 
  12.   <example2 /> 
  13.   <example3 /> 
  14. </configuration> 

以同样的方式配置节组也可以定义多次。这使得一个通常的自定义配置结构在同一个应用程序中,同时以多种方式使用,而且使得自定义配置可以重用。因为可能在一个.config文件中一个配置节定义多次,最终实现上述的模式包括一个简单的实例缓存。每次用不同的definedName调用GetSection(string),将返回不同的配置节对象且存储在缓存中。连续以相同的名字调用将返回相同的缓存实例。这种模式的另一个重要方面是缺少为两个新版本的GetSection(以一个Configuration对象作为参数的GetSection方法)的缓存。用Configuration对象比用ConfigurationManagerWebConfigurationManager花费更大的开销。通过配置管理器(manager)调用GetSection()方法将完全缓存节,而通过Configuration对象调用将导致节每次都要被解析。一般来说,Configuration对象只有当需要保存配置更改是才使用。配置管理器类应该被用来访问读取配置。这将保证使用配置设置时性能最佳。

最后一个秘诀是关于性能的主题。除非你实现高级的配置节或元素,包括事件通知,缓存配置设置在变量中通常沮丧的(行不通的)。更多关于这个的讨论将在下面的高级部分,首先考虑以下非常简单的情景:


  1. public class SomeProgram  
  2. {  
  3.     static Main()  
  4.     {  
  5.         s_config = MyConfig.GetSection();  
  6.         s_duration = s_config.Duration;  
  7.           
  8.         s_timer = new Timer(  
  9.             new TimerCallback(),  
  10.             null,  
  11.             TimeSpan.Zero  
  12.             s_duration  
  13.         );  
  14.           
  15.         Console.ReadKey();  
  16.         s_timer.Dispose();  
  17.     }  
  18.       
  19.     private static MyConfig s_config;  
  20.     private static Timer s_timer;  
  21.     private static TimeSpan s_duration;  
  22.       
  23.     private static void WriteCurrentTime(object data)  
  24.     {  
  25.         Console.WriteLine("The current time is " + DateTime.Now.ToString());  
  26.           
  27.         if (s_duration != s_config.Duration)  
  28.         {  
  29.             s_duration = s_config.Duration;  
  30.             s_timer.Change(TimeSpan.Zero, s_duration);  
  31.         }  
  32.     }  

在上面的应用程序中,我们希望如果配置文件更新的话,改变定时器间隔。我们配置节的实例,s_config,将一直保持更新。因此如果在应用程序运行时,.config文件改变,任何改变将被发现并载入到内存中。如果你跟我在文章中一样实现你的配置节,覆写静态构造器和替换属性(properties)集合,这样你的集合将有有高的性能。这使得访问一个配置属性(property)相对廉价的操作,因此上述代码可以重写成如下:


  1. public class SomeProgram  
  2. {  
  3.     static Main()  
  4.     {  
  5.         s_config = MyConfig.GetSection();  
  6.           
  7.         s_timer = new Timer(  
  8.             new TimerCallback(),  
  9.             null,  
  10.             TimeSpan.Zero  
  11.             s_config.Duration  
  12.         );  
  13.           
  14.         Console.ReadKey();  
  15.         s_timer.Dispose();  
  16.     }  
  17.       
  18.     private static MyConfig s_config;  
  19.     private static Timer s_timer;  
  20.     private static TimeSpan s_duration;  
  21.       
  22.     private static void WriteCurrentTime(object data)  
  23.     {  
  24.         Console.WriteLine("The current time is " +   
  25.                           DateTime.Now.ToString());         
  26.         s_timer.Change(TimeSpan.Zero, s_config.Duration);  
  27.     }  

如果这个例子过于简单揭示直接使用配置设置的意义,那么想象一个更复杂的场景,一个配置值在一个缓存变量。变量是顺序通过一个链来调用,然后循环使用。如果想要的结果对任何配置的过期及时发现并回答,那么缓存将不起作用。你必须直接访问配置属性(property),不用把它缓存到变量中。配置设置是全局访问的,可以在应用程序的任何地方。这意味着在你的代码中的任何地方都可以访问配置属性(property),而不用缓存变量的值和传递一个变量参数。将使得代码更干净,因为你要求更少的的参数。如果高性能是绝对必要的,是有可能写一个有事件的配置节,当配置数据在磁盘上已经改变能通知使用者。然而,这是一个更高级的主题,将在以后讨论。

11、高级配置主题

本文中概述的信息提供了一个全面地介绍.NET 2.0框架的配置功能特性。然而,这决不是一个全面的文件,并且还有一些更复杂的使用配置节。其他信息将在后面的文章:

  1. 解码.NET 2.0配置之谜
  2. 破解.NET 2.0配置之谜

12、附录

12.1、附录A: 配置结构的级联

在ASP.NET应用程序中,web.config文件可能针对任何IIS“应用程序”。倘若应用程序的虚拟文件夹是另一个应用程序的孩子,来自父应用程序的web.config文件将和子应用程序的web.config合并。因为IIS中的应用程序可以嵌套任何级别的深度,当子应用应程序的web.config加载时,配置级联将产生。

假设我们有一个站点安装在IIS里,以下面的层次结构且每个web.config文件包含一个共同的集合:


  1. \wwwroot  
  2.       web.config  
  3.       \firstapp  
  4.           web.config  
  5.       \anotherapp  
  6.           web.config  
  7.           \childapp  
  8.               web.config  
  9.       \finalapp  
  10.           web.config  
  11.  
  12. <!-- \wwwroot\web.config --> 
  13. <configuration> 
  14.     <commonCollection> 
  15.         <add key="first"  value="C98E4F32123A" /> 
  16.         <add key="second" value="DD0275C8EA1B" /> 
  17.         <add key="third"  value="629B59A001FC" /> 
  18.     </commonCollection> 
  19. </configuration> 
  20.  
  21. <!-- \wwroot\firstapp\web.config --> 
  22. <configuration> 
  23.     <commonCollection> 
  24.         <remove key="first" />          
  25.         <add key="first"  value="FB54CD34AA92" /> 
  26.           
  27.         <add key="fourth" value="DE67F90ACC3C" /> 
  28.     </commonCollection> 
  29. </configuration> 
  30.  
  31. <!-- \wwroot\anotherapp\web.config --> 
  32. <configuration> 
  33.     <commonCollection> 
  34.         <add key="fourth" value="123ABC456DEF" /> 
  35.         <add key="fifth"  value="ABC123DEF456" /> 
  36.         <add key="sixth"  value="0F9E8D7C6B5A" /> 
  37.     </commonCollection> 
  38. </configuration> 
  39.  
  40. <!-- \wwroot\anotherapp\childapp\web.config --> 
  41. <configuration> 
  42.     <commonCollection> 
  43.         <remove key="second" /> 
  44.         <remove key="fourth" /> 
  45.         <remove key="sixth" /> 
  46.  
  47.         <add key="seventh" value="ABC123DEF456" /> 
  48.         <add key="ninth"  value="0F9E8D7C6B5A" /> 
  49.     </commonCollection> 
  50. </configuration> 
  51.  
  52. <!-- \wwroot\lastapp\web.config --> 
  53. <configuration> 
  54.     <commonCollection> 
  55.         <clear /> 
  56.           
  57.         <add key="first"  value="AABBCCDDEEFF" /> 
  58.         <add key="second" value="112233445566" /> 
  59.         <add key="third"  value="778899000000" /> 
  60.         <add key="fourth" value="0A0B0C0D0E0F" /> 
  61.     </commonCollection> 
  62. </configuration> 

如果我们研究了每个应用程序的集合,结果将如下:

  1. \wwwroot\web.config
    1. first = C98E4F32123A
    2. second = DD0275C8EA1B
    3. third = 629B59A001FC
  2. \wwwroot\firstapp\web.config
    1. first = FB54CD34AA92
    2. second = DD0275C8EA1B
    3. third = 629B59A001FC
    4. fourth = DE67F90ACC3C
  3. \wwwroot\anotherapp\web.config
    1. first = C98E4F32123A
    2. second = DD0275C8EA1B
    3. third = 629B59A001FC
    4. fourth = 123ABC456DEF
    5. fifth = ABC123DEF456
    6. sixth = 0F9E8D7C6B5A
  4. \wwwroot\anotherapp\childapp\web.config
    1. first = C98E4F32123A
    2. third = 629B59A001FC
    3. fifth = ABC123DEF456
    4. seventh = ABC123DEF456
    5. ninth = 0F9E8D7C6B5A
  5. \wwwroot\lastapp\web.config
    1. first = AABBCCDDEEFF
    2. second = 112233445566
    3. third = 778899000000
    4. fourth = 0A0B0C0D0E0F

我希望这个简单的示例,子应用程序的web.config是如何继承设置的,这些设置是如何被覆写,足够了。你可能不是经常遇到这种情况,但是了解发生了什么,以及如何覆写父web.config的配置设置,应该有助于减轻ASP.NET开发者的配置文件问题。

12.2、附录B: 包含外部配置文件

尽管在.NET 2.0的配置功能中都很伟大,但是仍有一个缺点。当工作在一个多环境的单一项目中,管理配置文件是一个噩梦。管理多环境下的多版本的配置文件(如开发、测试、阶段、产品)的过程,我目前的工作包括手工比较.config文件,将更改部署到一个环境或另外一个,通过手工合并。我花了几个月试图找到一种更好的方法,最终找到了。进入这样那样一些没有“没有文档的”或很少文档的——微软著名的特点,的其中的一个:configSource。当我用Reflector深入挖掘.NET 2.0配置源码的时候,碰到这个珍品,美妙的小工具。

每个配置节在被.NET配置类解析和加载时,都分配了一个SectionInformation对象。SectionInformation对象包含关于配置节的元信息,并允许管理节如何互相覆写,当定义在一个子web.config中时(ASP.NET)。现在,我们将忽略大部分SectionInformation对象提供的,考虑configSource属性(property)。通过添加configSource属性(attribute)到任何ConfigurationSection根元素,你可以指定一个备用,外部的配置设置将被加载。


  1. <configuration>  
  2.   <connectionStrings configSource="externalConfig/connectionStrings.config"/>  
  3. </configuration>  
  4.  
  5. <!-- externalConfig/connectionStrings.config -->  
  6. <connectionStrings>  
  7.   <add name="conn" connectionString="blahblah" />  
  8. </connectionStrings> 

在上面的配置文件中,<connectionStrings>节源于名为externalConfig/connectionStrings.config的文件。所有应用程序的连接字符串将加载自这个特定文件。现在,连接字符串是从外部资源加载的,在相对相同位置的每个环境,他相对简单地创建一个connectionStrings.config文件。因此externalConfig/   connectionStrings.config 文件的路径。这里漂亮的地方是,我们可以正确地为每个环境定义连接字符串定义一次。我们不用担心意外覆写那些设置,在部署一个config文件时,无论是否合并得当或根本不合并。这是一个巨大的福音,当更改一个应用程序到产品环境是,他的关键正确的数据库连接字符串存在。使用configSource属性(attribute)失效,就是它要求所有的配置节将放置在外部文件中。没有继承或覆写是可能的,在某些情况下使它没用。所有的外部配置文件用configSource属性(attribute)引用,也必须放在相对子到主的.config文件路径上。我相信这是考虑web环境中的安全性,存储文件在相对父路径上。

别的需要注意的是<appSettings>节有一个更好的选择使用configSource,称为file。如果你使用file属性(attribute)而不是configSource<appSettings>节里,你可以定义设置在根.config文件或引用文件都可以。根.config文件的设置也能被引用文件覆写,简单地用相同的键添加东西。可悲的是,file属性(attribute)只适用在<appSettings>节,而不是建立在配置框架下。在我们自己的配置节中也可能实现类似的属性(attribute)。这将在将来的高级配置主题部分讨论,几个先决部分之后。

 

终于翻译完了Unraveling the Mysteries of .NET 2.0 Configuration ,可以歇息一下了!如果您觉得好请推荐下,谢谢!后面将继续翻译:

请继续关注!

此英文原文:Jon RistaUnraveling the Mysteries of .NET 2.0 Configuration







     本文转自Saylor87 51CTO博客,原文链接:http://blog.51cto.com/skynet/365552,如需转载请自行联系原作者



网友评论

登录后评论
0/500
评论
技术小胖子
+ 关注