浅析ASP.NET编译器

开发 后端
本文介绍ASP.NET编译器同时负责两次编译,那就能够省去第一次编译手工进行的麻烦,编译工作都由它在运行时负责就好了。

要深入理解ASP.NET动态控件,首先就要深入理解整个ASP.NET对页面的处理过程,由你书写好一个ASPX文件(可能还有一个code-behind文件)到你在浏览器中看到的HTML页面,这中间到底发生了什么事。这其中的第一步就是解释ASPX文件并进行编译,也就是这篇文章要讨论的内容。

由于ASP.NET编译器本身就是一个大话题,所以我决定在本系列文章把这个题目再细分成几篇文章来写。开头第一篇简单叙述编译过程中涉及的各个步骤,让大家了解ASPX中的声明性代码和C#/VB.NET代码如何合并在一起并编译成assembly。在这篇文章之后,再深入了解编译过程中的一些细节,看看一个ASPX中声明性定义的静态控件到底是如何运行起来的。

开始讲编译过程了,首先大家来看两张图,这张是ASP.NET 1.x的编译流程图:

ASP.NET

接下来这张是ASP.NET 2.0的编译流程图:

ASP.NET

这两张图来自官方文档ASP.NET 2.0 的内部变化,大家要注意到代码嵌入(code-beside, inline)与代码隐藏(code-behind)的编译模式是不同的:代码嵌入仅进行一次编译,声明性代码与C#/VB.NET代码都一起编译到一个类里面;代码隐藏则将声明性代码与C#/VB.NET代码分开几次进行翻译/编译,这些代码之间是局部与局部(partial)的关系或是基类与派生类的关系。

图上引人关注的地方就是代码隐藏编译时存在两次的“继承自”关系。第一次继承是很好理解的,用过VS2002/2003的人都记得代码中明确声明本页面的类继承自Page类,那么第二次继承又是怎么来的呢?

先把上面的问题放一边,我们换一种思路来思考,重新想一想我们的C#/VB.NET代码有什么。如果我们在ASPX中放上了一个TextBox,那么两边的代码都会出现它的定义,ASPX代码是<asp:TextBox id="myTextBox" runat="server" />,C#代码是TextBox myTextBox = new TextBox();myTextBox.ID = "myTextBox";。然后我们在此TextBox的后面用HTML写上<div>Please write down something</div>,那么这段HTML仅在ASPX中存在定义,而不在C#代码中存在定义。

接下来我们将C#代码给编译了,然后用ASP.NET引擎运行它(确实能够如此运行,但这不是我们当前关心的事),你猜我们能够看到什么?我们应该能够看到一个TextBox。至于后面那段文字呢,聪明的你应该马上想到它没在C#代码中被定义的,所以不可能被看到。

现在我们明白到了,有一部分逻辑是仅仅在ASPX中有所定义,我们需要将它们添加到C#编译结果上。如何添加这部分的逻辑?ASP.NET选择了继承机制,从C#编译结果的那个类继承,然后在派生类中加入仅在ASPX中定义的逻辑。至于作为声明性语言的ASPX如何编译成MSIL,则属于下一篇文章讨论的内容,在这里就不解释了。

需要说明的是,这两次编译中的第一次必须手动进行的,例如在VS2002/2003中执行编译;第二次编译在运行时进行自动进行。因此改动了ASPX无需重新手动编译,而改动了C#/VB.NET代码则需要手动编译。
ASP.NET 2.0

