2004-8-18+ 对构架/窗体/编译的认识
0.推荐看的一本书《Essential ASP.NET with Examples in C#/asp.net基础教程-C#案例版》
这本书的内容大多涉及到一些底层的细节,对一些具体技术的讲解也是“高屋建瓴”的那种,对增强对.net的整体把握很有好处,本文的知识大部分来自这本书。
当然这本书还有vb.net版,要注意的是,不要被这本书的中文译名给骗了,Essential是什么意思,大家心里都有数,不知道译者当初是怎么想的……
1.编译与解释
请求一个传统的asp页面,对网页中的文本进行线形解析,提取所有不是服务器端脚本的内容,并作为响应返回,而网页中的服务器端脚本首先通过一个合适的解释器(jscripe或vbscript)解释执行,执行结果作为响应返回。相反,asp.net网页始终被编译成.net类,并保存在程序集中。第一次访问网页所需的开销包括:加载asp.net工作者进程,对.aspx文件进行语法分析,并将它编译成一个程序集。以后访问就只需执行已编译的代码即可。
2.代码隐藏技术
假设有一名为pagemodel的asp.net网页,命名空间beginner。
当不使用代码隐藏技术时,整个程序就是一个.aspx文件,在编译时生成的类是直接继承自System.Web.UI.Page,并且属于ASP命名空间下,即ASP.pagemodel_aspx。
但如果使用了代码隐藏技术,则整个程序包括一个.aspx文件和一个.cs文件(假设是用c#),在编译时先编译.cs文件,然后再编译.aspx文件。.cs文件当然是直接编译成beginner.pagemodel,该类继承自System.Web.UI.Page,然后轮到.aspx文件,这时编译生成的类是继承自beginner.pagemodel,也就是说.cs文件编译生成的类起到了一个桥梁的作用。
由此可见,代码隐藏是一种创建中间基类的技术。使用这种技术,可以向代码隐藏类中添加字段、方法和事件处理方法,并可以让那些根据.aspx文件创建的类继承这些特性。
3.脚本块和脚本标记
当不使用代码隐藏时,可以在脚本块和脚本标记里对页面进行操作,典型的脚本块和脚本标记的例子为:
<script language="c#" runat=server>
private Label y=new Label();
private void Page_Load()
{
y.Text="ddx";
x.Controls.Add(y);
}
</script>
<asp:placeholder runat="server" id="x"/>
<%
Response.Write(this.GetType());
%>
注意在脚本块和脚本标记里放置的代码,将被放入生成的类定义中,而这两个地方是完全不同的。位于脚本块(<script language="c#" runat=server></script>)内的代码,是直接放到类的定义中,而位于脚本标记(<%%>)之内的代码,将放入类的一个方法主体中,这个方法类似于Control类里的Render方法,在生成网页时调用该方法把脚本标记内的代码输出。
4.控件的声明
当不使用代码隐藏时,在.aspx页面中直接用标记放置一个服务器控件就可以使用,也可以在代码块中操作它的属性,但是不能在脚本块中声明该控件,那将产生重复声明的错误,典型的错误提示为“类“ASP.pagemodel_aspx”已经包含了“x”的定义”。这是因为在编译生成的类中,会自动为用标记声明的控件产生一个受保护的字段用来标识该控件。
但使用代码隐藏时,在.aspx文件中插入一个服务器控件后,还可以在.cs文件中声明该控件,并进行操作,这样做不会产生重复声明的错误。
首先要明白的是,在.cs文件中声明的控件,其标识符必须与.aspx文件中的控件的标识符相同,这个不难理解,因为上面我们已经知道,.aspx生成的类继承自.cs文件生成的类,如果标识符不同,那将不能进行正确的操作。
在.aspx文件进行解析的过程中,解析器使用反射技术查找代码隐藏基类。如果在代码隐藏基类中查到与服务器端控件标识符相匹配的字段,则不管它是公有的还是受保护的字段,都不会在生成的类中为该控件创建一个字段。如果在代码隐藏基类中找不到这样的字段,才会在新生成的类中创建一个这样的字段。用这种办法来保证所有的服务器控件,不管是从代码隐藏基类继承而来的,还是直接在生成的类中声明的,都在生成的类中有一个相应的字段。
5.查找编译生成的文件
假设系统盘为c,则找到这些编译后的文件的方法是先进入C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files,其中v1.1.4322这个目录是我的机器上的目录,该目录是.net framework的版本号(?),进入后会发现一些以你设置的虚拟目录命名的文件夹,确定其中的一个,一直点下去,就会发现那些编译过的文件。可以用vs.net配合Roeder's .NET Reflector查看其细节。
6.配合编译文件查看细节
现在我们找到这些编译了的文件了,可以配合他们来复习一下上面的知识。
先来看上面的那个没有使用代码隐藏技术的页面,文件名为s.aspx,它的部分编译后的类代码如下:
b0fgdgkv.0.cs
//命名空间为ASP
namespace ASP {
//直接继承自System.Web.UI.Page
public class s_aspx : System.Web.UI.Page, System.Web.SessionState.IRequiresSessionState {
......
//使用标记直接插入的服务器控件
protected System.Web.UI.WebControls.PlaceHolder x;
......
//在脚本块内的代码
private Label y=new Label();
private void Page_Load()
{
y.Text="ddx";
x.Controls.Add(y);
}
......
//这个方法用来装在脚本标记内的代码
private void __Render__control1(System.Web.UI.HtmlTextWriter __output, System.Web.UI.Control parameterContainer) {
......
//在脚本标记内的代码
Response.Write(this.GetType());
......
再来看一个使用了代码隐藏技术的,该文件名sp_dr2.aspx和sp_dr2.aspx.cs,命名空间是startmssql,其中.aspx文件编译后的类的部分代码如下:
zti43wya.0.cs
//命名空间为ASP
namespace ASP {
//继承自startmssql.sp_dr2
public class sp_dr2_aspx : startmssql.sp_dr2, System.Web.SessionState.IRequiresSessionState {
......
另外,通过使用Roeder's .NET Reflector,从基本的对象树也可看出这种关系。特别的,当你查看sp_dr2类时,会发现Base Types是System.Web.UI.Page,而Derived Types则是ASP.sp_dr2_aspx,而查看zti43wya.dll时,Base Types为startmssql.sp_dr2
其他的一些细节大家可以自己动手找找 :)
这篇笔记可以整理出来,还得谢谢昨天晚上东方的那个问题。昨晚我回宿舍后又翻了翻Essential这本书,今上午过来后把那些编译文件找出来研究,这才让自己明白了一些。文章里面肯定还有不对的地方,请高手指教。
另外最后再提两个问题,希望可以得到高手的解答 :)
a.目录C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files中,v1.1.4322是不是.net framewok的版本号?
b.不使用代码隐藏技术时的一个.aspx文件,编译完成后的文件-比如.dll-是直接放到以虚拟目录命名的文件夹里,而不是和项目的整个.dll文件集成?