欢迎光临
我们一直在努力

.NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析-.NET教程,Asp.Net开发

建站超值云服务器,限时71元/月

.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是如何对页面文件进行预编译的。

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » .NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析-.NET教程,Asp.Net开发
分享到: 更多 (0)