zoukankan      html  css  js  c++  java
  • .NET 1.1中预编译ASP.NET页面实现原理浅析

     

    Microsoft在发布ASP.NET时的一大功能特性是,与ASPPHP等脚本语言不

    同,ASP.NET实际上是一种编译型的快速网页开发环境。这使得ASP.NET在具有开

    发和修改的简便性的同时,不会负担效率方面的损失。实现上ASP.NETJSP的思

    路类似,引擎在第一次使用一个页面之前,会将之编译成一个类,自动生成Assembly

    并载入执行。

    而通过《在WinForm程序中嵌入ASP.NET》一文中我们可以了解到,ASP.

    NET引擎实际上是可以无需通过IISWeb服务器调用而被使用的,这就使得手工

    预编译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”);

    }

    技术专题|CoverStory

     

    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应用程序实例完成实际的页面请求工作。

    伪代码如下:

    以下为引用:

    技术专题|CoverStory

     

    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);

    }

    技术专题|CoverStory

     

    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);

    技术专题|CoverStory

     

    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);

    }

    技术专题|CoverStory

     

    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);

    技术专题|CoverStory

     

    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;

    }

    技术专题|CoverStory

     

    else

    {

    this._theApplicationType = typeof(HttpApplication);

    this._state = new HttpApplicationState();

    }

    this.ReflectOnApplicationType();

    }

    分析到这里我们可以发现,内部类型System.Web.UI.ApplicationFileParserGetCompiledApplicationType

    函数是实际上进行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);

    }

    技术专题|CoverStory

     

    ...

    }

    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自动预编译的实现原理,

    以及在什么时候对页面文件进行预编译。

    支持TerryLee的创业产品Worktile
    Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
    https://worktile.com
  • 相关阅读:
    file is universal (3 slices) but does not contain a(n) armv7s slice error for static libraries on iOS
    WebImageButton does not change images after being enabled in Javascript
    ajax OPTION
    编程遍历页面上所有TextBox控件并给它赋值为string.Empty?
    获取海洋天气预报
    C#线程系列教程(1):BeginInvoke和EndInvoke方法
    js控制只能输入数字和小数点
    Response.AddHeader(,)
    ManualResetEvent的理解
    Convert.ToInt32、int.Parse(Int32.Parse)、int.TryParse、(int) 区别
  • 原文地址:https://www.cnblogs.com/Terrylee/p/250207.html
Copyright © 2011-2022 走看看