深入剖析ASP.NET的编译原理之二:预编译(Precompilation)

来源:互联网 发布:马云用淘宝怎么挣钱 编辑:程序博客网 时间:2024/05/08 11:01

在本篇文章的第一部分:[原创]深入剖析ASP.NET的编译原理之一:动态编译(Dynamical Compilation),详细讨论了ASP.NET如何进行动态编译的,现在我们来谈谈另外一种重要的编译方式:预编译(Precompilation)。

1.为什么要进行预编译

ASP.NET 2.0的编译方式大体可以分成两种:动态编译和预编译,要回答为什么要进行预编译,我们先要看看动态编译有什么不好的地方。我们回顾一下上一篇介绍的ASP.NET进行动态编译的简单的流程:当来自Brower的一个基于aspx的Http request抵达Web server,IIS handle这个request,通过分析注册在IIS中的Application Mapping,将Request 传给aspnet_isapi.dll ISAPI extension。ISAPI extension通过HttpRuntime进入Http Runtime Pipeline,HttpRuntime为每个Request创建一个单独的HttpContext对象,用于保存request的Context信息。在Http Runtime Pipeline中,Http request会被注册的一系列的Http module处理,比如OutputCache Module,Session Module,Authentication Module,AuthorizationErrorHandler Module等等。在Pipeline的终端,ASP.NET需要需要根据request创建对应的HttpHandler对象来处理该Request,并生成结果Response到Client。对于一个基于Aspx的Http request,对应的Http handler对象一般就是一个System.Web.UI.Page对象。

ASP.NET会先判断对应的Page type是否存在于被Cache的Assembly中,如果存在,直接创建Page对象,否则ASP.NET会先对该Page的相关的Source code (包括code behind,html等等) 进行编译,我们也说过这种编译是一Directory为单位的,也就是说,处于同一个Directory下的需要编译的文件会被编译成到同一个Assembly中。编译生成的Assembly会被Cache,用于后续的Request。

正是因为对资源的首次访问会导致一次编译(这样说不太准确,因为动态编译是以directory为单位进行的,应该对对某个Directory下的资源进行首次访问),这样会严重降低Web Application的响应速度。所以我们为了避免这种情况,需要预先对web site进行编译,所以提高web site的响应是进行预编译的最重要的原因。

同时动态编译就以为着Web server上放置的是Source code,而且他们是可被修改的。而对于一个开发完毕的Web Application,我们更希望以Binary Assembly的方式进行部署,这样Server上部署的都是Binary Assembly,不怕被别人篡改而导致系统的崩溃,从知识产权来讲,也更利于保护商业秘密。这也是我们为什么要进行预编译的另一个原因。

下面我们就来讲讲如何进行预编译,以及与编译背后的原理。同时在这里我需要特别提出的是,在上一部分讲的一些术语和原理,比如Preservation file,FastObjectFactory,同样适用于预编译,重复的内容,在这里就不必再介绍了。同时我也将沿用上一部的Sample。如果想看看相关的内容,请参阅[原创]深入剖析ASP.NET的编译原理之一:动态编译(Dynamical Compilation)。

2.In Place Pre-compilation V.S. Pre-compilation for Deployment

对于预编译,有可以分为In Place Pre-compilation和Pre-compilation for Deployment,In Place Pre-compilation很简单,实际上就是把整个Web site编译到我们一个临时的目录下面,这个临时目录也就是我们在介绍动态编译提到的那个临时目录。而且这个编译的方式,包括生成的文件也和动态编译完全一样,唯一不同就是编译的时间:预先编译,编译的范围:整个Web site。这种编译就是你常用的在VS的build。这种编译方式一般用于开发阶段。

为了部署为目的的编译是我们今天讨论的重点,下面我们就着重来讨论Pre-compilation for Deployment。

注:在ASP.NET的编译都是通过一个叫做aspnet_compiler的工具执行的,该工具随ASP.NET 2.0一起发布,你完全可以利用此工具以命令行的方式的执行编译,并通过传递不同的命令行开关设置不同的编译选项。该工具被置于了VS中,使你可以利用VS进行可视化的编译。

3.Non-updatable Pre-compilation V.S. Updatable Pre-compilation