上面我们解释ASP.NET 1.1的代码隐藏编译时也提到了其中的问题,一个TextBox控件要在两边同时声明,这明显违反了DRY(Don't Repeat Yourself)原则。ASP.NET 2.0为了解决这个问题而引入了新的机制。

所谓的新机制就是C#代码中的那个partial关键字,大家可能都习惯了它的存在,但有没有人曾经想过一个这样的Page继承类的其他partial在哪里呢?如果你在VS2005中作一次项目内搜索,就会发现这个类的其它partial是不存在的,这时候你就该去看看官方文档(例如我上面给出那个)。官方文档会告诉你,另外一个partial就是ASPX,它们会好像两个普通的partial文件那样合并编译,所以在ASP.NET 2.0中我们仅需要一次合并编译就解决了所有问题。然后我要告诉你,官方文档所说的是错误的,ASP.NET 2.0的编译还是好像ASP.NET 1.1那样,只不过根据ASPX中的控件定义生成对应C#定义的工作由IDE转交给了ASP.NET编译器,至于细节你可以去参考我之前写的两篇文章:《ASP.NET 2.0 解决了 Code-Behind 需要控件声明同步的问题》与《ASP.NET 2.0 的编译模型并非完全像 MS 说的那样》。

在ASP.NET编译器捡起了定义同步这项工作后,整个编译过程就都在它的职责范围内了,不再好像ASP.NET 1.x那样先由C#/VB.NET编译器负责隐藏代码的编译,再由ASP.NET编译器负责二次编译。既然ASP.NET编译器同时负责两次编译,那就能够省去第一次编译手工进行的麻烦,编译工作都由它在运行时负责就好了。

现在我们已经对整个编译过程有了了解,大多数编译步骤都很容易理解,无非是叫C#/VB.NET编译器出来做些本职工作,只有一个除外:仅在ASPX中声明的逻辑是如何被编译为MSIL的,因为我们将此作为下一步深入理解的目标,并在下一篇文章中讨论。

这里有一些简单的问题或者是小实验,通过它们可以加深大家对文章的理解,大家可以将答案直接写在文章评论中。

1. 我在Web应用的根目录新建了一个用户控件MyUserControl.ascx,隐藏文件中定义类名称为MyUserControl,我现在需要在页面上动态加载此用户控件,请问以下哪种方法正确?为什么?(提示:ASCX的编译方式与ASPX类似)
1). this.Page.Controls.Add(new MyUserControl());
2). this.Page.Controls.Add(this.Page.LoadControl("~/MyUserControl.ascx"));

2. 在讨论ASP.NET 1.1编译的时候,我说到可以直接运行隐藏代码编译出来的类,并且说应该能看到一个TextBox。事实上这个TextBox可能也无法看到,不过我手上没有VS2002/2003,所以没办法验证。大家有兴趣的话,可以自己去动手做一下实验看看那个TextBox到底是否会出现。在实验之前,让我先说说如何让隐藏代码编译结果直接运行:
1). 打开MSDN,找到IHttpHandler这个条目,然后看看它的示例代码,以及如何在web.config中配置一个路径使用特定的IHttpHandler。
2). 由于Page类本身实现了IHttpHandler,所以隐藏代码编译后的Page继承类也一定是IHttpHandler,在web.config中配置一个使用IHttpHandler的路径,并指向你要测试的隐藏代码类。
3). 在浏览器中访问你配置的路径,你就能够看到纯隐藏代码编译后的执行结果。

【编辑推荐】

  1. 微软发布ASP.NET MVC 2预览版 多项功能更新
  2. ASP.NET服务器自定义控件安全准则
  3. ASP.NET编程规范之编码规范浅析
  4. 关于ASP.NET Session的一点认识
  5. ASP.NET编程工具ASP.NET Web Matrix详细介绍
责任编辑:佚名 来源: yesky
相关推荐

2009-08-07 17:49:44

控件设计器

2009-07-31 12:43:59

ASP.NET MVC

2009-08-05 15:50:13

ASP.NET优点

2009-07-24 13:41:15

ASP.NET AJA

2009-08-05 18:36:12

ASP.NET Che

2009-08-10 13:32:15

ASP.NET TimASP.NET组件设计

2009-08-03 13:38:18

ASP.NET编程模型

2009-08-05 14:46:17

ASP.NET url

2009-07-20 16:23:01

ASP.NET授权模块

2009-07-28 13:35:18

2009-07-23 14:31:20

ASP.NET MVC

2009-07-27 10:18:12

TypeResolveASP.NET

2009-07-28 15:53:43

ASP.NET Web

2009-07-28 16:40:11

ASP.NET异步页面

2009-08-04 17:00:09

ASP.NET禁用Vi

2009-07-27 17:25:53

ASP.NET验证控件

2009-07-27 15:34:11

MembershipASP.NET

2009-08-03 10:07:20

ASP.NET Ses

2009-08-04 17:16:16

ASP.NET代码优化

2009-08-05 16:17:29

ASP.NET For
点赞
收藏

51CTO技术栈公众号