文章内容

从上章文章都知道,asp.net是运行在HttpRuntime里的,但是从CLR如何进入HttpRuntime的,可能大家都不太清晰。本章节就是通过深入分析.Net4的源码来展示其中的重要步骤。请先看下图:

首先,CLR在初始化加载的时候,会加载一个非常重要的类AppManagerAppDomainFactory,这个类是做什么用的呢?首先这个类继承了IAppManagerAppDomainFactory接口,而这个接口是是有个可供COM调用的Create方法,代码如下:

 [ComImport, Guid("02998279-7175-4d59-aa5a-fb8e44d4ca9d"), System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
public interface IAppManagerAppDomainFactory {
#if !FEATURE_PAL // FEATURE_PAL does not enable COM
[return: MarshalAs(UnmanagedType.Interface)]
#else // !FEATURE_PAL
Object Create(String appId, String appPath);
#endif // !FEATURE_PAL Object Create([In, MarshalAs(UnmanagedType.BStr)] String appId,
[In, MarshalAs(UnmanagedType.BStr)] String appPath); void Stop();
}

我们来细看一下这个AppManagerAppDomainFactory是如何实现这个接口的,首先该类在默认的构造函数里,获取了一个ApplicationManager的实例用于在Create方法里使用。代码如下:

[SecurityPermission(SecurityAction.Demand, Unrestricted=true)]

public AppManagerAppDomainFactory() {

    _appManager = ApplicationManager.GetApplicationManager();

    _appManager.Open();

} 

回到实现接口的Create方法,我们来看最重要的3行代码:

ISAPIApplicationHost appHost = new ISAPIApplicationHost(appId, appPath, false /*validatePhysicalPath*/);

ISAPIRuntime isapiRuntime = (ISAPIRuntime)_appManager.CreateObjectInternal(appId, typeof(ISAPIRuntime), appHost,

        false /*failIfExists*/, null /*hostingParameters*/);

isapiRuntime.StartProcessing();

代码的主要作用,就是通过ApplicationManager的CreateObjectInternal一系列操作,最终获取ISAPIRuntime的实例,然后让非托管代码调用。所以说CreateObjectInternal方法在这里发挥了至关重要的功能:创建AppDomain,创建HostingEnvironment等一系列操作。

首先来看看AppManagerAppDomainFactory的构造函数,其里面调用的ApplicationManager. GetApplicationManager()方法是一个单例的实现,代码如下:

public static ApplicationManager GetApplicationManager() {

    if (_theAppManager == null) {

        lock (_applicationManagerStaticLock) {

            if (_theAppManager == null) {

                if (HostingEnvironment.IsHosted)

                    _theAppManager = HostingEnvironment.GetApplicationManager();

                if (_theAppManager == null)

                    _theAppManager = new ApplicationManager();

            }

        }

    }

    return _theAppManager;

}

从代码看,大家可能有点疑惑,为什么HostingEnvironment属性IsHosted为true的时候会调用它的静态方法GetApplicationManager()来获取ApplicationManager的实例,这是因为ApplicationManager在后续的步骤创建HostingEnvironment对象并初始化的时候,将this自动传递给了HostingEnvironment对象实例(稍后在细说这个事情)。

回头再来看ApplicationManager实例的CreateObjectInternal方法,部分代码如下:

// get hosting environment

HostingEnvironment env = GetAppDomainWithHostingEnvironment(appId, appHost, hostingParameters);

// create the managed object in the worker app domain

// When marshaling Type, the AppDomain must have FileIoPermission to the assembly, which is not

// always the case, so we marshal the assembly qualified name instead

ObjectHandle h = env.CreateWellKnownObjectInstance(type.AssemblyQualifiedName, failIfExists);

return (h != null) ? h.Unwrap() as IRegisteredObject : null;

通过代码我们可以看到,首先要先得到HostingEnvironment的实例,然后通过该实例的CreateWellKnownObjectInstance方法返回上述Create方法需要的ISAPIRuntime的实例。那我们应该能想到GetAppDomainWithHostingEnvironment有2个作用,其一是先要获取AppDomain,其二是获取HostingEnvironment实例,来看看代码是否如我们猜想的结果,先来看代码:

private HostingEnvironment GetAppDomainWithHostingEnvironment(String appId, IApplicationHost appHost, HostingEnvironmentParameters hostingParameters) {

    LockableAppDomainContext ac = GetLockableAppDomainContext (appId);

    lock (ac) {

        HostingEnvironment env = ac.HostEnv;

        if (env != null) {

            try {

                env.IsUnloaded();

            } catch(AppDomainUnloadedException) {

                env = null;

            }

        }

        if (env == null) {

            env = CreateAppDomainWithHostingEnvironmentAndReportErrors(appId, appHost, hostingParameters);

            ac.HostEnv = env;

            Interlocked.Increment(ref _accessibleHostingEnvCount);
} return env; } }

