🥑本篇為學習博客園大佬聖殿騎士的《WPF基礎到企業應用系列》以及部分DotNet菜園的《WPF入門教程系列》所作筆記,對應聖殿騎士《WPF基礎到企業應用系列》第 1 - 6 章之間內容,包括 WPF 項目結構、程式的啟動和關閉、程式的生命周期、繼承關係以及常見的佈局控制項及其應用。文章有問題的話歡迎... ...
調用外部dll來實現組件化
場景
- 有一個設備管理控制系統,主要作用是控制設備及收集相關設備的信息,目前只集成了門禁和監控,後期期望添加更多設備時,一般都是在公司編寫完後現場實施並調試,代碼一般也是每個設備創建獨立的項目,供總項目調用;慢慢的可能會演變出所有設備都繼承一個公共的介面類,介面類中實現獲取能力集和發送命令,以此來減少對於總控模塊的修改,而此時只需要維護好能力集即可;可是這樣每次也需要運行一整個解決方案,如果其他地方也需要這個系統,只能通過卸載項目來減少引用進行實時,這時就可以通過將項目分拆出去,通過Natasha進行組件化的管理。這樣子的好處是通過約定的介面和能力集進行通信,主程式和設備耦合度低,如果遇到其他項目需要該系統,只需要將所需的dll放入特定文件夾即可。
- 任何可以分拆成模塊的系統都可以按照組件化的邏輯進行開發,例如有業務流程的項目,每個步驟可有多個組件選項,可執行一個或者多個;或者說通過獲得的插件來動態配置那一步應執行那個插件,通過插件的反饋判斷是否應執行下一步操作。
好處
- 低耦合,業務分拆,插件只需要關心插件介面及反饋即可,不需要關心核心系統的業務邏輯
- 分工明確,每個人只需關註插件代碼即可,項目整合後出現問題也好排查,未調用插件,則主系統有問題,調用插件結果與實際不符則插件有問題。
- 維護方便,學習成本低,對於某一個特定插件,代碼量會遠遠低於整個項目的代碼量,而且每個業務都可以進行分拆。
實現
- 獲得Assembly
主要使用NatashaDomain類實例化的方法(源碼位置:src\Natasha.Domain\Extension\NatashaDomainExtension.cs)
相關方法:
LoadPluginUseDefaultDependency 如果載入的dll已經被載入過了,則跳過
LoadPluginWithAllDependency 不會判斷高低版本,源碼中的解釋是預設的,感覺和Default類似
LoadPluginWithHighDependency 使用高版本的dll
LoadPluginWithLowDependency 使用低版本的dll
參數說明:
string path 必填,dll所在路徑
Func<AssemblyName, bool>? excludeAssembliesFunc = null 選填,需要排除的dll,返回true為排除引用,例如共同引用某個公用的dll(例如Utils.dll),此時可以選擇使用哪個版本的dll
- 找到clas類並且實例化
找到class類(預設所有插件的實現類為*Controller),也可以按照下麵例子來
var type = assembly.GetTypes().Where(item => item.Name.IndexOf("Controller")!=-1).First();
實例化
var plugin = (IPluginClass)(Activator.CreateInstance(type)!);
例子
-
創建兩個底層項目
-
IPluginBase項目:用於創建插件使用的介面
using PluginUtil; namespace IPluginBase { public interface IPluginClass { /// <summary> /// 初始化方法 /// </summary> public void initialize(); /// <summary> /// 獲得插件特有的方法 /// </summary> /// <returns></returns> public List<FruitFunction> getFunction(); /// <summary> /// 獲得需要定時方法 /// </summary> /// <returns></returns> public List<FruitFunction> getTimeFunc(); /// <summary> /// 通過方法執行插件代碼 /// </summary> /// <param name="function">FruitFunction</param> /// <param name="param">可能輸入的數值</param> /// <returns></returns> public String execute(FruitFunction function, String param); } }
-
PluginUtil項目,用於聲明FruitFunction
using System.ComponentModel; namespace PluginUtil { public enum FruitFunction { [Description("硬度")] hardness = 01, [Description("蘋果特有功能")] appleAttr = 02, [Description("切")] cut = 03 } }
-
-
創建兩個插件項目
-
PluginApple項目
using IPluginBase; using PluginUtil; namespace PluginApple { public class PluginAppleClass : IPluginClass { public string execute(FruitFunction function, string param) { switch (function) { case FruitFunction.cut: Console.WriteLine("切蘋果"); break; case FruitFunction.hardness: Console.WriteLine("蘋果很脆"); break; case FruitFunction.appleAttr: Console.WriteLine("蘋果特有屬性"); break; } return "結束"; } public List<FruitFunction> getFunction() { Console.WriteLine("返回蘋果的現有功能"); return new List<FruitFunction>() { FruitFunction.appleAttr, FruitFunction.cut, FruitFunction.hardness }; } public List<FruitFunction> getTimeFunc() { Console.WriteLine("返回蘋果的定時功能"); return new List<FruitFunction>() { FruitFunction.cut}; } public void initialize() { Console.WriteLine("蘋果初始化完成"); } } }
-
PluginBanana項目
using IPluginBase; using PluginUtil; namespace PluginBanana { public class PluginBananaClass : IPluginClass { public string execute(FruitFunction function, string param) { switch (function) { case FruitFunction.cut: Console.WriteLine("切香蕉"); break; case FruitFunction.hardness: Console.WriteLine("香蕉很軟"); break; } return "結束"; } public List<FruitFunction> getFunction() { Console.WriteLine("返回香蕉的現有功能"); return new List<FruitFunction>() { FruitFunction.cut, FruitFunction.hardness }; } public List<FruitFunction> getTimeFunc() { Console.WriteLine("返回香蕉的定時功能"); return new List<FruitFunction>() { FruitFunction.cut }; } public void initialize() { Console.WriteLine("香蕉初始化完成"); } } }
-
-
使用Natasha實現兩個插件項目的方法
前置條件:將PluginApple.dll和PluginBanana.dll放到NatashaStudyConsole.exe同級下的plugins文件夾中
using IPluginBase; using PluginUtil; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace NatashaStudyConsole { internal class PluginDemo { public void PluginMethod() { //獲得dll存放路徑 string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory+ "plugins"); //實例化NatashaDomain NatashaDomain domain = new(Guid.NewGuid().ToString()); List<IPluginClass> assemblies = new List<IPluginClass>(); //通過獲得所有的dll來進行實例化 Directory.GetFiles(path, "*.dll").ToList().ForEach(dll => { //載入dll var assembly = domain.LoadPluginWithAllDependency(dll); // 本例子中項目名稱為"A",需要實例化的類為"AClass",因此使用IndexOf方法 // 獲得項目名稱 var asmName = assembly.GetName().Name!; // 根據項目名稱判斷應該實例那個class var type = assembly.GetTypes().Where(item => item.Name.IndexOf(asmName)!=-1).First(); // 實例化 var plugin = (IPluginClass)(Activator.CreateInstance(type)!); if (plugin != null) { // 保存 assemblies.Add(plugin); } }); assemblies.ForEach(assembly => { // 實現相關方法 List<FruitFunction> functions = assembly.getFunction(); if(functions != null && functions.Count > 0) { assembly.execute(functions[0], ""); } }); } } }
代碼結構示意圖:
執行結果: