[MVC]Oxite分析之初始化(更新版)

转载:http://www.cnblogs.com/alby/archive/2009/11/20/oxite-initialize-new.html

change set:46759
download:http://oxite.codeplex.com/SourceControl/ListDownloadableCommits.aspx
Web应用程序的初始化我觉得应该分两类,一类是系统级的初始化,另一类是应用程序级的初始化。两者也有交叉的部分,如将会谈到的Application_StartApplication_End ,正是利用其在系统级的特殊性来完成应用程序级的初始化工作。关于系统级的初始化,MSDN上有简要的描述:《ASP.NET应用程序生命周期概述》http://msdn.microsoft.com/zh-cn/library/ms178473(VS.80).aspx 。本篇主要分析Oxite在配置级的初始化工作。
一、对ASP.NET应用程序生命周期的理解

摘要《ASP.NET 应用程序生命周期概述》中的几段话:
A:

第一次在应用程序中请求 ASP.NET 页或进程时,将创建 HttpApplication 的一个新实例。不过,为了尽可能提高性能,可对多个请求重复使用 HttpApplication 实例。
理解:也就是说,在应用程序域中将实例化多个HttpApplication对象。每一次请求都将为该请求分配一个HttpApplication对象。而多个请求可能会重复使用同一个HttpApplication对象。但不会存在并发的情况。

B:

Application_StartApplication_End 方法是不表示 HttpApplication 事件的特殊方法。在应用程序域的生命周期期间,ASP.NET 仅调用这些方法一次,而不是对每个 HttpApplication 实例都调用一次。

请求 ASP.NET 应用程序中第一个资源(如页)时调用。在应用程序的生命周期期间仅调用一次 Application_Start 方法。可以使用此方法执行启动任务,如将数据加载到缓存中以及初始化静态值。

在应用程序启动期间应仅设置静态数据。由于实例数据仅可由创建的 HttpApplication 类的第一个实例使用,所以请勿设置任何实例数据。
理解:Application_Start 是在应用程序发生第一次请求,创建第一个HttpApplication对象时发生。(在创建了所有模块之后,对 HttpApplication 类的每个实例都调用一次Application_Init方法)
C:

在应用程序的生命周期期间,应用程序会引发可处理的事件并调用可重写的特定方法。若要处理应用程序事件或方法,可以在应用程序根目录中创建一个名为 Global.asax 的文件。

如果创建了 Global.asax 文件,ASP.NET 会将其编译为从 HttpApplication 类派生的类,然后使用该派生类表示应用程序。
理解:我们创建Global.asax 目的是“替换”原来的HttpApplication 类,而为每一次请求分配的将是Global.asax类的实例。在Global.asax中,我们可以捕获相关事件或重写如Application_StartApplication_End 之类的特殊方法。
二、OixteSite项目中Global.asax文件中的Application_StartApplication_End方法

查看OxiteSite项目的Global.asax文件,发现其实现代码在Oxite项目的OxiteApplication类中。在Application_Start方法中对OxiteSite进行了初始化工作。
Application_Start方法具体做了哪些事呢?
一是设置依赖注入容器并将之存入应用程序状态中(HttpApplicationState)
二是根据配置加载模块

protected void Application_Start()
{
Application["container"] = setupContainer();
Application["bootStrappersLoaded"] = false;
load();
}

Oxite中使用的依赖注入容器为Unity(详见Enterprise Library 4.0以上版本)。
setupContainer方法设置注入容器并返回一个UnityContainer对象,保存为Application["container"]。稍后将仔细分析该方法。
Application["bootStrappersLoaded"]用于标认初始化是否完成。
load()方法调用静态方法Load(HttpContextBase context)。作用是根据配置加载指定模块(Module)。

 

Application_End方法调用unload()方法。在应用程序结束时,完成某些模块的清理工作。
三、setupContainer方法
预备知识:Unity (IOC/DI)、自定义web.config配置结点
通过setupContainer方法的方法名不难看出是用于设置依赖注入容器的。
在setupContainer方法中,首先定义一个IUnityContainer变量parentContainer:

IUnityContainer parentContainer = new UnityContainer();

首先,将几个基础对象注册为单例:

parentContainer
.RegisterInstance((OxiteConfigurationSection)ConfigurationManager.GetSection("oxite"))
.RegisterInstance(new AppSettingsHelper(ConfigurationManager.AppSettings))
.RegisterInstance(RouteTable.Routes)
.RegisterInstance(System.Web.Mvc.ModelBinders.Binders)
.RegisterInstance(ViewEngines.Engines)
.RegisterInstance(HostingEnvironment.VirtualPathProvider);

OxiteConfigurationSection类,自定义配置节点。其定义位与Oxite.Configuration命名空间下。是Oxite中实现模块化的配置文件。配置的结点单独放在OxiteSite项目下的oxite.config文件中。
AppSettingsHelper 类对ConfigurationManager.AppSettings 进行包装, 提供几个读取方法GetInt32、GetString等,用于读取web.config文件中的appSettings节点下的值。其实完全可以将这几 个读取方法放入NameValueCollectionExtensions类(Oxite.Extensions命名空间下)。不过后来想了想,这里用 AppSettingsHelper命名其实也可以明确该类的目的就是为了操作AppSettings结点。
RouteTable.Routes静态属性返回一个RouteCollection静态对象。RouteCollection类在System.Web.Routing程序集中定义。用于保存URL路由设置。 注入容器的目的是为了单元测试。
ModelBinders.Binders静态属性返回一个ModelBinderDictionary静态对象。用于处理数据绑定相关操作(获取表单、查询数据并转换;生成URL路径)。
ViewEngines.Engines静态属性返回一个ViewEngineCollection静态对象。用于视图引擎方面。
HostingEnvironment.VirtualPathProvider静态属性返回一个VirtualPathProvider静态对象。个人猜测可能会用在自定义ViewEngine中,不过目前Oxite版本中好像还没地方用,注释掉也没地方报错。
RouteTable.Routes、ModelBinders.Binders和ViewEngines.Engines是ASP.NET MVC底层比较基础性的属性或对象。值得花时间单独去学习。
接着,将web.config中的connectionStrings和自定义节点“oxite”(oxite.config文件)下的connectionStrings注册为单件。

foreach (ConnectionStringSettings connectionString in ConfigurationManager.ConnectionStrings)
parentContainer.RegisterInstance(connectionString.Name, connectionString.ConnectionString);
foreach (ConnectionStringSettings connectionString in parentContainer.Resolve<OxiteConfigurationSection>().ConnectionStrings)
parentContainer.RegisterInstance(connectionString.Name, connectionString.ConnectionString);

疑问1:在运行时,站点不重启的情况下,如果oxite结点下的connectionStrings改变后,要怎样才能更新到依赖注入容器?这里的 处理似乎欠妥。 后来我到Oxite.codeplex.com去问了,Oxite项目组的ErikPorter说目前得重启站点才行。希望他们尽快修正,不然所谓的模块 热插拔会大打折扣。
接着看setupContainer方法:

parentContainer
.RegisterInstance<IBootStrapperTask>("LoadModules", new LoadModules(parentContainer))
.RegisterInstance<IBootStrapperTask>("LoadBackgroundServices", new LoadBackgroundServices(parentContainer));

LoadModules类和LoadBackgroundServices类位于Oxite.BootStrapperTasks命名空间。从类命 名上看,一个是和加载模块相关的,另一个是和加载后台服务相关的,具体是什么得往后细看了。两者都实现了Oxite.Infrastructure命名空 间下的IBootStrapperTask接口。IBootStrapperTask就两个方法:Execute和Clearup。 IBootStrapperTask我觉得可以直译为引导程序接口,其实例可以称为引导程序。
在这里我们只需要知道,Modules实例和LoadBackgroundSercies实例分别注册为单件。
接着看setupContainer方法中将一些类型也注册到依赖注入容器中,除了几个自定义生命周期的类型外,其他的都只是简单的映射,这里就不多说了。
在 setupContainer结束返回值之前,会将web.config文件中Unity配置结点注册入容器中。如果配置结点和我们硬编码中的设置重复, 则会覆盖硬编码中的配置。这一特性非常有用,它允许我们在使用程序的默认配置的同时,又提供了一个接口以供我们替换。详情可以查看相关Unity方面的资 料。
四、静态方法Load