代码告诉我们,首先会检查是否会有已经存在的AddDomain以及相应的HostingEnvironment实例,如果有返回,没有就会创建一个新的。通过辗转调用,最终来到一个私有方法CreateAppDomainWithHostingEnvironment,在这个300行的私有方法里,有我们所迷惑已久的东西。

首先会有关于信任级别的代码,比如是运行在FullTrust上还是MiddleTrust上,这里会有相应的处理代码,由于我们这次代码分析的重点不在这里,所以具体代码就不细说了,来看看我们需要知道的代码段:

//  Create the app domain

 AppDomain appDomain = null;

//  此处省略很多代码

if (isLegacyCas) {

    appDomain = AppDomain.CreateDomain(domainId,

#if FEATURE_PAL // FEATURE_PAL: hack to avoid non-supported hosting features

                                        null,

#else // FEATURE_PAL

GetDefaultDomainIdentity(),

#endif // FEATURE_PAL

setup);

}

else {

    appDomain = AppDomain.CreateDomain(domainId,
#if FEATURE_PAL // FEATURE_PAL: hack to avoid non-supported hosting features null, #else // FEATURE_PAL GetDefaultDomainIdentity(), #endif // FEATURE_PAL setup, permissionSet,
fullTrustAssemblies /* fully trusted assemblies list: null means only trust GAC assemblies */); }

通过代码可以看出,这就是传说中创建AppDomain的地方,后续所有的东西比如HttpRuntime, HttpContext都是依托于这个AppDomain,这就是为什么HttpContext为什么不能在多站点共享,而能安全存在于AppDomain的原因。

继续往下看,在创建AppDomain的代码之后有几行这样的代码:

Type hostType = typeof(HostingEnvironment);

String module = hostType.Module.Assembly.FullName;

String typeName = hostType.FullName;

ObjectHandle h = null;

//  此处省略很多代码

h = Activator.CreateInstance(appDomain, module, typeName);

//  此处省略很多代码

HostingEnvironment env = (h != null) ? h.Unwrap() as HostingEnvironment : null;

//  此处省略很多代码

if (appDomainStartupConfigurationException == null) {

    env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel);

 }

 else {

    env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel, appDomainStartupConfigurationException);

}

return env;

这就是创建HostingEnvironment实例的地方,创建实例以后,紧接着会调用Initialize方法来进行初始化,然后返回对象实例(注意该方法的第一个参数哦,是this,也就是ApplicationManager实例自身,就解释了上面我所说的那个为什么能通过HostingEnvironment的静态方法GetApplicationManager()来获取ApplicationManager实例了)。

通过这些代码,我们就可以简单的知道了,如何获取ISAPIRuntime实例,从而为进入HttpRuntime做准备了。但是,我依然好奇HostingEnvironment的Initialize初始化方法到底都做了什么,好吧,我们来看看。

OK,瞄到了一行重要的代码:

// initiaze HTTP-independent features

HttpRuntime.InitializeHostingFeatures(hostingFlags, policyLevel, appDomainCreationException);

该代码进入HttpRuntime的静态方法,接着调用HostingInt方法进行一些初始化工作,其中有一行代码也是我们需要知道的,如下:

// Initialize the build manager

BuildManager.InitializeBuildManager();

该BuildManager的InitializeBuildManager方法,会调用自己的Initialize方法进行初始化另外一些工作,其中包括编译App_Code目录下所有的.NET源代码。由上面的一系列介绍我们知道,在一个AppDomain内,只有一个HostringEnvironment,所以该这个BuildManager的Initialize也就只执行一次,从而保证了编译不出问题(源码的注释也是这么说的哦)。

另外HostingInit方法里在初始化失败的时候,在catch里有一行非常特殊的代码:

_hostingInitFailed = true;

这是说在创建HostingEnvironment失败的时候,会给HttpRuntime的HostingInitFailed赋值为True。后面的章节所讨论的PreApplicationStartMethodAttribute的概念和WebActivator的入口都和这个值有关系,现在先不做讨论,后面章节细再说。

好了,回到AppManagerAppDomainFactory的Create方法,在得到ISAPIRuntime的实例,并且运行StartProcessing方法以后,会返回一个ObjectHandle对象给非托管代码,其中包括了ISAPIRuntime的实例,代码如下:

return new ObjectHandle(isapiRuntime);

非托管代码接受ObjectHandle对象以后,要干什么呢?我们且看下篇文章的继续分析。

同步与推荐

