MVC之前的那点事儿系列(2):HttpRuntime详解分析(上)
文章内容
从上章文章都知道,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详解分析(上)的更多相关文章
- MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)
文章内容 话说,经过各种各样复杂的我们不知道的内部处理,非托管代码正式开始调用ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime继承了IISPAIRuntime接口 ...
- MVC之前的那点事儿系列
MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园 ...
- MVC之前的那点事儿 ---- 系列文章
MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园 ...
- MVC之前的那点事儿系列(10):MVC为什么不再需要注册通配符(*.*)了?
文章内容 很多教程里都提到了,在部署MVC程序的时候要配置通配符映射(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前确实应该这么多,但是.NET4.0之后已经不要再费事了 ...
- MVC之前的那点事儿系列(8):UrlRouting的理解
文章内容 根据对Http Runtime和Http Pipeline的分析,我们知道一个ASP.NET应用程序可以有多个HttpModuel,但是只能有一个HttpHandler,并且通过这个Http ...
- MVC之前的那点事儿系列(6):动态注册HttpModule
文章内容 通过前面的章节,我们知道HttpApplication在初始化的时候会初始化所有配置文件里注册的HttpModules,那么有一个疑问,能否初始化之前动态加载HttpModule,而不是只从 ...
- MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的?
文章内容 上个章节我们讲到了,可以在HttpModules初始化之前动态添加Route的方式来自定义自己的HttpHandler,最终接管请求的,那MVC是这么实现的么?本章节我们就来分析一下相关的M ...
- MVC之前的那点事儿系列(7):WebActivator的实现原理详解
文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在Http ...
- MVC之前的那点事儿系列(5):Http Pipeline详细分析(下)
文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class ...
随机推荐
- NanoProfiler - 适合生产环境的性能监控类库 之 实践ELK篇
上期回顾 上一期:NanoProfiler - 适合生产环境的性能监控类库 之 大数据篇 上次介绍了NanoProfiler的大数据分析理念,一晃已经时隔一年多了,真是罪过! 有朋友问到何时开源的问题 ...
- jetty
相关的文章太多了,我只按照自己的意思做简单总结. 参见: http://www.cnblogs.com/duanxz/p/3154982.html http://www.cnblogs.com/win ...
- Java-数组练习2
2.找出如下数组中最大的元素和最小的元素,a[][]={{3,2,6},{6,8,2,10},{5},{12,3,23}} int[][] i={{3,2,6},{6,8,2,10},{5},{12, ...
- 用Chrome插件对自动化测试TestWriter进行录制
1.打开Chrome浏览器,在浏览地址中输入: chrome://extensions/,并勾选开发者模式.如图: 2.点击按钮[加载已解压的扩展程序-].如图: 3.选择Testwriter客户端下 ...
- eclipse提交项目到github
1.在https://github.com new repository 2.在eclipse中new project 比如:Test项目 3.右击"Test"->Te ...
- 动手搭个wordpress
看到很多人都是自己搭建博客服务器,然后一切都在自己的掌控之下,这样就不存在什么迁移,数据安全之类的问题,当然需要自己搞个空间了,不过现在都便宜的不行,$15/year,也是醉了.我不怎么写博客,但是个 ...
- C# string.format、string.connect和+=运算 效率计算
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Stri ...
- 初了解JS设计模式,学习笔记
什么是设计模式. 回答这个问题,往往我们得先知道我们为什么需要设计模式,正是因为有需求才会有设计模式,难道不是吗? 我们为什么需要设计模式. 如果没有按照设计模式去写,你的代码很可能是乱无肆忌写的,也 ...
- 引入CSS
前面的话 Web早期,HTML是一种很有限的语言,这种语言不关心外观,它只是一种简洁的小型标记机制.随着Mosaic网页浏览器的出现,网站开始到处涌现.对于页面改变外观的需求增加,于是增加了类似< ...
- js返回上一页并刷新的多种实现方法
<a href="javascript:history.go(-1)">返回上一页</a> <a href="javascript:loca ...