一、前言 插件模型指應(yīng)用程序由一些動態(tài)的獨立模塊構(gòu)成,每個模塊均具有一個或多個服務(wù),并滿足一定的插件協(xié)議,能夠借助主程序?qū)崿F(xiàn)主程序-插件,插件-插件之間的通訊。它定義了一套公共的接口,通過接口與插件進行通信,主要是通過反射來獲取相關(guān)的屬性和方法,然后再執(zhí)行指定的操作。其實,它也可以理解為定義一套通用的解決方案,通過反射來獲取相應(yīng)的程序集的相關(guān)類型,然后執(zhí)行這些指定類型的相關(guān)操作。它是一種即插即用的方案,更新及維護簡便。
本文僅僅是描述插件開發(fā)的大體模型,設(shè)計比較簡單,主要的步驟如下:
(1)、定義公共的接口以及抽象類。
(2)、定義和實現(xiàn)相關(guān)組件。
(3)、實現(xiàn)通用程序集反射操作類。
其中,公共的接口和抽象類定義在組件Jasen.Framework.Core中,該組件中提供通用程序集反射操作類AssemblyUtility;具體實現(xiàn)的相關(guān)組件為Jasen.Framework.Oracle、Jasen.Framework.Access和Jasen.Framework.SqlServer,它們都實現(xiàn)了Jasen.Framework.Core中的公共接口??蛻舳丝梢愿鶕?jù)實際情況來進行相應(yīng)的操作。相關(guān)組件圖如下:
二、公共接口和抽象類的定義以及相關(guān)組件的定義和實現(xiàn)
首先,定義公共的接口以及抽象類,如下類圖所示,定義一個公共的接口IDataTable,定義一個抽象類DataTable,這些公共的類型放置在最頂端的程序集中。而其他組件將分別重新創(chuàng)建,實現(xiàn)相對應(yīng)的功能,如SqlServerDataTable、OracleDataTable和AccessDataTable實現(xiàn)各自的功能。注意:Assembly.LoadFile(file)動態(tài)加載程序集時,該程序集在當前的運行環(huán)境中必須不存在的,否則可能會出現(xiàn)意想不到的數(shù)據(jù)異常,因此相關(guān)組件的實現(xiàn)必須是獨立的(僅僅是實現(xiàn)公共的接口)。
三、通用程序集反射操作類的實現(xiàn)
下面的AssemblyUtility主要是對程序集操作的通用類,可以根據(jù)指定目錄以及文件列表動態(tài)獲取相應(yīng)的程序集。同時,也可以通過目錄,文件以及程序集獲取相關(guān)的類型集合和對象集合。其中需要注意的是,實現(xiàn)的子類必須提供默認構(gòu)造函數(shù)??蛻舳丝梢酝ㄟ^該類獲取相應(yīng)的類型和對象集合,然后再執(zhí)行相應(yīng)的操作。這些操作都是通過動態(tài)加載程序集來實現(xiàn)的,代碼如下所示:
public static class AssemblyUtility
{
public static IEnumerable<Type> GetImplementdTypesByDirectory<T>(string baseDirectory)
{
IList<Assembly> assemblies= GetAssemblies(baseDirectory);
List<Type> types = new List<Type>();
foreach (Assembly assembly in assemblies)
{
types.AddRange(GetImplementdTypes<T>(assembly));
}
return types;
}
public static IEnumerable<Type> GetImplementdTypes<T>(string assemblyFile)
{
if (!File.Exists(assemblyFile))
{
return null;
}
try
{
return GetImplementdTypes<T>(Assembly.LoadFile(assemblyFile));
}
catch (Exception ex)
{
return null;
}
}
public static IEnumerable<Type> GetImplementdTypes<T>(Assembly assembly)
{
if (assembly == null)
{
return null;
}
return assembly.GetExportedTypes().Where(p =>
p.IsSubclassOf(typeof(T)) && (!p.IsAbstract) && (!p.IsInterface));
}
public static IList<T> GetImplementedObjectsByDirectory<T>(string baseDirectory)
{
IList<Assembly> assemblies = GetAssemblies(baseDirectory);
List<T> entities = new List<T>();
foreach (Assembly assembly in assemblies)
{
entities.AddRange(GetImplementedObjects<T>(assembly));
}
return entities;
}
public static IList<T> GetImplementedObjects<T>(string assemblyFile)
{
if (!File.Exists(assemblyFile))
{
return null;
}
try
{
return GetImplementedObjects<T>(Assembly.LoadFile(assemblyFile));
}
catch (Exception ex)
{
return null;
}
}
public static IList<T> GetImplementedObjects<T>(Assembly assembly)
{
if (assembly == null)
{
return null;
}
IEnumerable<Type> types = GetImplementdTypes<T>(assembly);
var result = new List<T>();
foreach (Type type in types)
{
ConstructorInfo constructor = type.GetConstructor(new Type[0]);
if (constructor == null)
{
continue;
}
object instance = Activator.CreateInstance(type);
if (instance is T)
{
result.Add((T)instance);
}
}
return result;
}
public static IList<Assembly> GetAssemblies(string baseDirectory)
{
if (!Directory.Exists(baseDirectory))
{
return new List<Assembly>();
}
string[] files = Directory.GetFiles(baseDirectory, "*.dll");
return GetAssemblies(files);
}
public static IList<Assembly> GetAssemblies(string[] assemblyFiles)
{
IList<Assembly> assemblies = new List<Assembly>();
try
{
foreach (string file in assemblyFiles)
{
if (!File.Exists(file)||(!file.EndsWith(".dll",StringComparison.InvariantCultureIgnoreCase)))
{
continue;
}
assemblies.Add(Assembly.LoadFile(file));
}
}
catch (Exception ex)
{
return new List<Assembly>();
}
return assemblies;
}
}
public static IEnumerable<Type> GetImplementdTypesByDirectory<T>(string baseDirectory)
public static IEnumerable<Type> GetImplementdTypes<T>(string assemblyFile)
public static IList<T> GetImplementedObjects<T>(Assembly assembly)
以上3個方法根據(jù)不同的參數(shù)(目錄、地址、程序集)來動態(tài)獲取程序集中的特定類型集合,這些類型為類型T的類或者子類(非抽象類和接口)。
public static IList<T> GetImplementedObjectsByDirectory<T>(string baseDirectory)
public static IList<T> GetImplementedObjects<T>(string assemblyFile)
public static IList<T> GetImplementedObjects<T>(Assembly assembly)
而以上3個方法根據(jù)不同的參數(shù)(目錄、地址、程序集)來動態(tài)獲取程序集中的特定對象集合,這些對象為類型T的類或者子類(非抽象類和接口)的實例。當組件中子類存在有參構(gòu)造函數(shù)時,必須實現(xiàn)默認構(gòu)造函數(shù)。從如下代碼可以看出:如果默認構(gòu)造函數(shù)不存在,將不會添加該對象實例。
ConstructorInfo constructor = type.GetConstructor(new Type[0]);
if (constructor == null)
{
continue;
}
object instance = Activator.CreateInstance(type);
if (instance is T)
{
result.Add((T)instance);
}
四、通用程序集反射操作類的單元測試
AssemblyUtility類主要的單元測試如下,僅驗證了正確的情況,代碼如下:
public class AssemblyUtilityTest
{
[TestMethod()]
public void GetAssembliesTest()
{
string assemblyPath = AppDomain.CurrentDomain.BaseDirectory+"\\Files\\";
IList<Assembly> result = AssemblyUtility.GetAssemblies(assemblyPath);
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count);
}
[TestMethod()]
public void GetAssembliesByFilesTest()
{
string[] assemblyFiles = new string[] { AppDomain.CurrentDomain.BaseDirectory + "\\Jasen.Framework.Core.dll",
AppDomain.CurrentDomain.BaseDirectory + "\\Jasen.Framework.Core.Test.dll",
"www",
"ww.dll"};
IList<Assembly> result = AssemblyUtility.GetAssemblies(assemblyFiles);
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Count);
}
[TestMethod()]
public void GetImplementedObjectsByDirectoryTest()
{
string assemblyDir = AppDomain.CurrentDomain.BaseDirectory + "\\Files\\";
IList<DataTable> result = AssemblyUtility.GetImplementedObjectsByDirectory<DataTable>(assemblyDir);
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count);
}
[TestMethod()]
public void GetImplementedObjectsTest()
{
string assemblyFile =AppDomain.CurrentDomain.BaseDirectory + "\\Files\\Jasen.Framework.Oracle.dll";
IList<DataTable> result = AssemblyUtility.GetImplementedObjects<DataTable>(assemblyFile);
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Count);
}
[TestMethod()]
public void GetImplementedTypesTest()
{
string assemblyFile = AppDomain.CurrentDomain.BaseDirectory + "\\Files\\Jasen.Framework.Oracle.dll";
IEnumerable<Type> types = AssemblyUtility.GetImplementdTypes<DataTable>(assemblyFile);
Assert.IsNotNull(types);
int count = 0;
foreach (var type in types)
{
Assert.IsTrue(type.IsSubclassOf(typeof(DataTable)));
Assert.IsFalse(type.IsAbstract);
Assert.IsFalse(type.IsInterface);
count++;
}
Assert.AreEqual(1, count);
}
[TestMethod()]
public void GetImplementdTypesByDirectoryTest()
{
string assemblyDir = AppDomain.CurrentDomain.BaseDirectory + "\\Files\\";
IEnumerable<Type> types = AssemblyUtility.GetImplementdTypesByDirectory<DataTable>(assemblyDir);
Assert.IsNotNull(types);
int count = 0;
foreach (var type in types)
{
Assert.IsTrue(type.IsSubclassOf(typeof(DataTable)));
Assert.IsFalse(type.IsAbstract);
Assert.IsFalse(type.IsInterface);
count++;
}
Assert.AreEqual(3, count);
}
}
五、總結(jié)
全文中主要圍繞AssemblyUtility通用類來進行講解的,僅僅是插件開發(fā)的一個思路。具體應(yīng)用的話,應(yīng)該相對來說比較直接,在客戶端獲取相應(yīng)的類型集合以及對象集合,然后再執(zhí)行這些集合的具體操作即可。其中,實現(xiàn)的組件(插件)放置在指定的目錄下,通過AssemblyUtility類即可動態(tài)加載目錄下的程序集,從而獲取到指定類型的數(shù)據(jù)。具體執(zhí)行什么操作,實現(xiàn)什么功能,這些都是在組件(插件)中實現(xiàn)即可。
源代碼下載:C#插件開發(fā)模型源代碼