王朝网络
分享
 
 
 

.NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析

王朝asp·作者佚名  2006-01-09
宽屏版  字体: |||超大  

.NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析

http://edu.cn700.com (点击数:

7 时间: 2004-11-23)

.NET 1.1中预编译ASP.NET页面实现原理浅析

MS在发布ASP.NET时的一大功能特性是,与ASP和PHP等脚本语言不同,ASP.NET实际上是一种编译型的快速网页开发环境。这使得ASP.NET在具有开发和修改的简便性的同时,不会负担效率方面的损失。实现上ASP.NET与JSP的思路类似,引擎在第一次使用一个页面之前,会将之编译成一个类,自动生成Assembly并载入执行。

而通过《在WinForm程序中嵌入ASP.NET》一文中我们可以了解到,ASP.NET引擎实际上是可以无需通过IIS等Web服务器调用而被使用的,这就使得手工预编译ASP.NET页面成为可能。实际上这个需求是普遍存在的,早在ASP时代就层有第三方产品支持将ASP页面编译成二进制程序,以提高执行效率和保障代码安全性,而将伴随Whidbey发布的ASP.NET 2.0更是直接内置了预编译ASP.NET页面的功能。

实际上网上早就有人讨论过在ASP.NET 1.1中模拟预编译特性的实现方法,例如以下两篇文章

Pre-Compiling ASP.NET Web Pages

Pre-Compile ASPX pages in .NET 1.1

其思路基本上都是遍历所有需要预编译的页面文件,然后通过模拟Web页面请求的方式,触发ASP.NET引擎的自动预编译机制。这样做的好处是完全模拟真实情况,无需了解ASP.NET引擎的实现原理;但同时也会受到诸多限制,如预编译结果不透明,无法脱离原始ASP.NET页面文件使用等等,而且无法使我们从原理上理解预编译特性的实现。

下面我将分三到四个小节,简要讨论 ASP.NET 自动编译机制的实现、ASP.NET 页面文件编译的实现以及如何在ASP.NET 1.1中实现手动预编译页面和相应分发机制。

[1] 自动预编译机制浅析

本节我们将详细分析讨论.NET 1.1中,ASP.NET引擎内部实现自动页面预编译的原理。

首先,我们所说的ASP.NET页面实际上主要分为四类:

1.Web 应用程序文件 Global.asax

2.Web 页面文件 *.aspx

3.用户自定义控件文件 *.ascx

4.Web 服务程序文件 *.asmx

Web 应用程序文件对于每个Web 应用程序来说是可选唯一的,用来处理ASP.NET应用程序一级的事件,并将被预编译为一个System.Web.HttpApplication类的子类;

Web 页面文件是普通的ASP.NET页面,处理特定页面的事件,将被预编译为一个System.Web.UI.Page类的子类;

用户自定义控件文件是特殊的ASP.NET页面,处理控件自身的事件,将被预编译为一个System.Web.UI.UserControl类的子类;

Web 服务程序文件则是与前三者不太相同的一种特殊页面文件,暂时不予讨论。

然后,前三种ASP.NET文件的编译时机也不完全相同。Web 应用程序文件在此 Web 应用程序文件第一次被使用时自动编译;Web 页面文件在此Web页面第一次被使用时自动编译,实际上是调用 HttpRuntime.ProcessRequest 函数触发预编译;用户自定义控件文件则在其第一次被 Web 页面使用的时候自动编译,实际上是调用 Page.LoadControl 函数触发预编译。

在了解了以上这些基本知识后,我们来详细分析一下自动预编译的实现机制。

HttpRuntime.ProcessRequest 函数是处理Web页面请求的调用发起者,伪代码如下:

以下为引用:

public static void HttpRuntime.ProcessRequest(HttpWorkerRequest wr)