Application_Start中调用私有load方法,load将请求上下包装成HttpContextBase对象作为参数,调用静态方法Load。
Load方法虽为静态,但其接受一个HttpContextBase型参数,从而保证了其线程安全。

在静态方法Load中将会从依赖注入容器中将实现了IBootStrapperTask接口的类的实例(引导程序)从依赖注入容器中提取出来,并执行其Execute方法,具体过程如下分析。
通过对setupContainer的分析,我们知道在Load方法中,tasks集合中会有两个对象:LoadModules类和LoadBackgroundServices类的实例。
另外,Application["bootStrapperState"]可能是预留下来(也有可能是遗留下来的),暂时没有明确其具体的目的(可以自己想想)。
如果Application["bootStrappersLoaded"]为true,表示曾经加载过,则先逐个进行清理工作(Cleanup)。
然后在逐个执行其引导程序的Execute方法。完成后将Application["bootStrappersLoaded"]设为true表示加载完毕。

public static void Load(HttpContextBase context)
{
IEnumerable<IBootStrapperTask> tasks = ((IUnityContainer)context.Application["container"]).ResolveAll<IBootStrapperTask>();
bool bootStrappersLoaded = (bool)context.Application["bootStrappersLoaded"];
IDictionary<string, object> state = (IDictionary<string, object>)context.Application["bootStrapperState"];
if (state == null)
{
context.Application["bootStrapperState"] = state = new Dictionary<string, object>();
}
// If the tasks have been executed previously, call Cleanup on them to rollback any changes
// they caused.
if (bootStrappersLoaded)
{
foreach (IBootStrapperTask task in tasks)
{
task.Cleanup(state);
}
}
foreach (IBootStrapperTask task in tasks)
{
task.Execute(state);
}
context.Application["bootStrappersLoaded"] = true;
}

 
Oxite的初始化有很大一部分在IBootStrapperTask接口的两个实现类中,通过Execute方法来完成的。
五、LoadModules类
打 一个不十分恰当的比喻,Oxite就象一个Windows操作系统,在系统启动时会执行一些引导程序。有一个引导程序根据注册表的配置,运行一些应用程 序,比如杀毒软件、防火墙等等;另一个引导程序根据“启动”菜单中保存的链接,运行另外一些应用程序,如QQ。(Windows引导程序可能不用两个来完 成这两步操作)
在这里,LoadModules可以看成是通过注册表获取开机启动程序并启动的引导程序;LoadBackgroundServices可以看成是通过“启动”菜单中保存的链接来启动程序的引导程序。
Windows平台下的程序能够实现开机启动,前提条件它得是Windows程序。
我们换成Oxite模块的角度上考虑,他们都应该实现了某个或某些接口。的确,Oxite中的模块都实现了IOxiteModule接口。
而Oxite中模块,可以看成需要开机运行的程序。

Oxite可以看成是由一个个模块(Module)组成的,而模块指实现了IOxiteModule接口的类。
Oxite001

(图1:从模块的角度分析Oxite解决方案结构)
AspNetCache、Core、Membership、Oxite.Blogs、Oxite.CMS等都称之为模块。
LoadModules类实现于接口IBootStrapperTask,其目的正是用于加载模块(Module)。

public void Execute(IDictionary<string, object> state)
{
OxiteConfigurationSection config = container.Resolve<OxiteConfigurationSection>();
IModulesLoaded modulesLoaded = this.container.Resolve<IModulesLoaded>();
RouteCollection routes = this.container.Resolve<RouteCollection>();
IFilterRegistry filterRegistry = this.container.Resolve<FilterRegistry>();
ModelBinderDictionary modelBinders = this.container.Resolve<ModelBinderDictionary>();
filterRegistry.Clear();
modelBinders.Clear();
//todo: (nheskew) get plugin routes registered on load in the right order instead of just clearing the routes before module init
routes.Clear();
foreach (OxiteModuleConfigurationElement module in config.Modules)
{
IOxiteModule moduleInstance = modulesLoaded.Load(config, module);
if (moduleInstance != null)
{
moduleInstance.RegisterWithContainer();
moduleInstance.Initialize();
moduleInstance.RegisterFilters(filterRegistry);
moduleInstance.RegisterModelBinders(modelBinders);
this.container.RegisterInstance(modulesLoaded);
}
}
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LoadFromModules(modulesLoaded);
routes.LoadCatchAllFromModules(modulesLoaded);
container.RegisterInstance(filterRegistry);
}

