.NET配置文件解析过程详解

过程|详解

在我看来,WEB project的开发与WINFORM的开发最大的区别在于web的运行是在Framework上更高一层框架上运行,即ASP。NET框架,程序员在web下的开发可以说是黑盒开发,不是让你去定义程序入口和执行顺序,而是asp.net来调用你的各个方法,程序员做的一切都是一种受控的舞蹈。就像我们调用nunit之类的工具来测试一个dll一样,nunit是容器,是框架,执行哪个方法是由nunt来决定的。因此,也就有了页面执行周期各状态等令刚入门的程序员困惑不已的事,其实,究其根源,在于不了解容器而去使用容器。对于asp.net框架的学习,我们不妨从配置文件开始。

对于程序开发者而言,写配置文件是经常性的工作,如果你写了一个xx.config文件,如果没有详尽的注释,别人恐怕很难读懂,没有良好的配置架构,程序也失去了活力。在我看来,.net配置文件的特点在于反射定义和继承性。

我们访问配置文件时经常觉得配置文件的结构不太符合我们的需要,我们需要从里面更方便地获得自己定义的对象,而不仅仅是key和value,对于自定义配置文件的著述已有很多,在此不再描述,有兴趣的朋友可以访问

 

自定义配置节其实还是在.net配置文件架构的应用而已,我们先来搞懂配置文件的结构,弄清楚.net配置文件的运行方式。下面是machine.config的一部分内容:

<configSections>
     <section name="runtime"  type="System.Configuration.IgnoreSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowLocation="false" />
<sectionGroup name="system.net">
            <section name="authenticationModules" type="System.Net.Configuration.NetAuthenticationModuleHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</ sectionGroup>
</configSections>

  SDK中<section>的定义为:

<section
   name="section name"
   type="configuration section handler class, assembly"
   allowDefinition="Everywhere|MachineOnly|MachineToApplication" 
   allowLocation="true|false" />

<sectionGroup>的定义为:

<sectionGroup
   name="section group name"/>
</sectionGroup>

  我们来看看.net框架内是如何利用这种结构的。反编译System.dll找到GetConfig方法,在里面我们发现实际获取config的工作默认是由实现了IConfigurationSystem的DefaultConfiguationSystem类来实现的。

public static object GetConfig(string sectionName)
{
      if (!ConfigurationSettings._configurationInitialized)
      {
            lock (typeof(ConfigurationSettings))
            {
                  if ((ConfigurationSettings._configSystem == null) && !ConfigurationSettings.SetConfigurationSystemInProgress)
                  {
                        ConfigurationSettings.SetConfigurationSystem(new DefaultConfigurationSystem());
                  }
            }
      }
      if (ConfigurationSettings._initError != null)
      {
            throw ConfigurationSettings._initError;
      }
      return ConfigurationSettings._configSystem.GetConfig(sectionName);
}

  我们再来看DefaultConfigurationSystem,这个类主要包含了machine.config的名称路径的基本信息和一些uri操作,而实际的GetConfig的操作交给了ConfiguationRecord来处理,这个类没有实现任何接口,可见他和DefaultConfiguration是绑定在一起的。
 1internal class DefaultConfigurationSystem : IConfigurationSystem
 2{
 3      // Methods
 4      internal DefaultConfigurationSystem();
 5      object IConfigurationSystem.GetConfig(string configKey);
 6      void IConfigurationSystem.Init();
 7
 8      // Properties
 9      internal static Uri AppConfigPath { get; }
10      internal static string MachineConfigurationFilePath { get; }
11      internal static string MsCorLibDirectory { get; }
12
13      // Fields
14      private ConfigurationRecord _application;
15      private const string ConfigExtension = "config";
16      private const string MachineConfigFilename = "machine.config";
17      private const string MachineConfigSubdirectory = "Config";
18      private const int MaxPathSize = 0x400;
19}
20
事实上所有的配置文件的分析和获取都是在ConfiguationRecord里实现的,作为配置文件分析的第一步,正如我们经常做的一样->加载一个配置文件,这个方法公开为 Load(filename)。DefaultConfiguationSystem的Init()方法中用machine.config创建了一个ConfiguationRecord对象,并将其作为父对象传递给当前程序的ConfiguationRecord对象,当然前提是当前程序有配置文件,比如myapp.config,然后再加载当前程序的配置文件,从而实现配置文件的继承。
void IConfigurationSystem.Init()
{
      lock (this)
      {
            if (this._application == null)
            {
                  ConfigurationRecord record1 = null;
                  string text1 = DefaultConfigurationSystem.MachineConfigurationFilePath;
                  Uri uri1 = DefaultConfigurationSystem.AppConfigPath;
                  this._application = record1 = new ConfigurationRecord();
                  bool flag1 = record1.Load(text1);
                  if (!flag1 || (uri1 == null))
>                  {
                        return;
                  }
                  this._application = new ConfigurationRecord(record1);
                  this._application.Load(uri1.ToString());
            }
      }
}
 

