现在的位置: 首页 > 综合 > 正文

揭开.NET2.0配置之谜(三)

2013年09月21日 ⁄ 综合 ⁄ 共 12916字 ⁄ 字号 评论关闭

声明:此文是译文,原文是Jon
Rista


Unraveling the Mysteries of .NET 2.0 Configuration

由于这篇文章比较长,所以我就分为几部分来翻译,这是此译文的第三部分。若翻译有不当之处,请不吝赐教,以免此译文误导他人,在此谢过。

let's go on!

10、配置技巧和窍门

在我研究和实验配置节的时候,我学到一些技巧,可是使他们更容易使用。自定义配置节的某些方面非常乏味,如所有时间一直通过调用ConfigurationManager.GetSection("someSectionName")

SomeSectionClass
。为了减轻繁琐乏味,我尝试在我的ConfigurationSection

类中使用下列方式:


public
 class
 SomeConfigurationSection
{
static SomeConfigurationSection()
{
// Preparation...
}

// Properties...

#region GetSection Pattern
private static SomeConfigurationSection m_section;

/// <summary>
/// Gets the configuration section using the default element name.
/// </summary>
public static SomeConfigurationSection GetSection()
{
return GetSection("someConfiguration ");
}

/// <summary>
/// Gets the configuration section using the specified element name.
/// </summary>
public static SomeConfigurationSection GetSection(string definedName)
{
if (m_section == null )
{
m_section = ConfigurationManager.GetSection(definedName) as
SomeConfigurationSection;
if (m_section == null )
throw new ConfigurationException("The < " + definedName +
"> section is not defined in your .config file! ");
}

return m_section;
}
#endregion
}

 

上述的模式增加了一个静态GetSection()

方法给
每个自定义ConfigurationSection

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


using
 System.Web;
using System.Web.Configuration;

public class SomeConfigurationSection
{
static SomeConfigurationSection()
{
// Preparation...
}

// Properties...

#region GetSection Pattern
private static SomeConfigurationSection m_section;

/// <summary>
/// Gets the configuration section using the default element name.
/// </summary>
/// <remarks>
/// If an HttpContext exists, uses the WebConfigurationManager
/// to get the configuration section from web.config.
/// </remarks>
public static SomeConfigurationSection GetSection()
{
return GetSection("someConfiguration ");
}

/// <summary>
/// Gets the configuration section using the specified element name.
/// </summary>
/// <remarks>
/// If an HttpContext exists, uses the WebConfigurationManager
/// to get the configuration section from web.config.
/// </remarks>
public static SomeConfigurationSection GetSection(string definedName)
{
if (m_section == null )
{
string cfgFileName = ".config ";
if (HttpContext.Current == null )
{
m_section = ConfigurationManager.GetSection(definedName)
as SomeConfigurationSection;
}
else
{
m_section = WebConfigurationManager.GetSection(definedName)
as SomeConfigurationSection;
cfgFileName = "web.config ";
}

if (m_section == null )
throw new ConfigurationException("The < " + definedName +
"> section is not defined in your " +
cfgFileName + " file! ");
}

return m_section;
}
#endregion
}

 

正如你看到的,在ASP.NET下访问配置节需要使用System.Web.Configuration.WebConfigurationManager


而不是System.Configuration.ConfigurationManager


检查当前的HttpContext

足以确定使用哪个管理器
(manager)去获取配置节。还有一个增强,我们对这种模式,使得允许我们可以保存修改。我们知道,必须使用Configuration

对象来保存修改,因为管理器(manager)类不提供Save()

方法。我们可以在我们的GetSection方法中创建一个Configuration

对象,但是最终将缺乏灵活性、效率不高。在最终完全的模式
中,我这样做,把Configuration

对象作为一个参数:


using
 System.Web;
using System.Web.Configuration;

