change set:42353
download :http://oxite.codeplex.com/SourceControl/ListDownloadableCommits.aspx
和一般的web程序一样,Oxite在第一次被访问时将进行初始化操作,即Oxite.OxiteApplication的 Application_Start方法。这些操作笼统来说包括两部分,一是设置依赖注入容器并将之存入应用程序状态中 (HttpApplicationState),二是通过依赖注入容器中的某些配置加载模块(Oxite分析之Module)。
这两部分的分析入口分别为Application_Start方法中的Application["container"] = setupContainer();和load();
一、设置依赖注入容器
注意力转移至setupContainer方法。该方法直接或间接将大批的对象和类型注册入依赖注入容器。
首先,将几个基础对象注册为单例。
1: parentContainer
2: .RegisterInstance((OxiteConfigurationSection)ConfigurationManager.GetSection("oxite"))
3: .RegisterInstance(new AppSettingsHelper(ConfigurationManager.AppSettings))
4: .RegisterInstance(RouteTable.Routes)
5: .RegisterInstance(System.Web.Mvc.ModelBinders.Binders)
6: .RegisterInstance(ViewEngines.Engines)
7: .RegisterInstance(HostingEnvironment.VirtualPathProvider);
OxiteConfigurationSectio类,自定义配置节点。其下目前有三个属 性:connectionStrings,dataProviders,moduls(可能会增加一个settings属性)。可以查看 OxiteSite项目下的oxite.confing文件直观的了解其结构。该配置基本上是针对Module的(Oxite分析之Module)。
Oxite几乎将所有东西都放置在一个个模块中,包括最核心的部分。比如设置自定义CollectionerFactiory、自定义 ControllerActionInvoker、自定义ViewEngines等在Oxite.Moduls.Core模块中进行。而moduls节点 就定义了Oxite系统包括哪些Module;
dataProviders定义了各个需要数据访问的Module的数据访问方式的分类(category属性),而在Module的具体定义中可以这个分类注入不同的Repository以达到各个模块可以单独使用不同的数据访问方式设置不同的数据库。
connectionStrings节点结构和站点配置web.config中的系统connectionStrings节点完全相同,这里之所以又进行 一次定义,是为了提供给dataProviders使用(dataProviders有个defaultConnectionString属性)。当 dataProviders下的元素未定义connectionString时,可以使用dataProviders节点的 defaultConnectionString属性对应的connectionStrings节点的元素的connectionString属性(真拗 口)。
AppSettingsHelper 类对ConfigurationManager.AppSettings 进行包装, 提供几个读取方法GetInt32、GetString等,用于读取web.config文件中的appSettings节点下的值。其实完全可以将这几 个读取方法放入NameValueCollectionExtensions类(Oxite已定义该类)。appSettings节点下,可以定义名称为 Oxite.InstanceName的值来指定Oxite实例,该值指导程序在数据库读取Oxite_Site表中的相应配置。还可以定义名称为 IsEmail、IsUrl等,值为正则表达式的元素。如果appSettings节点中没有定义这些元素,将会使用程序硬编码中的设置。
RouteTable.Routes,RouteCollection对象。System.Web.Routing中对其定义,稍微熟悉一点ASP.NET MVC应该清楚这是做什么的。
另外ModelBinders.Binders(Oxite分析之ModelBinders)、ViewEngines.Engines(Oxite分析之ViewEngines) 可以参看关于ASP.NET MVC方面的资料。
HostingEnvironment.VirtualPathProvider,HostingEnvironment 类的静态属性,其值是VirtualPathProvider类的对象。个人猜测可能会用在自定义ViewEngine中,不过目前Oxite版本中好像 还没地方用。
然后,将web.config中的connectionStrings和自定义节点”oxite”下的connectionStrings注册为单件。
1: foreach (ConnectionStringSettings connectionString in ConfigurationManager.ConnectionStrings)
2: parentContainer.RegisterInstance(connectionString.Name, connectionString.ConnectionString);
3:
4: foreach (ConnectionStringSettings connectionString in parentContainer.Resolve<OxiteConfigurationSection>().ConnectionStrings)
5: parentContainer.RegisterInstance(connectionString.Name, connectionString.ConnectionString);
将数据库连接字符串注册为单件,在构造数据库访问对象(或ORM,Linq to SQL、Entity Framework等)将会使用。
接着,两行咋看不太起眼的代码,实际上是进入Oxite核心的入口:
1: parentContainer
2: .RegisterInstance<IBootStrapperTask>("LoadModules", new LoadModules(parentContainer));
创建LoadModules对象,并注册为单例。LoadModules实现了IBootStrapperTask接口。
1: public interface IBootStrapperTask
2: {
3: void Execute(IDictionary<string, object> state);
4: void Cleanup(IDictionary<string, object> state);
5: }
在OxiteApplication的Application_Start调用私有方法load,load调用静态方法 Load。Load方法中,将从依赖注入容器中提取(ResolveAll)所有实现了IBootStrapperTask接口的类的对象,并调用 Execute方法(详情查看静态方法Load);在Application_End方法中,类似的将间接执行实现了IBootStrapperTask 接口的类的对象的Cleanup方法(详情查看私有方法unload)。当然,目前Oxite版本中只有一个LoadModules对象。
LoadModules类的主要代码:
1: public void Execute(IDictionary<string, object> state)
2: {
3: OxiteConfigurationSection config = container.Resolve<OxiteConfigurationSection>();
4: IModulesLoaded modulesLoaded = this.container.Resolve<IModulesLoaded>();
5: RouteCollection routes = this.container.Resolve<RouteCollection>();
6: IFilterRegistry filterRegistry = this.container.Resolve<FilterRegistry>();
7: ModelBinderDictionary modelBinders = this.container.Resolve<ModelBinderDictionary>();
8: IBackgroundServiceRegistry backgroundServicesRegistry = this.container.Resolve<IBackgroundServiceRegistry>();
9:
10: filterRegistry.Clear();
11:
12: modelBinders.Clear();
13:
14: //todo: (nheskew) get plugin routes registered on load in the right order instead of just clearing the routes before module init
15: routes.Clear();
16:
17: foreach (OxiteModuleConfigurationElement module in config.Modules)
18: {
19: IOxiteModule moduleInstance = modulesLoaded.Load(config, module);
20:
21: if (moduleInstance != null)
22: {
23: moduleInstance.RegisterWithContainer();
24: moduleInstance.Initialize();
25: moduleInstance.RegisterFilters(filterRegistry);
26: moduleInstance.RegisterModelBinders(modelBinders);
27:
28: this.container.RegisterInstance(modulesLoaded);
29:
30: //TODO: (erikpo) Move this into its own BootStrapper and spin up the appropriate executor classes and start them
31: IOxiteBackgroundService backgroundServices = moduleInstance as IOxiteBackgroundService;
32:
33: if (backgroundServices != null)
34: backgroundServices.RegisterBackgroundServices(backgroundServicesRegistry);
35: }
36: }
37:
38: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
39:
40: routes.LoadFromModules(modulesLoaded);
41:
42: routes.LoadCatchAllFromModules(modulesLoaded);
43:
44: container.RegisterInstance(filterRegistry);
45:
46: container.RegisterInstance(backgroundServicesRegistry);
47: }
48:
49: public void Cleanup(IDictionary<string, object> state)
50: {
51: container.Resolve<IModulesLoaded>().UnloadModules();
52:
53: //TODO: (erikpo) Loop through all background services running in the background and stop them
54: }
在这里我们暂时不去关心IBackgroundServiceRegistry、IFilterRegistry和FilterRegistry的定义,不过通过接口名称可以看出是和后台服务和ActionFilter相关的。
下面简单说说Oxite怎么实现Module(Oxite分析之Module)的插拔功能的。
Oxite中的Module都实现了IOxiteModule接口。当做好一个Module后(源码文件.cs或.vb等),直接复制到OxiteSet 的App_Code目录下,或者编译成dll复制到bin目录下并在依赖注入容器中进行注入,然后在oxite配置节点中进行配置即可。轻松实现 Module的插拔功能。
IModulesLoaded接口,从字面上看就是”已经加载的Module”,在Oxite中有一个实现:ModulesLoaded类,在依赖注入容 器中已经进行了注册:parentContainer.RegisterType<IModulesLoaded, ModulesLoaded>()
。该类的Load方法正是实现上述Module插拔功能。
Load方法中将找到的Module实例化,并将对象私有变量List<IOxiteModule> modules中,分别提供泛型和非泛型的GetModules方法供外部读取。当然,这种插拔功能如果完全可以自己另外实现一 个:parentContainer.RegisterType<IModulesLoaded, MyModulesLoaded>()。
然后再看看IOxiteModule接口。
IOxiteModule接口定义了7个方法:
1: public interface IOxiteModule
2: {
3: void Initialize();
4: void Unload();
5: void RegisterRoutes(RouteCollection routes);
6: void RegisterCatchAllRoutes(RouteCollection routes);
7: void RegisterFilters(IFilterRegistry filterRegistry);
8: void RegisterModelBinders(ModelBinderDictionary modelBinders);
9: void RegisterWithContainer();
10: }
11:
在LoadModules类的Execute方法中的,查看foreach循环:
foreach (XoohooModuleConfigurationElement module in container.Resolve<XoohooConfigurationSection>().Modules)
对oxite配置节点获取moduls节点的所有元素进行遍历。
IOxiteModule moduleInstance = modulesLoaded.Load(config, module);
ModulesLoaded类实例modulesLoaded的Load方法将Module进行实例化,返回IOxiteModule对象。如果正常返回 IOxiteModule对象,就调用如下四个方法: RegisterWithContainer
Initialize
RegisterFilters
RegisterModelBinders
接着:
this.container.RegisterInstance(modulesLoaded);
作用是将”已经加载的Module”注册为单例。
这句写在循环里,为什么不写在循环外面?
接下来的几句是关于后台服务的,略去。
跳出循环后:
1: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
2:
3: routes.LoadFromModules(modulesLoaded);
4:
5: routes.LoadCatchAllFromModules(modulesLoaded);
6:
第一句代码比较熟悉,排除axd文件的路由。
第二、三行代码,调用RouteCollection类型的扩展方法LoadFromModules和LoadCatchAllFromModules。 这两个方法实际上分别执行所有”已经加载的Module”的IModulesLoaded.LoadFromModules方法和 IModulesLoaded.LoadCatchAllFromModules。
这里有一个疑问,这两行代码为什么没有像调用ModulesLoaded类的RegisterWithContainer,Initialize,RegisterFilters,RegisterModelBinders这四个方法那样调用:
1: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
2:
3: foreach (OxiteModuleConfigurationElement module in config.Modules)
4: {
5: IOxiteModule moduleInstance = modulesLoaded.Load(config, module);
6:
7: if (moduleInstance != null)
8: {
9: moduleInstance.RegisterWithContainer();
10: moduleInstance.Initialize();
11: moduleInstance.RegisterFilters(filterRegistry);
12: moduleInstance.RegisterModelBinders(modelBinders);
13: moduleInstance.RegisterRoutes(routes);
14: moduleInstance.RegisterCatchAllRoutes(routes);
15:
16: this.container.RegisterInstance(modulesLoaded);
17: //...
18: }
19: }
20:
setupContainer方法接下来注册的类型,除非了ModulesLoaded,其他的目前还不能直接分析。
二、静态方法Load
静态方法Load在被load方法调用,load方法被Applaction_Start方法调用。也就是说,Load方法在站点第一次被访问时,就会被调用。
1: public static void Load(HttpContextBase context)
2: {
3: IEnumerable<IBootStrapperTask> tasks = ((IUnityContainer)context.Application["container"]).ResolveAll<IBootStrapperTask>();
4: bool bootStrappersLoaded = (bool)context.Application["bootStrappersLoaded"];
5: IDictionary<string, object> state = (IDictionary<string, object>)context.Application["bootStrapperState"];
6:
7: if (state == null)
8: {
9: context.Application["bootStrapperState"] = state = new Dictionary<string, object>();
10: }
11:
12: // If the tasks have been executed previously, call Cleanup on them to rollback any changes
13: // they caused.
14: if (bootStrappersLoaded)
15: {
16: foreach (IBootStrapperTask task in tasks)
17: {
18: task.Cleanup(state);
19: }
20: }
21:
22: foreach (IBootStrapperTask task in tasks)
23: {
24: task.Execute(state);
25: }
26:
27: context.Application["bootStrappersLoaded"] = true;
28: }
29:
Application["bootStrapperState"]保存一个状态值,指示站点时候已经初始化。
Application["bootStrappersLoaded"],可以查看后台服务的实现部分。
Load方法首先获取实现IBootStrapperTask接口并注册入依赖注入容器的所有实例并保存入局部变量 IEnumerable<IBootStrapperTask> tasks。通过之前的分析我们知道,目前tasks集合中也就只有一个对象:LoadModules对象。
state部分暂时略去,看下面的部分:
1: if (bootStrappersLoaded)
2: {
3: foreach (IBootStrapperTask task in tasks)
4: {
5: task.Cleanup(state);
6: }
7: }
8:
9: foreach (IBootStrapperTask task in tasks)
10: {
11: task.Execute(state);
12: }
13:
如果站点已经初始化首先进行清理操作,然后再重新执行。
如果需要在运行时候重新加载模块,在其他地方调用Load也是可以的,从而可以实现真正的Module热插拔。
文未校对。但不成为不被拍砖的的理由。