在 Avalonia 中实现插件系统,推荐使用 Microsoft.Extensions.DependencyInjection(DI) 结合 MEF(Managed Extensibility Framework) 或纯 DI 方式动态加载插件。Avalonia 本身不内置插件机制,但可借助 .NET 的模块化能力(如 AssemblyLoadContext、AssemblyLoadEventArgs、接口抽象 + 运行时反射)构建松耦合、热插拔的插件架构。
定义统一插件接口与契约
所有插件必须实现约定接口,这是解耦核心。建议放在独立类库(如
MyApp.Plugins.Contracts)中供宿主和插件共同引用: IPlugin:基础生命周期(
Initialize()/
Shutdown()) INavigationProvider:提供菜单项或导航入口(返回
MenuItem或
Route) 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<iexportservice pluginexportservice>()</iexportservice>)
宿主 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()→ 宿主用
<contentcontrol content="{Binding ActivePluginView}"></contentcontrol> 绑定渲染
命令可绑定到 ICommand属性,由插件提供 ViewModel 实现,宿主仅负责触发 避免直接在插件中操作宿主窗口(如
Application.Current.MainWindow),改用事件或消息总线(如
WeakEvent或
CommunityToolkit.Mvvm.Messaging)通信
不复杂但容易忽略:确保插件 DLL 不包含重复依赖(如 Avalonia.*),全部由宿主提供;发布时将插件目录设为
CopyToOutputDirectory;调试阶段可用
AssemblyResolve事件辅助定位加载失败原因。
