如何用C#和.NET Framework开发自己的插件构架
在企业环境中,允许在内部应用程序中编写并导入插件,而不必改变核心应用的情况十分少见。我认为原因之一在于:以前很难执行这类使用插件的构架。
开发者被迫花大量时间设计构架,或开发替代方案完成其目标。我们都知道,在企业环境中不容易获得大量时间,所以大多数开发者似乎都被迫选择开发替代方案。微软已经用C#和.NET Framework解决这个问题。因此现在开发一款允许动态插入组件,且不必改变核心代码的应用程序已不再重要。
本文中包括的解决方案(参见下载的项目文件)说明如何执行一个简单的插件构架。这个示范解决方案动态执行测试(每个测试为一个插件)并报告结果。每个测试完成不同的任务,并可以添加或删除测试,而不会影响到核心测试引擎的功能。这样我们就可以给应用程序增加测试,而不必重建整个应用程序。我将这个解决方案称为“SystemTests”,它包含四个项目:SharedObjects、TestEngine、TestExamples和TestApplication。
许多允许使用插件的应用程序都有一些激活插件功能的基本层,SystemTests方案也不例外。
共享对象层
共享对象层为主应用程序提供一种与插件通信的方法。主应用程序和插件引用同样的对象。
共享对象(Shared Objects)层由SharedObject项目执行。这个项目包括以下各项:
- ITest.cs—这是一个控制核心应用程序与插件通信的接口。在这个接口中,您可以发现下列方法与属性:
☉方法:
■ Execute(TestEngineState) – 执行测试。
☉ 属性:
■ Result– 返回一个存储测试结果信息的TestResult对象。
■ ID—存储测试ID。
■ Name—存储测试名称。
■ Description—存储测试说明。 - Test.cs – 这是一个功能很弱的基本类。它是一个辅助类,每个测试由它发展而来。继承这个类后,编写测试的开发者不必明确写出执行ITest接口所需的属性。
- TestEngineState.cs – 这个类被提交给每个测试,帮助每个测试确定之前的哪个测试通过或失败。使用这个类可以使测试彼此依赖。
- TestResult.cs – 测试执行完成后,它生成带一个TestResult对象的Result属性。这个类包含几个允许测试引擎决定测试结果的属性:
☉ 属性:
■ Message – 这个属性存储测试何以通过或失败的信息。
■ Passed – 这是一个表明测试是否通过的布尔属性。如果测试通过,此属性为真。
核心或主应用程序层
主应用程序层是应用程序执行核心功能的地方。这个层把所有插件连接起来,完成所需的报告,并与用户或数据库进行互操作。
主应用程序层由TestEngine项目执行,它包含一个名为“Engine”的类。收集必须运行的测试并运行它们,并通过一个TestEngineState项目将结果返回给调用的应用程序,是Engine类的工作。
LoadConfiguration和CreateTest是Engine类的两个最重要的方法。下面详细说明这两个方法:
- LoadConfiguration
通过解析一个XML配置文件确定必须加载哪个插件。LoadConfiguration由列表A中的代码执行,其中的注释对代码进行了解释:
列表A
privatevoid LoadConfiguration(string path)
{
XmlDocument configuration = newXmlDocument();
//Load the given XML file into our XML document.
configuration.Load(path);
//Get a NodeList of the Test nodes.
XmlNodeList tests = configuration.SelectNodes("/TestEngine/Tests/Test");
//Loop through the test nodes and load each test.
foreach (XmlNode test in tests)
{
//Load the enabled attribute to determine if the test is enabled.
XmlAttribute enabled = test.Attributes["Enabled"];
if (enabled.Value.ToLower() == "true")
{
XmlAttribute assemblyPath = test.Attributes["Assembly"];
XmlAttribute typeName = test.Attributes["Type"];
//Call CreateTest to instantiate the test and add it to the
// Tests property.
this.Tests.Add(CreateTest(assemblyPath.Value, typeName.Value));
}
}
}
- CreateTest
从指定的装配路径和类型名称中建立一个ITest对象。这个方法如列表B所示,其中的注释说明了从装配中动态初始化一个对象的必要步骤。
列表B
privateITest CreateTest(string assemblyPath, string typeName)
{
//Create an ObjectHandle object that will hold an instance of the requested
// type in the given assembly. The ObjectHandle class is in the
// System.Remoting namespace.
ObjectHandle handle = Activator.CreateInstanceFrom(assemblyPath, typeName);
//Unwrap the object instance into an object class.
object test = handle.Unwrap();
//Test to make sure the object implements the ITest interface. Any object
// that does not implement the ITest interface will cause an exception to
// be thrown.
if (test isITest)
return (ITest)test; //Return the object as an ITest instance.
else
thrownewException("This Type does not implement ITest, which is required.\nAssembly: " + assemblyPath + "\nType: " + typeName);
}
插件层
插件层中包含为应用程序建立的所有插件。一般来说,这个层中包含的插件执行共享对象层中的一个或几个接口。这允许主应用程序层与插件进行通信。
插件层由TestExamples项目执行。每个测试的功能各不相同,对于测试的功能也没有特别规定。唯一的规定就是测试必须执行ITest接口,其它功能由测试开发者决定。
列表C是一个非常简单的名为“MathTest”的测试实例。如果1不等于2(1!=2),则测试通过;如果1等于2(1==2),则测试失败:
列表C
using System;
using System.Collections.Generic;
using System.Text;
using SystemTests.SharedObjects;
namespace SystemTests.TestExamples
{
publicclassMathTest : Test, ITest
{
publicoverridevoid Execute(TestEngineState state)
{
//Set the ID for this test.
this.ID = "MathTest";
//Set the Name of this test.
this.Name = "Math Test";
//Set the description of this test.
this.Description = "Tests to see if 1 = 2";
//Instantiate a TestResult object to hold the
// results of this test.
TestResult result = newTestResult();
//Very simple test to see if 1 = 2
if (1 == 2)
result.Passed = true;
else
result.Passed = false;
//Set the result.
this.Result = result;
}
}
}
虽然这个测试十分简单,如上所述,只要它执行ITest接口,它就能实现任何功能。
本文中的解决方案包含另外两个样本测试:
- SuperManTest – 决定Superman类是否执行IsuperHero接口。
- WebTest – 决定本地计算机是否能够访问http://microsoft.com/,(如果您使用代理,这个测试可能失败。)
本文包含的解决方案中还有另外一个名为“TestApplication”的项目。这个项目是一个非常简单的Windows应用程序,它显示TestEngine的结果。
应用插件解决自己的问题
了解本文中包含的C#和.NET Framework解决方案,考虑如何将这种类型的插件功能用于解决您所在公司的商业问题。可能至少有一些情形适用这种高度动态化的功能。在适当的时候使用本文中的代码实例!