public class SomeConfigurationSection
{
static SomeConfigurationSection()
{
// Preparation...
}

// Properties...

#region GetSection Pattern
// Dictionary to store cached instances of the configuration object
private static Dictionary<string ,
SomeConfigurationSection> m_sections;

/// <summary>
/// Finds a cached section with the specified defined name.
/// </summary>
private static SomeConfigurationSection
FindCachedSection(string definedName)
{
if (m_sections == null )
{
m_sections = new Dictionary<string ,
SomeConfigurationSection>();
return null ;
}

SomeConfigurationSection section;
if (m_sections.TryGetValue(definedName, out section))
{
return section;
}

return null ;
}

/// <summary>
/// Adds the specified section to the cache under the defined name.
/// </summary>
private static void AddCachedSection(string definedName,
SomeConfigurationSection section)
{
if (m_sections != null )
m_sections.Add(definedName, section);
}

/// <summary>
/// Removes a cached section with the specified defined name.
/// </summary>
public static void RemoveCachedSection(string definedName)
{
m_sections.Remove(definedName);
}

/// <summary>
/// Gets the configuration section using the default element name.
/// </summary>
/// <remarks>
/// If an HttpContext exists, uses the WebConfigurationManager
/// to get the configuration section from web.config. This method
/// will cache the instance of this configuration section under the
/// specified defined name.
/// </remarks>
public static SomeConfigurationSection GetSection()
{
return GetSection("someConfiguration ");
}

/// <summary>
/// Gets the configuration section using the specified element name.
/// </summary>
/// <remarks>
/// If an HttpContext exists, uses the WebConfigurationManager
/// to get the configuration section from web.config. This method
/// will cache the instance of this configuration section under the
/// specified defined name.
/// </remarks>
public static SomeConfigurationSection GetSection(string definedName)
{
if (String.IsNullOrEmpty(definedName))
definedName = "someConfiguration ";

SomeConfigurationSection section = FindCachedSection(definedName);
if (section == null )
{
string cfgFileName = ".config ";
if (HttpContext.Current == null )
{
section = ConfigurationManager.GetSection(definedName)
as SomeConfigurationSection;
}
else
{
section = WebConfigurationManager.GetSection(definedName)
as SomeConfigurationSection;
cfgFileName = "web.config ";
}

if (section == null )
throw new ConfigurationException("The < " + definedName +
"> section is not defined in your " + cfgFileName +
" file! ");

AddCachedSection(definedName, section);
}

return section;
}

/// <summary>
/// Gets the configuration section using the default element name
/// from the specified Configuration object.
/// </summary>
/// <remarks>
/// If an HttpContext exists, uses the WebConfigurationManager
/// to get the configuration section from web.config.
/// </remarks>
public static SomeConfigurationSection GetSection(Configuration config)
{
return GetSection(config, "someConfiguration ");
}

/// <summary>
/// Gets the configuration section using the specified element name
/// from the specified Configuration object.
/// </summary>
/// <remarks>
/// If an HttpContext exists, uses the WebConfigurationManager
/// to get the configuration section from web.config.
/// </remarks>
public static SomeConfigurationSection GetSection(Configuration config,
string definedName)
{
if (config == null )
throw new ArgumentNullException("config ",
"The Configuration object can not be null. ");

if (String.IsNullOrEmpty(definedName))
definedName = "someConfiguration ";

SomeConfigurationSection section = config.GetSection(definedName)
as SomeConfigurationSection;

if (section == null )
throw new ConfigurationException("The < " + definedName +
"> section is not defined in your .config file! ");

return section;
}
#endregion
}

 

通过传递Configuration

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

代码

 

以同样的方式配置节组也可以定义多次。这使得一个通常的自定义配置结构在同一个应用程序中,同时以多种方式使用,而且使得自定义配置可以重用。因为
可能在一个.config文件中一个配置节定义多次,最终实现上述的模式包括一个简单的实例缓存。每次用不同的definedName

调用GetSection(string)


将返回不同的配置节对象且存储在缓存中。连续以相同的名字调用将返回相同的缓存实例。这种模式的另一个重要方面是缺少为两个新版本的GetSection

(以一个Configuration


象作为参数的GetSection

方法)的缓存。用Configuration

对象比用ConfigurationManager

WebConfigurationManager
花费更大的开销。通过配置管理器
(manager)调用GetSection()

方法将完全缓存
节,而通过Configuration

对象调用将导致节每次都要
被解析。一般来说,Configuration

对象只有当需要保
存配置更改是才使用。配置管理器类应该被用来访问读取配置。这将保证使用配置设置时性能最佳。

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

代码

 

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

代码

 

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

11、高级配置主题

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

  • 解码.NET 2.0配置之谜
  • 破解.NET 2.0配置之谜

12、附录

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

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

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

代码

 

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

  • /wwwroot/web.config
    • first
      = C98E4F32123A
    • second
      = DD0275C8EA1B
    • third
      = 629B59A001FC
  • /wwwroot/firstapp/web.config
    • first
      = FB54CD34AA92
    • second
      = DD0275C8EA1B
    • third
      = 629B59A001FC
    • fourth
      = DE67F90ACC3C
  • /wwwroot/anotherapp/web.config
    • first
      = C98E4F32123A
    • second
      = DD0275C8EA1B
    • third
      = 629B59A001FC
    • fourth
      = 123ABC456DEF
    • fifth
      = ABC123DEF456
    • sixth
      = 0F9E8D7C6B5A
  • /wwwroot/anotherapp/childapp/web.config
    • first
      = C98E4F32123A
    • third
      = 629B59A001FC
    • fifth
      = ABC123DEF456
    • seventh
      = ABC123DEF456
    • ninth
      = 0F9E8D7C6B5A
  • /wwwroot/lastapp/web.config
    • first
      = AABBCCDDEEFF
    • second
      = 112233445566
    • third
      = 778899000000
    • 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

    根元素,你可以指定一个备用,外部的配置设置
    将被加载。

    代码
    
    

    在上面的配置文件中,<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
    Rista


    Unraveling the Mysteries of .NET 2.0 Configuration

    抱歉!评论已关闭.