首先获取oxite.config配置文件。Oxite配置结点在setupContainer方法的分析中提到过,他是Oxite模块化的配置。它相当与注册表,用于保存需要开机启动的程序列表。
OxiteConfigurationSection config = container.Resolve<OxiteConfigurationSection>();
接着获取IModulesLoaded对象(在setupContainer方法中,IModulesLoaded映射为ModulesLoaded类)。
IModulesLoaded modulesLoaded = this.container.Resolve<IModulesLoaded>();
ModulesLoaded的作用是保存“已经加载的模块”。之后会注册为单件。

接着获取RouteCollection对象,即RouteTable.Routes。在setupContainer方法被注册为单件。
RouteCollection routes = this.container.Resolve<RouteCollection>();
接着获取IFilterRegistry实例。FilterRegistry是和Filter相关的(ActionFilter、ResultFilter等),可能需要单独的篇幅来分析。
IFilterRegistry filterRegistry = this.container.Resolve<FilterRegistry>();
这里只是简单的实例化,因为IFilterRegistry 并没有在依赖注如容器中注册过。不过在最后,会将IFilterRegistry实例注册为单件。
接着获取ModelBinderDictionary实例。即System.Web.Mvc.ModelBinders.Binders,在setupContainer方法被注册为单件。
ModelBinderDictionary modelBinders = this.container.Resolve<ModelBinderDictionary>();
本质上FilterRegistry、ModelBinderDictionary、RouteCollection都是集合类。接下来将filterRegistry、modelBinders、routes清空。

接下来是一个foreach循环。ModulesLoaded类实例modulesLoaded的Load方法将Module进行实例化,返回IOxiteModule对象。

foreach (OxiteModuleConfigurationElement module in config.Modules)
{
IOxiteModule moduleInstance = modulesLoaded.Load(config, module);
//...
}

如果正常返回IOxiteModule对象,就调用如下四个方法:
      RegisterWithContainer
      Initialize
      RegisterFilters
      RegisterModelBinders

      接着:

      this.container.RegisterInstance(modulesLoaded);

疑问2:上面这句写在循环里,为什么不写在循环外面? 
 

跳出循环后,对路由规则进行设置:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LoadFromModules(modulesLoaded);
routes.LoadCatchAllFromModules(modulesLoaded);

LoadFromModules和LoadCatchAllFromModules方法是扩展方法。
方法内部会遍历 ModulesLoaded对象中保存的IOxiteModuel对象并分别调用对象的RegisterRoutes和 RegisterCatchAllRoutes方法。详情请看Oxite.Extensions.RouteCollectionExtensions 类。
疑问3,这两行代码为什么没有像调用ModulesLoaded类的RegisterWithContainer,Initialize,RegisterFilters,RegisterModelBinders这四个方法那样调用。
我觉得完全可以这样:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
foreach (OxiteModuleConfigurationElement module in config.Modules)
{
IOxiteModule moduleInstance = modulesLoaded.Load(config, module);
if (moduleInstance != null)
{
moduleInstance.RegisterWithContainer();
moduleInstance.Initialize();
moduleInstance.RegisterFilters(filterRegistry);
moduleInstance.RegisterModelBinders(modelBinders);
moduleInstance.RegisterRoutes(routes);
moduleInstance.RegisterCatchAllRoutes(routes);
}
}
this.container.RegisterInstance(modulesLoaded);
this.container.RegisterInstance(filterRegistry);

(疑问3的解释:大家都知道,Routing规则设置的顺序非常重要,RegisterRoutes方法中先对Modules进行倒序再注册,目的 是使排在后面的Module的Routing先注册。我觉得这也带来一点麻烦,也许你会覆盖(说成隐藏比较合适)了原本不想覆盖的,比如新加的模块有可能 会覆盖系统模块的Routing规则。也说明了两个方面问题,一方面模块之间也有依赖关系,比如其他模块对系统模块的依赖,当然我们要尽量普通模块之间的 依赖;另一方面模块在oxite.config中的顺序也很重要。)
此文为《Oxite分析之初始化》的更新版,老版有些错误的认识,也留着被拍吧。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