现在我们可以专注于configuationrecord的具体实现了,load方法中得到一个xmltextwriter,并执行.scanfactoriesrecursive和scansectionsrecursive方法。

 reader1 = ConfigurationRecord.OpenXmlTextReader(filename);
            if (reader1 != null)
            {
                  this.ScanFactoriesRecursive(reader1);
                  if (reader1.Depth == 1)
                  {
                        this.ScanSectionsRecursive(reader1, null);
                  }
                  return true;
            }

 scanfactoriesrecursive方法会调用他的一个重载方法来解析<configsections>中的<sectiongroup>,<section>,<remove>,<clear>,我们写配置文件时大小写可不能写错哦,.net没有做toslower之类的转换,直接就是 “== “。在这个方法里程序会将解析得到的sectiongroup以key=tagkey,value= ConfigurationRecord.GroupSingleton的方式存到EnsureFactories里,将section以key=tagkey,value=typestring的方式储存,值得注意的是,这里并没有创建实现IConfigurationSectionHandler的实例对象,而是将其类型名(比如:字符串”system.Configuration.NameValueSectionHandler”)作为值到EnsureFactories,等到后面GetConfig的时候再来反射创建。<remove>则存为ConfigurationRecord.RemovedFactorySingleton。<clear>就清空EnsureFactories。这里的tagkey是各级name的组合,比如”mygroup/mysection”这样以分隔符”/”组合的形式。应该客观地说这部分代码用了很多goto语句,可读性不是太好,但这样写也确实没有什么问题。

   this.CheckRequiredAttribute(text3, "name", reader);
            this.CheckRequiredAttribute(text4, "type", reader);
            this.VerifySectionName(text3, reader);
            string text5 = ConfigurationRecord.TagKey(configKey, text3);
            if (this.HaveFactory(text5) != ConfigurationRecord.HaveFactoryEnum.NotFound)
            {
                  objArray1 = new object[] { text3 } ;
                  throw this.BuildConfigError(SR.GetString("Tag_name_already_defined", objArray1), reader);
            }
            this.EnsureFactories[text5] = text4;
            goto Label_02B6;

scansectionsrecursive方法会解析配置文件里的section实例,并将其tagkey储存到hashtable _unevaluatedSections中,表示尚未evaluated的configkey的集合,可见section实例对象的创建和handler一样,都是fetch when need。在后面的操作Getconfig中会使用他。

    if (this._unevaluatedSections == null)
            {
                  this._unevaluatedSections = new Hashtable();
            }
            this._unevaluatedSections[text2] = null;

现在我们就可以看getconfig方法是怎么来执行得到我们想要的对象的。

public object GetConfig(string configKey)
{
      if (this._error != null)
      {
            throw this._error;
      }
      if (this._results.Contains(configKey))
      {
            return this._results[configKey];
      }
      object obj1 = this.ResolveConfig(configKey);
      lock (this._results.SyncRoot)
      {
            this._results[configKey] = obj1;
      }
      return obj1;
}

如果_result中有对应configkey的section实例,就返回,没有则需要对configkey进行resolveconfig,将解析到的对象保存到_result中并返回给用户。在resolveconfig方法中,可以看到如果当前的配置文件中没有要求的configkey就会返回父级的section实例,比如machine.config里的内容。

public object ResolveConfig(string configKey)
{
      Hashtable hashtable1 = this._unevaluatedSections;
      if ((hashtable1 != null) && hashtable1.Contains(configKey))
      {
            return this.Evaluate(configKey);
      }
      if (this._parent != null)
      {
            return this._parent.GetConfig(configKey);
      }
      return null;
}
 

然后就是evaluate及其后续操作了,主要就是将configkey分解成字符串数组,一层层地去找对应的xmlelement,找到后传给configkey对应的handler,如果该handler没有创建则反射创建,然后由该handler创建section的实例对象,返回给用户,该部分关键代码如下:

  ConfigXmlDocument document1 = new ConfigXmlDocument();
  document1.LoadSingleElement(this._filename, reader);
 config = factory.Create(config, null, document1.DocumentElement);

 现在我们就明白了当我们用system..configurtion.configuationsetting.getconfig的时候发生过什么了。

时间: 2024-09-13 08:47:52

.NET配置文件解析过程详解的相关文章

DNS解析过程详解

DNS解析过程详解   目录(?)[+] 先说一下DNS的几个基本概念: 一. 根域 就是所谓的".",其实我们的网址www.baidu.com在配置当中应该是www.baidu.com.(最后有一点),一般我们在浏览器里输入时会省略后面的点,而这也已经成为了习惯. 根域服务器我们知道有13台,但是这是错误的观点. 根域服务器只是具有13个IP地址,但机器数量却不是13台,因为这些IP地址借助了任播的技术,所以我们可以在全球设立这些IP的镜像站点,你访问到的这个IP并不是唯一的那台主机

