理論的解說一般都是枯燥的,研究一個代碼框架更是如此,似乎除了對大量的源碼加以解釋之外無話可說,但是這又是不可缺少的,否則應用這樣的框架起來總有不放心之感,總有不少的坑要踩。更進一步講,一個好的框架應該給它的使用者足夠的擴展空間(尤其是像MVC這樣的基礎框架),對框架本身沒有很好的把握則對於框架的擴展似乎就只能尋找別人的“成功”經驗了,然而生搬硬套從來不是解決問題的好方法,合理運用他人經驗與創造性的解決方案都離不開對框架的深入理解,從這方面來說代碼分析其實強於抽象的說明,因為代碼的邏輯是確定的,代碼的語言說服力其實強於書面語言(當然是指編寫良好的代碼)。當然對於代碼之外的討論也是非常必要甚至更加重要的,比如探討框架設計的思路和模式,框架應用的相關實踐乃至對框架的擴展等等。此一系列文章都是基於Asp.net Mvc框架的源碼(包括Asp.net的一部分)對框架的基本設計與實現作出分析,另外參考了自己的一些項目以及當前一些優秀的開源項目(比如orchard)來討論框架的應用與擴展。
Asp.net Mvc是當前使用比較多的web框架,也是比較先進的框架,目前.net的大部分源碼都已經開放,這大大方便了我們對Asp.net Mvc的分析,下面就從Http請求進入Mvc框架處理之前的基本流程說起。
由於各IIS版本和工作模式(經典模式、集成模式)的不同,Http請求進入Asp.net的處理通道並不一樣,這里不去細究里面的細節,就從創建應用程序域開始:
AppManagerAppDomainFactory分析
注:AppDomainFactory及AppManagerAppDomainFactory類在System.Web.Hosting中實現
在創建Appdomain時會調用IAppDomainFactory接口,該接口的實現如下:
public sealed class AppDomainFactory : IAppDomainFactory {
private AppManagerAppDomainFactory _realFactory; public AppDomainFactory() { _realFactory = new AppManagerAppDomainFactory(); } public Object Create(String module, String typeName, String appId, String appPath,String strUrlOfAppOrigin, int iZone) { return _realFactory.Create(appId, appPath); } }
該實現會調用AppManagerAppDomainFactory完成實際的創建過程。
public sealed class AppManagerAppDomainFactory : IAppManagerAppDomainFactory {
private ApplicationManager _appManager; public AppManagerAppDomainFactory() { _appManager = ApplicationManager.GetApplicationManager(); _appManager.Open(); } public Object Create(String appId, String appPath) { try { if (appPath[0] == '.') { System.IO.FileInfo file = new System.IO.FileInfo(appPath); appPath = file.FullName; } if (!StringUtil.StringEndsWith(appPath, '\\')) { appPath = appPath + "\\"; } ISAPIRuntime isapiRuntime = (ISAPIRuntime)_appManager.CreateObjectInternal(appId, typeof(ISAPIRuntime), appHost,false, null); isapiRuntime.StartProcessing(); return new ObjectHandle(isapiRuntime); } catch (Exception e) { Debug.Trace("internal", "AppDomainFactory::Create failed with " + e.GetType().FullName + ": " + e.Message + "\r\n" + e.StackTrace); throw; } } }
代碼的主要作用,就是通過ApplicationManager的CreateObjectInternal創建AppDomain,創建HostingEnvironment等,最終獲取ISAPIRuntime的實例,然后讓非托管代碼調用。
ApplicationManager分析
注:ApplicationManager類在System.Web.Hosting中實現
首先看ApplicationManager類中的靜態的創建ApplicationManager對象的方法:
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已經創建則可以直接返回當前HostingEnvironment設置的ApplicationManager,如果沒有則創建新的ApplicationManager實例。
然后看ApplicationManager的CreateObjectInternal方法:
internal IRegisteredObject CreateObjectInternal(String appId, Type type, IApplicationHost appHost, bool failIfExists, HostingEnvironmentParameters hostingParameters) {
if (!typeof(IRegisteredObject).IsAssignableFrom(type)) throw new ArgumentException(SR.GetString(SR.Not_IRegisteredObject, type.FullName), "type"); HostingEnvironment env = GetAppDomainWithHostingEnvironment(appId, appHost, hostingParameters); ObjectHandle h = env.CreateWellKnownObjectInstance(type.AssemblyQualifiedName, failIfExists); return (h != null) ? h.Unwrap() as IRegisteredObject : null; }
首先要先取得HostingEnvironment的實例,然后通過該實例的CreateWellKnownObjectInstance方法返回上述Create方法需要的ISAPIRuntime的實例。先來看如何獲取HostingEnvironment實例的方法GetAppDomainWithHostingEnvironment。
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; } }
首先會檢查字典是否會有已經存在的HostingEnvironment實例,如果有就返回,沒有就會創建一個新的並保存到字典中,查看相關代碼發現最終會調用ApplicationManager的私有方法CreateAppDomainWithHostingEnvironment創建AppDomain和HostingEnvironment。
private HostingEnvironment CreateAppDomainWithHostingEnvironment(String appId, IApplicationHost appHost, HostingEnvironmentParameters hostingParameters)
{
……
appDomain = AppDomain.CreateDomain(domainId,GetDefaultDomainIdentity(),setup); …… Type hostType = typeof(HostingEnvironment); String module = hostType.Module.Assembly.FullName; String typeName = hostType.FullName; ObjectHandle h = null; …… try { h = Activator.CreateInstance(appDomain, module, typeName); } …… HostingEnvironment env = (h != null) ? h.Unwrap() as HostingEnvironment : null; if (env == null) throw new SystemException(SR.GetString(SR.Cannot_create_HostEnv)); IConfigMapPathFactory configMapPathFactory = appHost.GetConfigMapPathFactory(); if (appDomainStartupConfigurationException == null) { env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel); } else { env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel, appDomainStartupConfigurationException); } return env; }
可以看到代碼創建了AppDomain和HostingEnvironment實例,創建HostingEnvironment實例以后,緊接着會調用其Initialize方法來進行初始化,然后返回對象實例。
HostingEnvironment分析
Initialize初始化方法,注意該方法的第一個參數是this,也就是ApplicationManager實例自身,這樣會在HostingEnvironment中設置ApplicationManager,因而初始化之后可以通過HostingEnvironment獲取ApplicationManager 。Initialize方法調用HttpRuntime的靜態方法,進行一些初始化工作(其中會調用BuildManager的InitializeBuildManager方法進行初始化另外一些工作,其中包括編譯App_Code目錄下所有的.NET源代碼)。最后如果HostingEnvironment初始化失敗時會設置hostingInitFailed為true。
internal void Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters, PolicyLevel policyLevel, Exception appDomainCreationException)
{
……
_appManager = appManager; …… HttpRuntime.InitializeHostingFeatures(hostingFlags,policyLevel,appDomainCreationException); …… catch (Exception e) { _hostingInitFailed = true; } }
創建HostingEnvironment后,會調用其方法CreateWellKnownObjectInstance創建ISAPIRuntime。創建好ISAPIRuntime實例后初始化工作就完成了第一個階段,整個過程如下圖所示:
理論的解說一般都是枯燥的,研究一個代碼框架更是如此,似乎除了對大量的源碼加以解釋之外無話可說,但是這又是不可缺少的,否則應用這樣的框架起來總有不放心之感,總有不少的坑要踩。更進一步講,一個好的框架應該給它的使用者足夠的擴展空間(尤其是像MVC這樣的基礎框架),對框架本身沒有很好的把握則對於框架的擴展似乎就只能尋找別人的“成功”經驗了,然而生搬硬套從來不是解決問題的好方法,合理運用他人經驗與創造性的解決方案都離不開對框架的深入理解,從這方面來說代碼分析其實強於抽象的說明,因為代碼的邏輯是確定的,代碼的語言說服力其實強於書面語言(當然是指編寫良好的代碼)。當然對於代碼之外的討論也是非常必要甚至更加重要的,比如探討框架設計的思路和模式,框架應用的相關實踐乃至對框架的擴展等等。此一系列文章都是基於Asp.net Mvc框架的源碼(包括Asp.net的一部分)對框架的基本設計與實現作出分析,另外參考了自己的一些項目以及當前一些優秀的開源項目(比如orchard)來討論框架的應用與擴展。