.Net源码之Page类
自.NetFrameWork开源以来已经一段时间了,以下是我关于这些开源代码的一点理解,本篇主要讨论了Page类本身,和生命周期中的初始化与加载阶段在Page类代码中的体现.借此篇只是希望抛砖引玉,让大家能够更多的关注与源代码的研究,让我们在开发的时候能够有更深层次的理解.对于出现的Error等,我们能够更加清晰的理解这个机制.由于水平有限,不足之处望其谅解!当然希望大家能够指出.^_^!(让臭鸡蛋来的更猛烈些吧!^_^)
我们用Asp.Net开发的所有的Web窗体页都是直接或者间接的继承之Page类。我们今天讲的就是这个Page类,在我们看这个Page类之前,我们先来看一下Asp.Net页的生命周期。因为Asp.Net页的加载的过程就是严格的根据这个生命周期的过程来执行的。
根据MSDN对于ASP.NET页生命周期的定义,有以下8个方面:页请求,开始,页初始化,页加载,验证,回发事件,呈现,卸载。
既然我们这个Page类是严格按照这个生命周期阶段来,那么我们这个类的入口点在哪里呢?
现在我们先记下这8个生命周期阶段和这个问题,接下来就来看看这个Page类。我们先从MSDN对这个类的解析开始,MSDN对Page类的解析有以下几个方面:
1. 表示从 ASP.NET Web 应用程序的宿主服务器请求的 .aspx 文件
2. Page类与扩展名为 .aspx 的文件相关联。这些文件在运行时被编译为 Page对象,并被缓存在服务器内存中。
3. Page对象充当页中所有服务器控件的命名容器。
4. Page类是一个用作 Web 应用程序的用户界面的控件
对于第1条我们大家都已经清楚了,第2,3条我们本次不讨论。单看第4条,忽略定语,就是 “Page类是控件”。既然是控件,那么Page类肯定是直接或者间接的继承自Control类了。
那么下面我们从源代码上来看一下Page类的定义是怎么样的。
public class Page: TemplateControl, IHttpHandler
继承了TemplateControl类,并且实现了接口IHttpHandler。现在我们可以肯定TemplateControl类肯定是直接或者间接继承自Control类的,查看源代码发现事实也是如此。现在我们不管这个TemplateControl类,如果大家感兴趣的话,可以通过MSDN,查看源代码自己去了解,这里不做解释了。我们关注的重点其实是接口IHttpHandler,关于IHttpHandler的解析大家看看MSDN吧,我们下面直接看一下它的源代码,发现定义了一个属性和一个方法,我们主要来关注一下这个方法:
void ProcessRequest(HttpContext context);
MSDN对此方法的解释:通过实现 IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。对于这个IHttpHandler接口,我暂时建议大家去关注一下,这个东东非常有用,我们可以利用它来做很多事情,比如,我们可以利用它来防止图片被盗链,或者重写URL。
说明这个是个入口点,对于Web请求的处理,通过此方法开始,那么我们就知道了Page类的入口点在哪里了。^_^
那么我们就来看看这个Page的入口点函数吧!我们查看了这个函数会发现,它将会调用另外一个函数,虽然这个入口点函数没甚么可以说的,但是有一个关键点,我们看一下如下定义:
public virtual void ProcessRequest(HttpContext context)
不知道大家发现没有,整个是虚函数,那就是我们可以在自己的页面中Override它,那么这个对我们有甚么用呢?主要的一点就是我们控制了生命周期开始的开关。如果我们不需要这个过程,我们就可以Override了,然后不调用后面的过程。对于文字描述,我们可能比较迷惑。甚么时候我们不需要它呢?现在说个例子,我们大家都会写一种页面,就是用来页面转向的,那时候我们完全不需要甚么乱七八糟的生命周期这么多阶段(不包括页请求,开始等,这些肯定有,^_^),直接可以在Override函数里面执行转向了,免的麻烦。当然这个例子有点牵强,但是也就是这个意思,当不需要其后的生命周期阶段的时候,我们可以将其Override了。
现在我们可以来看一下这个被调用的函数了,如下:
private void ProcessRequestWithNoAssert(HttpContext context) {
SetIntrinsics(context);
ProcessRequest();
}
我们要在这个函数这里停顿一下,因为这里做了一些有意思的工作。就是SetIntrinsics(context)函数。这个函数会调用另外一个重载函数,我们就来看看这个重载函数实现了甚么我们需要特意来看看它。
private void SetIntrinsics(HttpContext context, bool allowAsync) {
_context = context;
_request = context.Request;
_response = context.Response;
_application = context.Application;
_cache = context.Cache;
这些_context, _request, _response, _application, _cache其实就是对应Page.Context,Page.Request,Page.Request,Page.Response,Page.Application,Page.Cache。现在我们知道了这几个Page属性是在哪里被初始化了。而且我们知道了最重要的一点就是,这几个属性原来都只是一个引用罢了(不是说是引用类型哦!)。那这个有甚么用处呢?
我们暂且先记下这个问题。回到函数ProcessRequestWithNoAssert往下看,接着调用函数ProcessRequest()。我们也来看看这个函数:
private void ProcessRequest() {
Thread currentThread = Thread.CurrentThread;
CultureInfo prevCulture = currentThread.CurrentCulture;
CultureInfo prevUICulture = currentThread.CurrentUICulture;
ProcessRequest(true /*includeStagesBeforeAsyncPoint*/, true /*includeStagesAfterAsyncPoint*/);
}
这里没甚么有意思的,就是设置了一些本地化信息,接着调用了这个重载函数。有兴趣的朋友可以去看看这个重载函数,这里就不讲了,这个重载函数调用了主要函数,我们的生命周期阶段都是在这个函数里面完成的。就是这个
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint)
这个函数的代码就不贴了,大家对照整个函数的代码来看吧!
现在我们可以来回顾一下这个生命周期阶段了,对于页生命周期阶段中的页请求,开始。到这里已经过了,现在我们首先来看看这个页初始化阶段吧。我们从源代码里看,这个请求有3个阶段:预请求,请求,请求完毕。对应的函数为:PerformPreInit();InitRecursive(null); OnInitComplete(EventArgs.Empty);
我们可以在Page类代码的4060行开始发现以上这些代码,那么在这个函数中预请求之前发生了甚么事情呢?对于这些事情我大家可以自己去看看,^_^,我们现在只关心下这个生命周期阶段。这里对预请求与请求完毕都不将做叙述,他们主要触发了各自对应的事件。我们主要来看看这个请求的函数:InitRecursive(null)。顾名思义,这个函数将是递归的初始化。
而这个InitRecursive(null)函数是父类Control类的成员,并且是个虚函数,如下定义:
internal virtual void InitRecursive(Control namingContainer)
而且我们Page类也没有Override。所以我们将会执行Control类中的这个函数。我们来看看这个函数的主要代码:
int controlCount = _occasionalFields.Controls.Count;
for (int i = 0; i < controlCount; i++) {
Control control = _occasionalFields.Controls[i];
// Propagate the page and namingContainer
control.UpdateNamingContainer(namingContainer);
if ((control._id == null) && (namingContainer != null) && !control.flags[idNotRequired]) {
control.GenerateAutomaticID();
}
control._page = Page;
control.InitRecursive(namingContainer);
}
这个是个递归的过程,其中_occasionalFields.Controls表示当前控件的所有子控件集合。根据代码,我们可以发现,先遍历子控件,然后子控件再初始化,然后子控件的子控件再初始化,依次递归,直到最里层的控件为止。从中我们可以得到甚么呢?只有1条:初始化的时候是从最里层的控件开始初始化,然后再依次向外初始化。那么到最里层控件会执行甚么呢?看下面的代码,我们就清楚了
if (_controlState < ControlState.Initialized) {
_controlState = ControlState.ChildrenInitialized; // framework also initialized
if ((Page != null) && !DesignMode) {
if (Page.ContainsTheme && EnableTheming) {
ApplySkin(Page);
}
}
if (_adapter != null) {
_adapter.OnInit(EventArgs.Empty);
}
else {
OnInit(EventArgs.Empty);
}
_controlState = ControlState.Initialized;
}
我们发现主要也就是触发这个控件的初始化事件。所以我们页面上控件的初始化事件比页面初始化事件被触发的早。
以上就是这个生命周期阶段中初始化的整个过程,我们简单回顾一下,其实只有一句话:控件初始化的时候,是先初始化最里层的控件,再依次向外初始化。
接下去是页面加载阶段,同样的有预加载,加载,加载完毕3个过程,我们也只是来看看这个加载阶段。但是注意1点,我们在初始化与加载之间,还有一个阶段,一个非常重要的阶段,就是LoadViewSate,载入视图阶段。我们暂且不说,先来看看这个加载阶段的函数代码。
首先分析LoadRecursive();函数,这也是个会起到递归作用的函数,同时也是Control类的成员函数,看下面的定义,
internal virtual void LoadRecursive()
这是一个只供那不使用的虚函数,那么我们就没法子Override了。接这看看里面的代码。在这之前,我们先猜一下,有下面2点:
1.这个函数是个递归的过程,那么肯定会遍历所有的控件进行加载。
2.这个函数里面肯定会触发OnLoad事件。
那么根据初始化的过程,我们这里面是不是也是:加载控件的时候,是先加载最里层的控件,再依次向外加载呢?
跟着这个问题,我们来看看下面的代码:
if (_controlState < ControlState.Loaded) {
if(_adapter != null) {
_adapter.OnLoad(EventArgs.Empty);
}
else {
OnLoad(EventArgs.Empty);
}
}
// Call Load on all our children
if (_occasionalFields != null && _occasionalFields.Controls != null) {
string oldmsg = _occasionalFields.Controls.SetCollectionReadOnly(SR.Parent_collections_readonly);
int controlCount = _occasionalFields.Controls.Count;
for (int i = 0; i < controlCount; i++) {
_occasionalFields.Controls[i].LoadRecursive();
}
_occasionalFields.Controls.SetCollectionReadOnly(oldmsg);
}
我们看这个代码,其实一眼就清楚了,跟初始化的时候的代码顺序变了一下,触发事件的在前面,遍历控件的在后面。那么这么做也就只有一个结果了,就是先触发事件,再遍历控件。总结一下就是:加载控件的时候,先加载最外层的控件,再依次下内加载控件。刚好跟初始化的相反。也就是先加载页面上的Page_Load,再去执行各个控件上的OnLoad事件。上面的代码也没其他的好说了,只要记住这一点就够了。
我们用Asp.Net开发的所有的Web窗体页都是直接或者间接的继承之Page类。我们今天讲的就是这个Page类,在我们看这个Page类之前,我们先来看一下Asp.Net页的生命周期。因为Asp.Net页的加载的过程就是严格的根据这个生命周期的过程来执行的。
根据MSDN对于ASP.NET页生命周期的定义,有以下8个方面:页请求,开始,页初始化,页加载,验证,回发事件,呈现,卸载。
既然我们这个Page类是严格按照这个生命周期阶段来,那么我们这个类的入口点在哪里呢?
现在我们先记下这8个生命周期阶段和这个问题,接下来就来看看这个Page类。我们先从MSDN对这个类的解析开始,MSDN对Page类的解析有以下几个方面:
1. 表示从 ASP.NET Web 应用程序的宿主服务器请求的 .aspx 文件
2. Page类与扩展名为 .aspx 的文件相关联。这些文件在运行时被编译为 Page对象,并被缓存在服务器内存中。
3. Page对象充当页中所有服务器控件的命名容器。
4. Page类是一个用作 Web 应用程序的用户界面的控件
对于第1条我们大家都已经清楚了,第2,3条我们本次不讨论。单看第4条,忽略定语,就是 “Page类是控件”。既然是控件,那么Page类肯定是直接或者间接的继承自Control类了。
那么下面我们从源代码上来看一下Page类的定义是怎么样的。
public class Page: TemplateControl, IHttpHandler
继承了TemplateControl类,并且实现了接口IHttpHandler。现在我们可以肯定TemplateControl类肯定是直接或者间接继承自Control类的,查看源代码发现事实也是如此。现在我们不管这个TemplateControl类,如果大家感兴趣的话,可以通过MSDN,查看源代码自己去了解,这里不做解释了。我们关注的重点其实是接口IHttpHandler,关于IHttpHandler的解析大家看看MSDN吧,我们下面直接看一下它的源代码,发现定义了一个属性和一个方法,我们主要来关注一下这个方法:
void ProcessRequest(HttpContext context);
MSDN对此方法的解释:通过实现 IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。对于这个IHttpHandler接口,我暂时建议大家去关注一下,这个东东非常有用,我们可以利用它来做很多事情,比如,我们可以利用它来防止图片被盗链,或者重写URL。
说明这个是个入口点,对于Web请求的处理,通过此方法开始,那么我们就知道了Page类的入口点在哪里了。^_^
那么我们就来看看这个Page的入口点函数吧!我们查看了这个函数会发现,它将会调用另外一个函数,虽然这个入口点函数没甚么可以说的,但是有一个关键点,我们看一下如下定义:
public virtual void ProcessRequest(HttpContext context)
不知道大家发现没有,整个是虚函数,那就是我们可以在自己的页面中Override它,那么这个对我们有甚么用呢?主要的一点就是我们控制了生命周期开始的开关。如果我们不需要这个过程,我们就可以Override了,然后不调用后面的过程。对于文字描述,我们可能比较迷惑。甚么时候我们不需要它呢?现在说个例子,我们大家都会写一种页面,就是用来页面转向的,那时候我们完全不需要甚么乱七八糟的生命周期这么多阶段(不包括页请求,开始等,这些肯定有,^_^),直接可以在Override函数里面执行转向了,免的麻烦。当然这个例子有点牵强,但是也就是这个意思,当不需要其后的生命周期阶段的时候,我们可以将其Override了。
现在我们可以来看一下这个被调用的函数了,如下:
private void ProcessRequestWithNoAssert(HttpContext context) {
SetIntrinsics(context);
ProcessRequest();
}
我们要在这个函数这里停顿一下,因为这里做了一些有意思的工作。就是SetIntrinsics(context)函数。这个函数会调用另外一个重载函数,我们就来看看这个重载函数实现了甚么我们需要特意来看看它。
private void SetIntrinsics(HttpContext context, bool allowAsync) {
_context = context;
_request = context.Request;
_response = context.Response;
_application = context.Application;
_cache = context.Cache;
这些_context, _request, _response, _application, _cache其实就是对应Page.Context,Page.Request,Page.Request,Page.Response,Page.Application,Page.Cache。现在我们知道了这几个Page属性是在哪里被初始化了。而且我们知道了最重要的一点就是,这几个属性原来都只是一个引用罢了(不是说是引用类型哦!)。那这个有甚么用处呢?
我们暂且先记下这个问题。回到函数ProcessRequestWithNoAssert往下看,接着调用函数ProcessRequest()。我们也来看看这个函数:
private void ProcessRequest() {
Thread currentThread = Thread.CurrentThread;
CultureInfo prevCulture = currentThread.CurrentCulture;
CultureInfo prevUICulture = currentThread.CurrentUICulture;
ProcessRequest(true /*includeStagesBeforeAsyncPoint*/, true /*includeStagesAfterAsyncPoint*/);
}
这里没甚么有意思的,就是设置了一些本地化信息,接着调用了这个重载函数。有兴趣的朋友可以去看看这个重载函数,这里就不讲了,这个重载函数调用了主要函数,我们的生命周期阶段都是在这个函数里面完成的。就是这个
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint)
这个函数的代码就不贴了,大家对照整个函数的代码来看吧!
现在我们可以来回顾一下这个生命周期阶段了,对于页生命周期阶段中的页请求,开始。到这里已经过了,现在我们首先来看看这个页初始化阶段吧。我们从源代码里看,这个请求有3个阶段:预请求,请求,请求完毕。对应的函数为:PerformPreInit();InitRecursive(null); OnInitComplete(EventArgs.Empty);
我们可以在Page类代码的4060行开始发现以上这些代码,那么在这个函数中预请求之前发生了甚么事情呢?对于这些事情我大家可以自己去看看,^_^,我们现在只关心下这个生命周期阶段。这里对预请求与请求完毕都不将做叙述,他们主要触发了各自对应的事件。我们主要来看看这个请求的函数:InitRecursive(null)。顾名思义,这个函数将是递归的初始化。
而这个InitRecursive(null)函数是父类Control类的成员,并且是个虚函数,如下定义:
internal virtual void InitRecursive(Control namingContainer)
而且我们Page类也没有Override。所以我们将会执行Control类中的这个函数。我们来看看这个函数的主要代码:
int controlCount = _occasionalFields.Controls.Count;
for (int i = 0; i < controlCount; i++) {
Control control = _occasionalFields.Controls[i];
// Propagate the page and namingContainer
control.UpdateNamingContainer(namingContainer);
if ((control._id == null) && (namingContainer != null) && !control.flags[idNotRequired]) {
control.GenerateAutomaticID();
}
control._page = Page;
control.InitRecursive(namingContainer);
}
这个是个递归的过程,其中_occasionalFields.Controls表示当前控件的所有子控件集合。根据代码,我们可以发现,先遍历子控件,然后子控件再初始化,然后子控件的子控件再初始化,依次递归,直到最里层的控件为止。从中我们可以得到甚么呢?只有1条:初始化的时候是从最里层的控件开始初始化,然后再依次向外初始化。那么到最里层控件会执行甚么呢?看下面的代码,我们就清楚了
if (_controlState < ControlState.Initialized) {
_controlState = ControlState.ChildrenInitialized; // framework also initialized
if ((Page != null) && !DesignMode) {
if (Page.ContainsTheme && EnableTheming) {
ApplySkin(Page);
}
}
if (_adapter != null) {
_adapter.OnInit(EventArgs.Empty);
}
else {
OnInit(EventArgs.Empty);
}
_controlState = ControlState.Initialized;
}
我们发现主要也就是触发这个控件的初始化事件。所以我们页面上控件的初始化事件比页面初始化事件被触发的早。
以上就是这个生命周期阶段中初始化的整个过程,我们简单回顾一下,其实只有一句话:控件初始化的时候,是先初始化最里层的控件,再依次向外初始化。
接下去是页面加载阶段,同样的有预加载,加载,加载完毕3个过程,我们也只是来看看这个加载阶段。但是注意1点,我们在初始化与加载之间,还有一个阶段,一个非常重要的阶段,就是LoadViewSate,载入视图阶段。我们暂且不说,先来看看这个加载阶段的函数代码。
首先分析LoadRecursive();函数,这也是个会起到递归作用的函数,同时也是Control类的成员函数,看下面的定义,
internal virtual void LoadRecursive()
这是一个只供那不使用的虚函数,那么我们就没法子Override了。接这看看里面的代码。在这之前,我们先猜一下,有下面2点:
1.这个函数是个递归的过程,那么肯定会遍历所有的控件进行加载。
2.这个函数里面肯定会触发OnLoad事件。
那么根据初始化的过程,我们这里面是不是也是:加载控件的时候,是先加载最里层的控件,再依次向外加载呢?
跟着这个问题,我们来看看下面的代码:
if (_controlState < ControlState.Loaded) {
if(_adapter != null) {
_adapter.OnLoad(EventArgs.Empty);
}
else {
OnLoad(EventArgs.Empty);
}
}
// Call Load on all our children
if (_occasionalFields != null && _occasionalFields.Controls != null) {
string oldmsg = _occasionalFields.Controls.SetCollectionReadOnly(SR.Parent_collections_readonly);
int controlCount = _occasionalFields.Controls.Count;
for (int i = 0; i < controlCount; i++) {
_occasionalFields.Controls[i].LoadRecursive();
}
_occasionalFields.Controls.SetCollectionReadOnly(oldmsg);
}
我们看这个代码,其实一眼就清楚了,跟初始化的时候的代码顺序变了一下,触发事件的在前面,遍历控件的在后面。那么这么做也就只有一个结果了,就是先触发事件,再遍历控件。总结一下就是:加载控件的时候,先加载最外层的控件,再依次下内加载控件。刚好跟初始化的相反。也就是先加载页面上的Page_Load,再去执行各个控件上的OnLoad事件。上面的代码也没其他的好说了,只要记住这一点就够了。