Natasha 插件化之dll

  • Natasha 插件化之dll已关闭评论
  • 137 次浏览
  • A+
所属分类:.NET技术
摘要

主要使用NatashaDomain类实例化的方法(源码位置:srcNatasha.DomainExtensionNatashaDomainExtension.cs)


调用外部dll来实现组件化

场景

  1. 有一个设备管理控制系统,主要作用是控制设备及收集相关设备的信息,目前只集成了门禁和监控,后期期望添加更多设备时,一般都是在公司编写完后现场实施并调试,代码一般也是每个设备创建独立的项目,供总项目调用;慢慢的可能会演变出所有设备都继承一个公共的接口类,接口类中实现获取能力集和发送命令,以此来减少对于总控模块的修改,而此时只需要维护好能力集即可;可是这样每次也需要运行一整个解决方案,如果其他地方也需要这个系统,只能通过卸载项目来减少引用进行实时,这时就可以通过将项目分拆出去,通过Natasha进行组件化的管理。这样子的好处是通过约定的接口和能力集进行通信,主程序和设备耦合度低,如果遇到其他项目需要该系统,只需要将所需的dll放入特定文件夹即可。
  2. 任何可以分拆成模块的系统都可以按照组件化的逻辑进行开发,例如有业务流程的项目,每个步骤可有多个组件选项,可执行一个或者多个;或者说通过获得的插件来动态配置那一步应执行那个插件,通过插件的反馈判断是否应执行下一步操作。

好处

  1. 低耦合,业务分拆,插件只需要关心插件接口及反馈即可,不需要关心核心系统的业务逻辑
  2. 分工明确,每个人只需关注插件代码即可,项目整合后出现问题也好排查,未调用插件,则主系统有问题,调用插件结果与实际不符则插件有问题。
  3. 维护方便,学习成本低,对于某一个特定插件,代码量会远远低于整个项目的代码量,而且每个业务都可以进行分拆。

实现

  1. 获得Assembly

主要使用NatashaDomain类实例化的方法(源码位置:srcNatasha.DomainExtensionNatashaDomainExtension.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], "");                 }             });         }     } }  

    代码结构示意图:

    Natasha 插件化之dll

    执行结果:

    Natasha 插件化之dll