ASP.NET 2.0为我们提供了几种不同方式的预编译和部署。为了弄清楚这些预编译和部署方式,我们先来回顾一下ASP.NET 1.x下的编译方式。我们知道在ASP.NET 1.x时代对整个Web site进行编译,实际上我们只会对所有C#和VB.NET等后台代码进行编译,并生成一个单一的Assembly。而Web page的aspx是不会参与编译的。所以当我们访问一个Web page的时候,ASP.NET必须对aspx进行动态编译。

这一切之所以能够进行是因为Web page采用的是aspx + code behind的模式。

<%@ Page Language="C#" AutoEventWireup="false"   Codebehind="Default.aspx.cs"   Inherits="Default" %>


public partial class Default : System.Web.UI.Page
{   
      protected void Page_Load(object sender, EventArgs e)    
     {    
     }
}

从上面我们可以看到aspx和Code behind是一种继承的关系,aspx继承和它对应的Code Behind。ASP.NET可以把Code behind和aspx分开进行编译,把它们编译到不同的Assembly中。我们就是上面的Code为例,我们现在若对该Web site进行编译的话,Default.aspx.cs会被编译到一个Assembly中,假设这个Assembly为App_Web.dll. 我们把该Dll和aspx部署到Production Server上。如果我们现在访问defaut.aspx。ASP.NET会对aspx进行动态编译,生成的Assembly可以暂时成为App_Web_aspx.dll。对于Default.aspx,如果我们如C#代码来描述的话,应该像下面一样定义:

public class default_aspx:Default
{   
}

这种编译方式,我自己把它叫做对asXx的动态编译。在ASP.NET2.0 中也沿用了这种编译方式。这种编译方式的主要特征是对Code behind和所有的后台代码进行预编余,aspx(确切地说应该是asXx:asax,asmx,asax等)原样部署。由于这种方式的预编译,asXx是可以修改的(当然这种修改是有一定限制的,因为code behind已经编译好了,所以这种修改只可能是和code behind无关的修改),所以又叫做Updatable Pre-compilation。

除了Updatable Pre-compilation之外,ASP.NET还提供另外一种高效的预编译方式,Non-updatable Pre-compilation,之所以叫做不可修改的预编译,这是因为:这种编译方式把asXx、Code behind、后台代码甚至是部分Resource都进行预编译,从而避免了运行时对asXx的动态编译,从而最大程度地提高了整个Web site的响应。在部署的时候,我们除了把生成的Assembly进行部署之外,所有的通过编译生成的asXx也必须进行部署。 不过需要特别说明的是,此时的asXx文件仅仅是一个占位的文件而已,它里面不具有任何HTML。

4.Partial class

在ASP.NET 1.x,由于采用的aspx + code behind的机制,对于任何一个Web page或者其他ASP.NET 基于axXx的对象来说,都是由两个文件、两个class组成。两个文件是指axXx和code behind,两个class是指Code behind定义的继承自System.Web.UI.Page的class,和一个继承自它的由axXx生成的class。

对于使用过ASP.NET 1.x来说,一定会很熟悉这样一种情况:对于每个在aspx中通过HTML定义的Server Control,在Code behind中必须具有一个对应的protected成员,否则你不能通过编程的方式访问这个Server control。以不同方式呈现的同一个Server control通过ID关联起来,如果在Code behind中改了Server control的ID,Server control的Server端的Event handler将会失去原有的作用。

但是在ASP.NET 2.0来说,这种情况发生了改变,在aspx中的Server control在Code behind中却没有相应的成员变量,但是我们可以毫无障碍地访问到每个Server control。这使得我们的code behind更加简洁,通过避免了Server control在aspx和code bebind中的不匹配的问题。这一切都得益于.NET Framework 2.0提供的partial class的机制:把同一个class分布于不同文件中进行定义。有了这个概念,我们来看ASP.NET 2.0的code behind机制。

比如我们有这样的一个Page:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
     <title>Default</title>
     <link href="Style.css" rel="stylesheet" type="text/css" />  
</head>
<body>
     <form id="form1" runat="server">  
     <div>          
            <asp:Button runat="server" ID="btnRefresh" Text="Refresh" OnClick="btnRefresh_Click" />   
     </div>
     </form>
</body>
</html>

Code behind如下:

public partial class _Default : System.Web.UI.Page
{
     protected void Page_Load(object sender, EventArgs e)
     {
     }
     protected void btnRefresh_Click(object sender, EventArgs e)
     {
         this.Response.Write("The click event of /"Refresh/" button is fired");
     }
}
而实际上,ASP.NET会为我们创建一个隐藏的.cs文件(这个文件有人 把它称之为Sibling partial class):

 