DNS解析过程详解【转】

转自:http://blog.chinaunix.net/uid-28216282-id-3757849.html 先说一下DNS的几个基本概念: 一. 根域 就是所谓的".",其实我们的网址www.baidu.com在配置当中应该是www.baidu.com.(最后有一点),一般我们在浏览器里输入时会省略后面的点,而这也已经成为了习惯. 根域服务器我们知道有13台,但是这是错误的观点. 根域服务器只是具有13个IP地址,但机器数量却不是13台,因为这些IP地址借助了任播的技术,所以我

SQL解析过程详解

作者:一帅 简介 SQL任务是ODPS中使用最频繁的一类作业,大部分用户开始使用ODPS时要做的第一件事情就是学习怎么写ODPS的SQL.ODPS SQL是一种非常灵活的语言,兼容大部分的SQL92规范,也对大规模计算场景做了一些特别的定制.有些用户写出的SQL让人看了之后茅塞顿开的感觉,也有一些神级用户经常写一些1000多行的SQL,让人看的只想撞墙.本文会介绍一下SQL是如何分析解析,并拆解成分布式飞天任务的一些实现原理. ps.由于一些历史包袱和工程实现的原因,ODPS某些内部实现细节可能

JavaScript处理解析JSON数据过程详解_javascript技巧

JSON (JavaScript Object Notation)一种简单的数据格式,比xml更轻巧. JSON 是 JavaScript 原生格式,这意味着在 JavaScript 中处理 JSON 数据不需要任何特殊的 API 或工具包. JSON的规则很简单: 对象是一个无序的"'名称/值'对"集合.一个对象以"{"(左括号)开始,"}"(右括号)结束.每个"名称"后跟一个":"(冒号):"

Android的init过程详解(一)init的初始化

本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10 本文及后续几篇文章将对Android的初始化(init)过程进行详细地.剥丝抽茧式地分析,并且在其中穿插了大量的知识,希望对读者了解Android的启动过程又所帮助.本章主要介绍了与硬件相关初始化文件名的确定以及属性服务的原理和实现. Android本质上就是一个基于Linux内核的操作系统.与Ubuntu Linux.Fedora Linux类似.只是Android在应用层专门为移动设备添加了一些特有的支持.既然An

seajs中模块的解析规则详解和模块使用总结

 这篇文章主要介绍了seajs中模块的解析规则详解和模块使用总结,需要的朋友可以参考下 seajs github 模块标识已经说的相对清楚了.但并没有面面俱到,特别是当你需要手写 [模块ID]和[模块依赖]的时候,或者自己写自动化工具来做 transport 的时候(ps:spm貌似适应性不是很强也不易用,毕竟每个项目的目录结构可能相差很大,且不易改变.当然如果他的定位是包管理工具就别指望它来做你的项目的自动化构建工具了),ID的解析规则就需要了解透彻了. 注意事项: 1. 顶级标识始终相对 b

Android的init过程详解(一)(转)

  本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10      本文及后续几篇文章将对Android的初始化(init)过程进行详细地.剥丝抽茧式地分析,并且在其中穿插了大量的知识,希望对读者了解Android的启动过程又所帮助.本章主要介绍了与硬件相关初始化文件名的确定以及属性服务的原理和实现.      Android本质上就是一个基于Linux内核的操作系统.与Ubuntu Linux.Fedora Linux类似.只是Android在应用层专门为移动设备添加了

IIS8 使用FastCGI配置PHP环境支持 过程详解

原文:IIS8 使用FastCGI配置PHP环境支持 过程详解   平时帮朋友们配置过一些PHP环境的服务器,但是一直使用的都是Apache HTTP+PHP,今天呢,我吧IIS+PHP配置方式给大家发一下下~呵呵.   在这里,我使用的是FastCGI模块映射的方式配置的,当然还有ISAPI处理程序映射,不过ISAPI的方式在PHP5.5之后就没有了,FastCGI是推荐的方式,效率相对比较高也稳定. 系统我用的是自己的笔记本,Windows 8.1,IIS是8的,当然Windows Serv

《嵌入式 Linux应用程序开发标准教程(第2版)》——2.2 Linux启动过程详解

2.2 Linux启动过程详解 嵌入式 Linux应用程序开发标准教程(第2版) 在了解了Linux的常见命令之后,下面详细讲解Linux的启动过程.Linux的启动过程包含了Linux工作原理的精髓,而且在嵌入式开发过程中非常需要这方面的知识. 2.2.1 概述 用户开机启动Linux过程如下: (1)当用户打开PC(intel CPU)的电源时,CPU将自动进入实模式,并从地址0xFFFF0000开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址.这时BIOS进行开机自检,并按BI