Natasha 插件化之dll

来源:https://www.cnblogs.com/wanghun315/archive/2023/05/20/17417520.html
-Advertisement-
Play Games

🥑本篇為學習博客園大佬聖殿騎士的《WPF基礎到企業應用系列》以及部分DotNet菜園的《WPF入門教程系列》所作筆記,對應聖殿騎士《WPF基礎到企業應用系列》第 1 - 6 章之間內容,包括 WPF 項目結構、程式的啟動和關閉、程式的生命周期、繼承關係以及常見的佈局控制項及其應用。文章有問題的話歡迎... ...


調用外部dll來實現組件化

場景

  1. 有一個設備管理控制系統,主要作用是控制設備及收集相關設備的信息,目前只集成了門禁和監控,後期期望添加更多設備時,一般都是在公司編寫完後現場實施並調試,代碼一般也是每個設備創建獨立的項目,供總項目調用;慢慢的可能會演變出所有設備都繼承一個公共的介面類,介面類中實現獲取能力集和發送命令,以此來減少對於總控模塊的修改,而此時只需要維護好能力集即可;可是這樣每次也需要運行一整個解決方案,如果其他地方也需要這個系統,只能通過卸載項目來減少引用進行實時,這時就可以通過將項目分拆出去,通過Natasha進行組件化的管理。這樣子的好處是通過約定的介面和能力集進行通信,主程式和設備耦合度低,如果遇到其他項目需要該系統,只需要將所需的dll放入特定文件夾即可。
  2. 任何可以分拆成模塊的系統都可以按照組件化的邏輯進行開發,例如有業務流程的項目,每個步驟可有多個組件選項,可執行一個或者多個;或者說通過獲得的插件來動態配置那一步應執行那個插件,通過插件的反饋判斷是否應執行下一步操作。

好處

  1. 低耦合,業務分拆,插件只需要關心插件介面及反饋即可,不需要關心核心系統的業務邏輯
  2. 分工明確,每個人只需關註插件代碼即可,項目整合後出現問題也好排查,未調用插件,則主系統有問題,調用插件結果與實際不符則插件有問題。
  3. 維護方便,學習成本低,對於某一個特定插件,代碼量會遠遠低於整個項目的代碼量,而且每個業務都可以進行分拆。

實現

  1. 獲得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

  1. 找到clas類並且實例化

找到class類(預設所有插件的實現類為*Controller),也可以按照下麵例子來

var type = assembly.GetTypes().Where(item => item.Name.IndexOf("Controller")!=-1).First();

實例化

var plugin = (IPluginClass)(Activator.CreateInstance(type)!);

例子

  1. 創建兩個底層項目

    1. 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);
          }
      }
      
    2. PluginUtil項目,用於聲明FruitFunction

      using System.ComponentModel;
      
      namespace PluginUtil
      {
          public enum FruitFunction
          {
              [Description("硬度")]
              hardness = 01,
              [Description("蘋果特有功能")]
              appleAttr = 02,
              [Description("切")]
              cut = 03
          }
      }
      
  2. 創建兩個插件項目

    1. 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("蘋果初始化完成");
              }
          }
      }
      
    2. 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("香蕉初始化完成");
              }
          }
      }
      
  3. 使用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], "");
                    }
                });
            }
        }
    }
    
    

    代碼結構示意圖:

    執行結果:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 創建阻塞的 EchoClient 客戶程式一般不需要同時建立與伺服器的多個連接,因此用一個線程,按照阻塞模式運行就能滿足需求 ```java public class EchoClient { private SocketChannel socketChannel = null; public ...
  • ## 1. 安裝Django 在命令行中輸入以下命令安裝Django ```shell pip install django ``` ## 2. 創建Django項目 在命令行中輸入以下命令創建一個名為myblog的Django項目 ```shell django-admin startprojec ...
  • ## 文章首發 [【重學C++】01| C++ 如何進行記憶體資源管理?](https://mp.weixin.qq.com/s/ZhRhN07wjypnkWXcu_Lz3g) ## 前言 大家好,我是只講技術乾貨的會玩code,今天是【重學C++】的第一講,我們來學習下C++的記憶體管理。 與java ...
  • 前言: 最近生產環境系統發現一個疑難雜症,看了很久的問題但是始終無法定位到問題並處理,然後查閱了相關資料也是定位不到問題,不過資料查閱卻給了個新的思路,以此為跳板最終解決了問題。 一、問題描述 功能介紹: “主計劃拆分子計劃”是APS系統很常見的功能,功能大概意思是用戶可選多個主計劃一次性進行“展開 ...
  • 本文圍繞 Spring Boot 中如何讓你的 bean 在其他 bean 之前完成載入展開討論。 問題 今天有個小伙伴給我出了一個難題:在 SpringBoot 中如何讓自己的某個指定的 Bean 在其他 Bean 前完成被 Spring 載入?我聽到這個問題的第一反應是,為什麼會有這樣奇怪的需求 ...
  • IDEA常用設置(提高開發效率) IDEA是一款當前比較主流的編譯器,我個人也用的比較多,但是有時出於各種原因,比如更換設備等等,IDEA總是需要重新安裝配置。這就讓我比較苦惱,因為總是記不全自己之前都修改了哪些地方(原諒腦子不好使hh),所以就以此篇文章記錄一下目前我的IDEA的設置情況。可能依舊 ...
  • # AOP概念 AOP(Aspect Oriented Programming),即面向切麵編程,可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來建立一種對象層次結構,用於模擬公共行為的一個集合。不過OOP允許開發 ...
  • ## 創建阻塞的伺服器 當 `ServerSocketChannel` 與 `SockelChannel` 採用預設的阻塞模式時,為了同時處理多個客戶的連接,必須使用多線程 ```java public class EchoServer { private int port = 8000; priv ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...