{

// 检查当前调用者有没有作为ASP.NET宿主(Host)的权限

InternalSecurityPermissions.AspNetHostingPermissionLevelMedium.Demand();

if(wr == null)

{

throw new ArgumentNullException("custom");

}

RequestQueue queue = HttpRuntime._theRuntime._requestQueue;

if(queue != null)

{

// 将参数中的Web页面请求放入请求队列中

// 并从队列中使用FIFO策略获取一个页面请求

wr = queue.GetRequestToExecute(wr);

}

if(wr != null)

{

// 更新性能计数器

HttpRuntime.CalculateWaitTimeAndUpdatePerfCounter(wr);

// 实际完成页面请求工作

HttpRuntime.ProcessRequestNow(wr);

}

}

HttpRuntime.ProcessRequestNow函数则直接调用缺省HttpRuntime实例的ProcessRequestInternal函数完成实际页面请求工作,伪代码如下:

以下为引用:

internal static void HttpRuntime.ProcessRequestNow(HttpWorkerRequest wr)

{

HttpRuntime._theRuntime.ProcessRequestInternal(wr);

}

HttpRuntime.ProcessRequestInternal函数逻辑稍微复杂一些,大致可分为四个部分。

首先检查当前HttpRuntime实例是否第一次被调用,如果是第一次调用则通过FirstRequestInit函数初始化;

接着调用HttpResponse.InitResponseWriter函数初始化页面请求的返回对象HttpWorkerRequest.Response;

然后调用HttpApplicationFactory.GetApplicationInstance函数获取当前 Web 应用程序实例;

最后使用Web应用程序实例完成实际的页面请求工作。

伪代码如下:

以下为引用:

private void HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)

{

// 构造 HTTP 调用上下文对象

HttpContext ctxt = new HttpContext(wr, 0);

// 设置发送结束异步回调函数

wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, ctxt);

// 更新请求计数器

Interlocked.Increment(&(this._activeRequestCount));

try

{

// 检查当前HttpRuntime实例是否第一次被调用

if(this._beforeFirstRequest)

{

lock(this)

{

// 使用 Double-Checked 模式 避免冗余锁定

if(this._beforeFirstRequest)

{

this._firstRequestStartTime = DateTime.UtcNow;

this.FirstRequestInit(ctxt); // 初始化当前 HttpRuntime 运行时环境

this._beforeFirstRequest = false;

}

}

}

// 根据配置文件设置,扮演具有较高特权的角色

ctxt.Impersonation.Start(true, false);

try

{

// 初始化页面请求的返回对象

ctxt.Response.InitResponseWriter();

}

finally

{

ctxt.Impersonation.Stop();

}

// 获取当前 Web 应用程序实例

IHttpHandler handler = HttpApplicationFactory.GetApplicationInstance(ctxt);

if (handler == null)

{

throw new HttpException(HttpRuntime.FormatResourceString("Unable_create_app_object"));

}

// 使用Web应用程序实例完成实际的页面请求工作

if((handler as IHttpAsyncHandler) != null)

{

IHttpAsyncHandler asyncHandler = ((IHttpAsyncHandler) handler);

ctxt.AsyncAppHandler = asyncHandler;

// 使用异步处理机制

asyncHandler.BeginProcessRequest(ctxt, this._handlerCompletionCallback, ctxt);

}

else

{

handler.ProcessRequest(ctxt);

this.FinishRequest(ctxt.WorkerRequest, ctxt, null);

}

}

catch(Exception E)

{

ctxt.Response.InitResponseWriter();

this.FinishRequest(wr, ctxt, E);

}

}

HttpRuntime.ProcessRequestInternal函数中,涉及到文件预编译的有两部分:一是获取当前 Web 应用程序实例时,会根据情况自动判断是否预编译Web 应用程序文件;二是在完成实际页面请求时,会在第一次使用某个页面时触发预编译行为。

首先来看看对 Web 应用程序文件的处理。

