一、前言
插件模型指應用程序由一些動態的獨立模塊構成,每個模塊均具有一個或多個服務,並滿足一定的插件協議,能夠借助主程序實現主程序-插件,插件-插件之間的通訊。它定義了一套公共的接口,通過接口與插件進行通信,主要是通過反射來獲取相關的屬性和方法,然後再執行指定的操作。其實,它也可以理解為定義一套通用的解決方案,通過反射來獲取相應的程序集的相關類型,然後執行這些指定類型的相關操作。它是一種即插即用的方案,更新及維護簡便。
本文僅僅是描述插件開發的大體模型,設計比較簡單,主要的步驟如下:
(1)、定義公共的接口以及抽象類。
(2)、定義和實現相關組件。
(3)、實現通用程序集反射操作類。
其中,公共的接口和抽象類定義在組件Jasen.Framework.Core中,該組件中提供通用程序集反射操作類AssemblyUtility;具體實現的相關組件為Jasen.Framework.Oracle、Jasen.Framework.Access和Jasen.Framework.SqlServer,它們都實現了Jasen.Framework.Core中的公共接口。客戶端可以根據實際情況來進行相應的操作。相關組件圖如下:
二、公共接口和抽象類的定義以及相關組件的定義和實現
首先,定義公共的接口以及抽象類,如下類圖所示,定義一個公共的接口IDataTable,定義一個抽象類DataTable,這些公共的類型放置在最頂端的程序集中。而其他組件將分別重新創建,實現相對應的功能,如SqlServerDataTable、OracleDataTable和AccessDataTable實現各自的功能。注意:Assembly.LoadFile(file)動態加載程序集時,該程序集在當前的運行環境中必須不存在的,否則可能會出現意想不到的數據異常,因此相關組件的實現必須是獨立的(僅僅是實現公共的接口)。
三、通用程序集反射操作類的實現
下面的AssemblyUtility主要是對程序集操作的通用類,可以根據指定目錄以及文件列表動態獲取相應的程序集。同時,也可以通過目錄,文件以及程序集獲取相關的類型集合和對象集合。其中需要注意的是,實現的子類必須提供默認構造函數。客戶端可以通過該類獲取相應的類型和對象集合,然後再執行相應的操作。這些操作都是通過動態加載程序集來實現的,代碼如下所示:
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個方法根據不同的參數(目錄、地址、程序集)來動態獲取程序集中的特定類型集合,這些類型為類型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個方法根據不同的參數(目錄、地址、程序集)來動態獲取程序集中的特定對象集合,這些對象為類型T的類或者子類(非抽象類和接口)的實例。當組件中子類存在有參構造函數時,必須實現默認構造函數。從如下代碼可以看出:如果默認構造函數不存在,將不會添加該對象實例。
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);
}
}
五、總結
全文中主要圍繞AssemblyUtility通用類來進行講解的,僅僅是插件開發的一個思路。具體應用的話,應該相對來說比較直接,在客戶端獲取相應的類型集合以及對象集合,然後再執行這些集合的具體操作即可。其中,實現的組件(插件)放置在指定的目錄下,通過AssemblyUtility類即可動態加載目錄下的程序集,從而獲取到指定類型的數據。具體執行什麼操作,實現什麼功能,這些都是在組件(插件)中實現即可。
源代碼下載:C#插件開發模型源代碼
http://files.cnblogs.com/jasenkin/Jasen.Framework.AssemblySample.rar
作者:JasenKin
出處:http://www.cnblogs.com/jasenkin/