public partial class _Default: IRequiresSessionState
{
     protected Button   _ btnRefresh;
}

这个文件会随着aspx文件的改变而动态变化,所以code behind中的Server control永远和aspx中的Server control是完全匹配的。所以我们说ASP.NET 2.0的Page是由3个文件、两个class组成的。

5.编译的粒度和Assembly的命名

到现在为止,我们所讲的ASP.NET的预编译都是以Directory为单位的,同一个Directory下的所有需要编译的文件被编译到同一个Assembly中。ASP.NET还支持以Page为单位的预编译,也就是每个Page编译成一个Assembly。

在默认的情况下,ASP.NET预编译生成的Assembly名称是随机生成的,也就是每次生成的Assembly都具有不同的name。所以我们在部署Web site的时候,一般需要把原来的Assembly删掉,再部署新的Assembly。不过ASP.NET为我们提供了另外一种选择,使得每次编译生成的Assembly具有相同的名称,这样我们部署的时候就可以直接把新的Assembly 拷贝到Production Server上,自动覆盖掉同名的Assembly。

6. Sample

我们沿用上一部份是用的Sample,我们通过采用不同的预编译方式看看程序将如何运行。

6.1 Non-updatable Pre-compilation


我们采用如上图所示的默认的发布方式,ASP.NET 将会进行Non-updatable Pre-compilation。浏览目标文件夹,我们会发现如下的文件结构.



除了多了一个Bin目录和PrecompiledApp.config之外,整个结构和Source code中的结构完全一样。通过上面的分析,我们知道这种预编译方式是将asXx、code behind、后台代码已经Resource一起编译成Assembly。我们说过对于这样的预编译方式,aspx仅仅是一个站位的文件而以,其中HTML已经没有任何意义了,那么对于编译后的aspx中到底是什么东西呢。我们来一探究竟。打开每个aspx都是一段如下如下一样文字,并无任何HTML。

This is a marker file generated by the precompilation tool, and should not be deleted!

PrecompiledApp.config里面具有一段简短的configuration,表明version和是否可以进行进一步的修改。

<precompiledApp version="2" updatable="false"/>

所有的Assembly被编译到Bin目录中,我们来看看到底生成了一些什么样的文件在Bin目录中。

在Bin目录由两类文件构成:Assembly和以complied作为扩展名的Preservation file。Preservation file的内容和作用在第一部分已经详细介绍过了,相信大家不会感到陌生。Preservation file在这里和动态编译所起的作用一样。唯一有一点不同的是,他的结构更加简洁,去掉的Dependence file的列表,因为对于Non-updatable Pre-compilation来说,每个Page的以来的文件都是不可更改的。

<preserve resultType="6" virtualPath="/Artech.ASPNETDeployment/App_Code/" hash="439abe7d" filehash="" flags="140000" assembly="App_Code" />

我们来运行以下程序,和动态编译情况下的输出结果比较,看看有什么不同。我们照例先运行Default Page。

输出的结果印证我们前面的讨论:处于同一个目录下的Default 和Default2被编译到同一个Assembly中,关注于处理逻辑的code behind的class name为Default和Default2,关注与可视化界面render的aspx对应的class name被加上的_aspx后缀,如果对default_aspx和default2_aspx进行Reflect的话,你会发现他们分别继承Default和Default2,而后者直接继承自System.Web.UI.Page。所以default_aspx和default2_aspx是真正的意义上基于Web page的Http handler。像动态编译一样,预编译生成一个基于Assembly的FastObjectFactory Type,对该对象的描述请参照第一部分。

有了前面的理论基础,相信大家已经猜到这时候,我浏览Part I下的Page1和Page2时的输出是什么 样子,由于预编译是以目录为单位的,我们对Part I下的任何一个page的访问,都会加载相同的Assembly,所以此时对这两个Page的访问会得到一样的输出结果:

 

6.2 Updatable Pre-compilation

接下来我们来对Web Page进行Updatable Pre-compilation,相关的编译设置如下:选择Allow this precompiled site to be updatable。

生成的文件及其结构和进行Non-updatable Pre-compilation,不同的又一下3点:

1、PrecompiledApp.config:updatable被设置为true。