HttpRuntime.ProcessRequestInternal函数中调用了HttpApplicationFactory.GetApplicationInstance函数获取当前 Web 应用程序实例。System.Web.HttpApplicationFactory是一个内部类,用以实现对多个Web应用程序实例的管理和缓存。GetApplicationInstance函数返回的是一个IHttpHandler接口,提供IHttpHandler.ProcessRequest函数用于其后对Web页面文件的处理。伪代码如下:

以下为引用:

internal static IHttpHandler HttpApplicationFactory.GetApplicationInstance(HttpContext ctxt)

{

// 定制应用程序

if(HttpApplicationFactory._customApplication != null)

{

return HttpApplicationFactory._customApplication;

}

// 调试请求

if(HttpDebugHandler.IsDebuggingRequest(ctxt))

{

return new HttpDebugHandler();

}

// 判断是否需要初始化当前 HttpApplicationFactory 实例

if(!HttpApplicationFactory._theApplicationFactory._inited)

{

HttpApplicationFactory factory = HttpApplicationFactory._theApplicationFactory;

lock(HttpApplicationFactory._theApplicationFactory);

{

// 使用 Double-Checked 模式 避免冗余锁定

if(!HttpApplicationFactory._theApplicationFactory._inited)

{

// 初始化当前 HttpApplicationFactory 实例

HttpApplicationFactory._theApplicationFactory.Init(ctxt);

HttpApplicationFactory._theApplicationFactory._inited = true;

}

}

}

// 获取 Web 应用程序实例

return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(ctxt);

}

在处理特殊情况和可能的实例初始化之后,调用HttpApplicationFactory.GetNormalApplicationInstance函数完成获取Web应用程序实例的实际功能,伪代码如下:

以下为引用:

private HttpApplication HttpApplicationFactory.GetNormalApplicationInstance(HttpContext context)

{

HttpApplication app = null;

// 尝试从已施放的 Web 应用程序实例队列中获取

lock(this._freeList)

{

if(this._numFreeAppInstances > 0)

{

app = (HttpApplication)this._freeList.Pop();

this._numFreeAppInstances--;

}

}

if(app == null)

{

// 构造新的 Web 应用程序实例

app = (HttpApplication)System.Web.HttpRuntime.CreateNonPublicInstance(this._theApplicationType);

// 初始化 Web 应用程序实例

app.InitInternal(context, this._state, this._eventHandlerMethods);

}

return app;

}

构造新的 Web 应用程序实例的代码很简单,实际上就是对Activator.CreateInstance函数的简单包装,伪代码如下:

以下为引用:

internal static object HttpRuntime.CreateNonPublicInstance(Type type, object[] args)

{

return Activator.CreateInstance(type, BindingFlags.CreateInstance | BindingFlags.Instance |

BindingFlags.NonPublic | BindingFlags.Public, null, args, null);

}

internal static object HttpRuntime.CreateNonPublicInstance(Type type)

{

return HttpRuntime.CreateNonPublicInstance(type, null);

}

至此一个 Web 应用程序实例就被完整构造出来,再经过InitInternal函数的初始化,就可以开始实际页面处理工作了。而HttpApplicationFactory实例的_theApplicationType类型,则是结果预编译后的Global.asax类。实际的预编译工作在HttpApplicationFactory.Init函数中完成,伪代码如下:

以下为引用:

private void HttpApplicationFactory.Init(HttpContext ctxt)

{

if(HttpApplicationFactory._customApplication != null)

return;

using(HttpContextWrapper wrapper = new HttpContextWrapper(ctxt))

{

ctxt.Impersonation.Start(true, true);

try

{

try

{

this._appFilename = HttpApplicationFactory.GetApplicationFile(ctxt);

this.CompileApplication(ctxt);

this.SetupChangesMonitor();

}

finally

{

ctxt.Impersonation.Stop();

}

}

catch(Object)

{

}

this.FireApplicationOnStart(ctxt);

}

}

GetApplicationFile函数返回Web请求物理目录下的global.asax文件路径;CompileApplication函数则根据此文件是否存在,判断是预编译之并载入编译后类型,还是直接返回缺省的HttpApplication类型,伪代码如下:

