C# Avalonia如何实现一个插件系统 Avalonia MEF/DI插件

18次阅读

推荐在Avalonia中使用microsoft.Extensions.DependencyInjection结合AssemblyLoadContext实现插件系统:定义IPlugin等契约接口,通过隔离的AssemblyLoadContext动态加载插件DLL,插件向宿主IServiceCollection注册服务,宿主聚合INavigationProvider菜单项与IViewComponent视图并解耦通信。

C# Avalonia如何实现一个插件系统 Avalonia MEF/DI插件

在 Avalonia 中实现插件系统,推荐使用 Microsoft.Extensions.DependencyInjection(DI) 结合 MEF(Managed Extensibility Framework) 或纯 DI 方式动态加载插件。Avalonia 本身不内置插件机制,但可借助 .net 的模块化能力(如 AssemblyLoadContext、AssemblyLoadEventArgs、接口抽象 + 运行时反射)构建松耦合、热插拔的插件架构

定义统一插件接口与契约

所有插件必须实现约定接口,这是解耦核心。建议放在独立类库(如 Myapp.Plugins.Contracts)中供宿主和插件共同引用:

  • IPlugin:基础生命周期(Initialize() / Shutdown()
  • INavigationProvider:提供菜单项或导航入口(返回 MenuitemRoute
  • IViewComponent:可被 ContentControl 动态渲染的 Avalonia 控件(继承Control
  • 避免在接口中引用 Avalonia 程序集(如 Avalonia.Controls.Button),改用抽象类型或数据模型

插件发现与动态加载(基于 AssemblyLoadContext)

不依赖 MEF 的轻量方案(更可控、兼容 .NET 6+):

  • 插件以 .dll 形式存放于 Plugins/ 目录,命名规范如 MyPlugin.dll
  • 创建隔离的 AssemblyLoadContext 防止类型冲突:
var pluginContext = new AssemblyLoadContext(isCollectible: true); var assembly = pluginContext.LoadFromAssemblyPath(pluginPath);
  • 遍历 assembly.GetTypes(),筛选实现 IPlugin 的类型,用 Activator.CreateInstance 创建实例
  • 调用 plugin.Initialize(services),将宿主的 IServiceCollection 传入,让插件注册自身服务(如 services.AddSingleton()

宿主 DI 容器集成插件服务

AppBuilder 构建阶段注入插件:

  • 先构建基础 IServiceCollection(含 Avalonia 默认服务)
  • 扫描并加载插件,执行其 Initialize(IServiceCollection)
  • 完成所有插件注册后,调用 BuildServiceProvider()
  • 关键点:插件内部应只注册服务,**不调用 BuildServiceProvider**,避免容器嵌套

示例片段:

var services = new ServiceCollection(); // 注册宿主服务... RegisterHostServices(services);  // 加载插件 foreach (var pluginPath in GetPluginPaths()) {     var plugin = LoadPlugin(pluginPath);     plugin.Initialize(services); // 插件向 services 添加自己的类型 }  var app = BuildAvaloniaApp()     .UsePlatformDetect()     .SetupWithLifetime(lifetime); app.StartWithClassicDesktopLifetime(args, ShutdownMode.OnMainwindowClose);

运行时 UI 扩展(菜单/视图/命令)

插件通过接口向宿主“声明能力”,宿主负责聚合与呈现:

  • 插件实现 INavigationProvider.GetMenuItems() → 宿主收集所有 MenuItem 并添加到主菜单
  • 插件实现 IViewComponent.CreateView() → 宿主用 绑定渲染
  • 命令可绑定到 ICommand 属性,由插件提供 ViewModel 实现,宿主仅负责触发
  • 避免直接在插件中操作宿主窗口(如 Application.Current.MainWindow),改用事件或消息总线(如 WeakEventCommunityToolkit.Mvvm.Messaging)通信

不复杂但容易忽略:确保插件 DLL 不包含重复依赖(如 Avalonia.*),全部由宿主提供;发布时将插件目录设为 CopyToOutputDirectory;调试阶段可用 AssemblyResolve 事件辅助定位加载失败原因。

text=ZqhQzanResources