<precompiledApp version="2" updatable="true"/>

2、asXx和我们进行开发时内容一样,你如aspx包含的就是HTML,我们可以在部署之后对他们进行和code behind无关的修改。

3、Preversation file中有加上了Page对应的dependence file列表。

<?xml version="1.0" encoding="utf-8"?>
<preserve resultType="9" virtualPath="/Artech.ASPNETDeployment/App_GlobalResources/" hash="439abe7d" filehash="ff21249472dbf6cb" flags="140000" assembly="App_GlobalResources" resHash="1cba48dd56e28538" />

我们来运行一下Web site,看看现在的输出结果又有何不同。首先打开Default Page:



通过上图,我们发现此时加载了两个相关的Assembly。我们来分析一下为什么会这样。在分析Updatable Pre-compilation时,我们说过:asXx是不会才与编译的,只有他们的code behind, 所有的后台代码,资源文件才会本编译。对于一个page 来说,page的code behind被编译到Assembly中,aspx则不会。Aspx在运行时实行动态编译,所以aspx是可被修改的。在本例中,我们访问Default Page,ASP.NET先对aspx进行编译,其对应的class name为default_aspx,由于default_aspx继承与Default,并且Default存在于预编译生成的Assembly中,所以这个Assembly被加载进来。

由于同一个page最重本编译到两个不同的Assembly中,所以我们此时访问Part I中的Page1或者Page2,又会有两个Assembly被加载进来:

6.3 以Page为单位进行预编译

前面我们进行的都是以directory为单位的预编译,现在我们缩小编译的粒度,以Page为单位进行编译。我们选择了“Use fixed naming and single page assemblies”选项。那么现在进行的是 基于单个page的non-updatable pre-compilation。通时由于采用的是fixed naming的编译方式,每次进行编译生成的Assembly的名称都是一样的。

现在我们来看看,编译之后生成的Assembly:

我们看到编译器为每个Page生成了一个单独的Assembly。此时运行程序,你看到的又将不同。如果此时你访问Default Page,你将看到:

是不是和上面不同,Assembly只有Default对应的两个Type,没有了处于同一个目录下的Default2的Type。因为Default2有它独自的Assembly .所以你该会想到,如果我们现在每访问一个没有被访问过的Page,就会有一个新的Assembly被加载。比如我么访问Part I下的Page1:

6.4编译强类型的Assembly

我们知道可以通过一个Public key/Private key pair对Assembly进行签名,进而把它部署到GAC中,我们来看看如何做。

首先我通过SN.exe生成Public key/Private key pair并保存到一个文件中(比如D:/MyKey.keys),然后进行如下的编译设置

那么我们进行编译就会生成强类型的Assembly。我们可以运行我们的程序来证明:



每个Assembly有具有一样的PublicKeyToken,因为我们使用的一样的Public key/Private key pair进行对每个Assembly签名的。 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 在商场买到假货怎么办 网上买到假手机怎么办 网上买了假手机怎么办 网银转账被骗了怎么办 支付宝被骗了钱怎么办 被支付宝骗了钱怎么办 头发出油怎么办小妙招 照相的时候脸歪怎么办 怀孕两个月同床了怎么办 小姐被警察抓了怎么办 我的世界迷路了怎么办 砸到手指甲很痛怎么办 手被锤子砸肿了怎么办 拇指突然疼肿了怎么办 手指甲压紫了痛怎么办 砸到手指头肿了怎么办 小婴儿体重不长怎么办 2岁半宝宝不说话怎么办 米兰叶子掉光了怎么办 qq雷霆战机闪退怎么办 微信限额20万怎么办 欧拉方程x<0怎么办 柿子核吃下去了怎么办 橡胶底白色变黄怎么办 20了丁丁还很小怎么办 5角硬币吞进肚子怎么办 孩子吞了5角硬币怎么办 左腿比右腿微粗怎么办 一个腿长一个腿短怎么办 刚买的毛笔很硬怎么办 我哥哥太爱我了怎么办 如果婚姻无法维持该怎么办 捡了一只流浪狗怎么办 微博加载不出来怎么办 我的脾气很暴躁怎么办 歌单恢复已删除怎么办 家里破产了我该怎么办 对付有心计的人怎么办 上嘴唇无故肿了怎么办 下嘴唇肿了一半怎么办 狗狗早上叫不停怎么办