2
3 Assembly DomainAssemblyResolve(object sender, ResolveEventArgs args)
4 {
5 string resolvingAsmName = args.Name;
6 AssemblyName name = new AssemblyName(resolvingAsmName); //获取程序集全名。
7 Assembly asm = SearchAssemblyFromAllBundles(name); //从所有Bundle的程序集中查找匹配的程序集并加载。
8
9 return asm;
10 }
你相信三分钟能实现一个插件平台和一个插件吗?在这里我将演示一下史上最简单的插件框架。这个Demo以控制台为例,实现一个基于控制台的插件框架和一个控制台插件。当然,除了能三分钟开发一个控制台插件平台和插件,你还可以开发WinForm和Web,该框架完全兼容控制台、WinForm和ASP.NET应用系统,开发过程、开发规范完全一致。OK,我们直切主题。
1 控制台宿主——控制台插件框架
1) 在Visual Studio 2008中创建一个名为ConsoleShell1的命令行宿主应用程序。
2) 此时自动生成ConsoleShell1项目结构如下,它由一个Program.cs主程序和位于bin/plugin下的系统服务插件构成。该项目在Main方法中通过调用BundleRuntime.Start方法启动插件框架,然后加载运行所有插件。
3) 编译运行解决方案,此时的ConsoleShell只加载了几个系统的服务模块,使用UIShell.OSGi Remote Management Console来远程监控插件平台的结果如下。在控制台上输入list或者l命令,可以查看到目前启动了框架和6个系统服务模块。
该远程管理控制台通过ConsoleShell加载的RemotingManagement模块暴露的WebService来管理远程E-MEF框架运行状况。目前,它支持从远程安装、启动、停止、卸载、查询模块等功能。(声明:该控制台工具是基于EgeyeAddin 开源产品的ConsoleService来做,由于时间比较紧直接重用EgeyeAddin中ConsoleService大部分源代码,在此表示感谢。此外,我们还用了NDigester这个开源组件用于解析XML文件。)
到此为止,一个控制台插件框架便架构完成了。
2 控制台插件
1) 继续在1中所演示的工程中添加一个ConsolePlugin项目,命名为ConsolePlugin1。注意其位置是指向 Console Shell1\bin\plugins目录。
2) 此时生成的项目内容如下,它由模块激活器Activator.cs文件和模块清单文件Manifest.xml组成。
3) 修改Activator类的Start()和Stop()方法,参考示例如下:
2 {
3 public void Start(IBundleContext context)
4 {
5 Console.WriteLine("ConsolePlugin1 is active.");
6 }
7 public void Stop(IBundleContext context)
8 {
9 Console.WriteLine("ConsolePlugin1 is stopped.");
10 }
11 }
4) 至此,一个基本的Plugin示例已经建立完毕。
5) 编译整个解决方案,并运行上节建立的控制台应用程序,结果如下:
6) 运行远程管理控制台,运行list命令,结果如下,此时ConsolePlugin1已经被架加载并处于Active状态。
7) 在远程管理控制台输入“stop 3”来停止该模块,结果如下:
此时,ConsolePlugin1进入Resolved状态并在ConsoleShell运行的控制台上输出“ConsolePlugin1 is stopped.”信息。
这样一个插件也实现了!以下是这个过程涉及的名词的简单介绍。
3 宿主简介
插件框架是在公共语言运行时之上的模块运行时框架,需要由宿主启动和停止。也就是说,宿主是启动和停止模块运行时的程序,通过这种方式宿主就继承了插件框架动态插件化的所有特性。它支持各种类型的宿主,包括控制台、WinForm和ASP.NET等。控制台宿主为所有的插件提供了一个基于控制台的运行环境,而WinForm则相应的提供了一个桌面应用环境和网络应用环境。
4 激活器简介
激活器(Activator)是插件平台启动插件的入口/出口点,即在启动模块时会调用激活器的Start()函数,如果调用成功,模块将进入Active状态;当定制模块时会调用Stop()函数,一旦调用成功,模块将进入Resolved状态。因此插件可在激活器中实现预处理/清理功能,例如在Start()中模块可向服务总线注册服务,其它插件则可在此获取共享的服务;此外还可以在这里执行启动线程等其它操作。
5 Manifest简介
Manifes.xml包含了各个插件或服务所包含的私有定义, 依赖声明, 扩展及扩展点信息等, 是插件向底层框架暴露自己的唯一途径, 平台可以通过Manifest实现对插件和服务基础支持等一系列功能。
2 @"\\plugins\\TestPlugin\\bin\\TestPlugin.dll";
3 Assembly assembly = Assembly.LoadFile(assemblyFile);
4 IList<Assembly> assemblies = null;
5 PropertyInfo buildManagerProp = typeof(BuildManager).GetProperty("TheBuildManager",
6 BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetProperty);
7 if (buildManagerProp != null)
8 {
9 BuildManager buildManager = buildManagerProp.GetValue(null, null) as BuildManager;
10 if (buildManager != null)
11 {
12 PropertyInfo toplevelAssembliesProp = typeof(BuildManager).GetProperty(
13 "TopLevelReferencedAssemblies", BindingFlags.NonPublic |
14 BindingFlags.Instance | BindingFlags.GetProperty);
15 if (toplevelAssembliesProp != null)
16 {
17 assemblies = toplevelAssembliesProp.GetValue(
18 buildManager, null) as IList<Assembly>;
19 }
20 }
21 }
22 if (assemblies != null && assembly != null)
23 {
24 assemblies.Add(assembly);
25 }
我们团队从2008年5月份开始设计一个App Store,目前已经实现了产品的原型,现在产品已经进入了完善阶段。该产品最开始是基于我在2005年开发的一个Common Form Framework,此后结合了CAB & SCSF设计了Common UI Platform,从2008年5月份开始正式确定了产品的RoadMap,当然中间还是有不少变更了。它类似Google App Engine和Sina App Engine,只不过市场策略和模式是完全不同的。对于构建一个企业级的App Store,我个人认为.NET平台自身的特性在这领域确实不如Java,不过,好在也不是有什么是不可解决的。在产品设计过程中,我时刻关注业内的各种App Engine、App Store和Plugin Framework,包括Equinox、SharpDevelop、Egeye、Mono.Addin、MAF、MEF、SCSF、Google/Sina App Engine、Discuz等。每出现一种类似的产品,我都有一种心惊肉跳的感觉,担心别人抢在我之前设计了更好的同质产品,当然,也好在目前还没有看到同质的产品。
今天我先介绍一下该Store的内核OSGi.NET,同时也谈一下我见过比较简陋的插件系统Discuz。
OSGi是一个开放服务规范,“开放”意味着基于该平台可以使得很多人来共同使用和协作,而“服务”则是实现协作的一个手段。该规范可以总结为:A 插件化支持规范; B 面向服务支持规范; C 插件扩展规范; D 安全性与隔离性规范; E 系统服务规范。插件化规范完整详细定义了插件的结构、插件依赖、插件类加载、L10N和I18N、宿主插件和片段插件;面向服务支持规范定义了模块间服务协作的支持,这个服务并不是传统意义的企业级Web Service,确切的讲,仅是“接口+实现”,并对实现的引用进行管理;插件扩展规范定义了一个插件如何对另一个插件进行扩展,这种扩展手段非常简单,在这里提出了扩展点概念;安全性与隔离性则要确保被内核加载的插件不会对内核和其它插件产生一些副作用,比如我们决不能允许一个非法的插件来停止另一个插件;系统服务规范则预定义了几个系统服务。OSGi规范是基于Java编写的,此前还没有一个针对.NET平台OSGi规范,原因在于.NET并不支持ava那样优雅的类加载机制,不过还在我们目前都找到了绕过这些固有缺陷的方法并设计了针对.NET的规范。
我们团队在设计OSGi.NET时候,把易用性放在首位,通过场景驱动来设计Usecase。从而,基于OSGi.NET开发一个插件和开发一个.NET项目方式基本一样(当然,我们并不认为目前已经做到最好了,易用性肯定还有很大的改进空间)。以下是一个Hello World的插件,开发人员应该可以在5分钟内开发一个Hello World插件的。
1 在OSGi.NET的Plugins目录下,创建一个Class Library Project,并创建一个MyActivator类。
2 using System.Collections.Generic;
3 using System.Text;
4 using UIShell.OSGi;
5
6 namespace UIShell.TestBundle
7 {
8 public class MyActivator : IBundleActivator
9 {
10 public void Start(IBundleContext context)
11 {
12 Console.WriteLine("Hello World! Plugin is started.");
13 }
14 public void Stop(IBundleContext context)
15 {
16 Console.WriteLine("Plugin is stopped.");
17 }
18 }
19}
20
2 定义一个Manifest.xml文件
2 <Bundle Name="TestBundle"
3 SymbolicName="UIShell.TestBundle"
4 InitializedState="Started">
5 <Activator Type="UIShell.TestBundle.MyActivator" />
6 <Runtime>
7 <Assembly Path="bin/Debug/UIShell.TestBundle.dll" Share="false" />
8 </Runtime>
9 </Bundle>
10
通过Console运行OSGi.NET后,这个插件便会被内核加载启动,然后Print出Hello World。
基于OSGi.NET插件开发,一般只需做的事情有:(1)定义插件要实现的功能;(2)判断插件是否需要引用其它插件的功能,如果有,则可以通过A)在Runtime声明一个Dependeny节点;B)在MyActivator.Start方法中使用context.GetService方法获取依赖的服务;(3)通过定义Extension来扩展其它插件功能,通过ExtensionPoint暴露扩展点。
对于Discuz的关注是始于我们在设计基于ASP.NET的Store的。在设计这个Store的时候,我想比较一下基于OSGi.NET的ASP.NET插件平台和其它插件平台。Discuz插件系统是我见过设计的最为简单也是最为简陋的插件系统了。我只看了一下反编译的结果,就没有深入研究它的欲望了。以下是通过反编译看到的插件定义。
它的插件系统是基于接口和实现类来设计了。这种简陋的设计从严格意义上来讲不是一个插件平台,对开放性的支持也就更弱了,注定不可能有太多的人参与到这个产品插件的设计与扩展了。此外,每次内核的升级都可能会导致原有的插件无法正常使用。不过,这也可能是因为Discuz在产品设计阶段并没有提出开放性目标。