本文已同步至目录索引:MVC之前的那点事儿系列

MVC之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

MVC之前的那点事儿系列(2):HttpRuntime详解分析(上)的更多相关文章

  1. MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)

    文章内容 话说,经过各种各样复杂的我们不知道的内部处理,非托管代码正式开始调用ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime继承了IISPAIRuntime接口 ...

  2. MVC之前的那点事儿系列

    MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园 ...

  3. MVC之前的那点事儿 ---- 系列文章

    MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园 ...

  4. MVC之前的那点事儿系列(10):MVC为什么不再需要注册通配符(*.*)了?

    文章内容 很多教程里都提到了,在部署MVC程序的时候要配置通配符映射(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前确实应该这么多,但是.NET4.0之后已经不要再费事了 ...

  5. MVC之前的那点事儿系列(8):UrlRouting的理解

    文章内容 根据对Http Runtime和Http Pipeline的分析,我们知道一个ASP.NET应用程序可以有多个HttpModuel,但是只能有一个HttpHandler,并且通过这个Http ...

  6. MVC之前的那点事儿系列(6):动态注册HttpModule

    文章内容 通过前面的章节,我们知道HttpApplication在初始化的时候会初始化所有配置文件里注册的HttpModules,那么有一个疑问,能否初始化之前动态加载HttpModule,而不是只从 ...

  7. MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的?

    文章内容 上个章节我们讲到了,可以在HttpModules初始化之前动态添加Route的方式来自定义自己的HttpHandler,最终接管请求的,那MVC是这么实现的么?本章节我们就来分析一下相关的M ...

  8. MVC之前的那点事儿系列(7):WebActivator的实现原理详解

    文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在Http ...

  9. MVC之前的那点事儿系列(5):Http Pipeline详细分析(下)

    文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class ...

随机推荐

  1. 继电器是如何成为CPU的(2)

    继电器是如何成为CPU的(2) ——<穿越计算机的迷雾>整理和总结 上一篇已经从电池.开关.灯泡和继电器开始,画出了设计CPU所需的基本器件.这些器件将成为设计CPU的砖瓦木料.这一篇就用 ...

  2. 我心中的核心组件(可插拔的AOP)~调度组件quartz.net续~任务管理器的开发

    回到目录 对于任务调度来说,越来越多的团队选择了quartz,它在java和.net环境下表现都十分优秀,配置简单,功能强大,时间表达式配置灵活,但在使用时,还是感觉缺点什么,怎么说,你在服务器上安装 ...

  3. [SpringMVC]SpringMVC学习笔记一: springmvc原理及实例解析.

    前言:今天来回顾下SpringMVC的开发原理, 使用图文并茂的方式 来解析其中的内幕, 我相信懂了其中的运行机制后, 对于面试中SpringMVC大家都可以说so easy了. 一, 图示法 第二张 ...

  4. unity生成的WP8.1工程的Title本地化实现

    不同于WP本身工程的实现,unity的工程在package.appxmanifest文件中已经定义了 <Resources> <Resource Language="x-g ...

  5. hibernate(五) hibernate一对一关系映射详解

    序言 之前讲解了一对多(单向.双向).多对多(双向),今天就讲解一下最后一个关系,一对一. 心情不错.状态也挺好的,赶紧写一篇博文造福一下大家把. --WH 一.一对一关系的概述 一对一关系看起来简单 ...

  6. javascript中数组的22种方法

    × 目录 [1]对象继承 [2]数组转换 [3]栈和队列[4]数组排序[5]数组拼接[6]创建数组[7]数组删改[8]数组位置[9]数组归并[10]数组迭代[11]总结 前面的话 数组总共有22种方法 ...

  7. 信息加密之非对称加密算法RSA

    前面为大家已经总结了,基于密钥交换的DH算法,现在就为大家再介绍一种基于因子分解的RSA算法,这种加密算法有两种实现形式:1.公钥加密,私钥解密:2.私钥加密,公钥解密.下面就为大家分析一下实现代码, ...

  8. 查看abp框架异常信息

    abp框架中经常出现{"message":"An error has occurred."}的异常,并且也进入不到方法中,如果想查看详细信息,可以采用下面方法 ...

  9. NodeJs连接Oracle数据库

    nodejs连接oracle数据库,各个平台的官方详情文档:https://github.com/oracle/node-oracledb/blob/master/INSTALL.md 我的nodej ...

  10. 构建自己的PHP框架--抽象框架的内容

    上一篇博客中,我们搭建了一个最简单的框架,从单一入口的public/index.php进入,解析出相应的Controller和Action,去执行,渲染出相应的页面或者输出相应的数据. 但是我们可以看 ...