以下为引用:

internal static string HttpApplicationFactory.GetApplicationFile(HttpContext ctxt)

{

return Path.Combine(ctxt.Request.PhysicalApplicationPath, "global.asax");

}

private void HttpApplicationFactory.CompileApplication(HttpContext ctxt)

{

if(FileUtil.FileExists(this._appFilename))

{

ApplicationFileParser parser;

// 获取编译后的 Web 应用程序类型

this._theApplicationType = ApplicationFileParser.GetCompiledApplicationType(this._appFilename, context, out parser);

this._state = new HttpApplicationState(parser1.ApplicationObjects, parser.SessionObjects);

this._fileDependencies = parser.SourceDependencies;

}

else

{

this._theApplicationType = typeof(HttpApplication);

this._state = new HttpApplicationState();

}

this.ReflectOnApplicationType();

}

分析到这里我们可以发现,内部类型System.Web.UI.ApplicationFileParser的GetCompiledApplicationType函数是实际上进行Web应用程序编译工作的地方。但现在我们暂且打住,等下一节分析编译过程时再详细解说。 :)

然后我们看看对 Web 页面文件的处理。

在前面分析HttpRuntime.ProcessRequestInternal函数时我们曾了解到,在获得了Web应用程序实例后,会使用此实例的IHttpAsyncHandler接口或IHttpHandler接口,完成实际的页面请求工作。而无论有否Global.asax文件,最终返回的Web应用程序实例都是一个HttpApplication类或其子类的实例,其实现了IHttpAsyncHandler接口,支持异步的Web页面请求工作。对此接口的处理伪代码如下:

以下为引用:

private void HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)

{

...

// 使用Web应用程序实例完成实际的页面请求工作

if((handler as IHttpAsyncHandler) != null)

{

IHttpAsyncHandler asyncHandler = ((IHttpAsyncHandler) handler);

ctxt.AsyncAppHandler = asyncHandler;

// 使用异步处理机制

asyncHandler.BeginProcessRequest(ctxt, this._handlerCompletionCallback, ctxt);

}

else

{

handler.ProcessRequest(ctxt);

this.FinishRequest(ctxt.WorkerRequest, ctxt, null);

}

...

}

HttpRuntime.ProcessRequestInternal函数通过调用HttpApplication.IHttpAsyncHandler.BeginProcessRequest函数开始页面请求工作。而HttpApplication实际上根本不支持同步形式的IHttpHandler接口,伪代码如下:

以下为引用:

void HttpApplication.ProcessRequest(System.Web.HttpContext context)

{

throw new HttpException(HttpRuntime.FormatResourceString("Sync_not_supported"));

}

bool HttpApplication.get_IsReusable()

{

return true;

}

而在HttpApplication.IHttpAsyncHandler.BeginProcessRequest函数中,将完成非常复杂的异步调用后台处理操作,这儿就不多罗嗦了,等有机会写篇文章专门讨论一下ASP.NET中的异步操作再说。而其最终调用还是使用System.Web.UI.PageParser对需要处理的Web页面进行解析和编译。

最后我们看看对用户自定义控件文件的处理。

Page类的LoadControl函数实际上是在抽象类TemplateControl中实现的,伪代码如下:

以下为引用:

public Control LoadControl(string virtualPath)

{

virtualPath = UrlPath.Combine(base.TemplateSourceDirectory, virtualPath);

Type type = UserControlParser.GetCompiledUserControlType(virtualPath, null, base.Context);

return this.LoadControl(type1);

}

实际的用户自定义控件预编译操作还是在UserControlParser类中完成的。

至此,在这一节中我们已经大致了解了ASP.NET自动预编译的实现原理,以及在什么时候对页面文件进行预编译。下一节我们将详细分析ApplicationFileParser、PageParser和UserControlParser,了解ASP.NET是如何对页面文件进行预编译的。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有