.NET MAUI Blazor动态加载程序集时服务注册缺失报错
问题场景
项目包含MAUI、Shared、Web三个工程,UI代码全部在Shared中,MAUI和Web工程实现Shared中的接口。需实现应用启动时检查服务器新版本,下载更新后的Shared DLL并动态加载。当前已编写动态加载逻辑,但运行时抛出错误:
'Cannot provide a value for property 'FormFactor' on type 'MyProject.Shared.Components.Pages.DeviceFormFactor'. There is no registered service of type 'MyProject.Shared.Interfaces.IFormFactor''
错误原因
动态加载的更新版Shared DLL被加载到独立的AssemblyLoadContext中,该上下文内的IFormFactor接口类型,与MAUI项目启动时注册到DI容器中的IFormFactor接口(来自主应用的AssemblyLoadContext)不属于同一类型系统。DI容器无法识别跨上下文的同名接口,因此找不到对应服务实现。
解决方案
1. 拆分接口到独立类库(推荐)
将MyProject.Shared.Interfaces中的所有接口提取到单独的类库项目(如MyProject.Core.Interfaces),让MAUI、Shared、Web三个项目都引用该类库。这样主应用和动态加载的Shared DLL会使用同一个程序集内的接口类型,从根源避免类型不匹配问题。
2. 配置动态加载上下文共享主依赖
若不拆分接口,修改动态加载逻辑,让自定义AssemblyLoadContext优先从主应用上下文获取已加载的依赖程序集(尤其是接口所在的Shared程序集):
修改LoadDynamicAssembly方法:
private void LoadDynamicAssembly() { try { var dllPath = Components.Layout.DownloadLayout.GetFrontendFilePath(); // 创建共享主上下文依赖的AssemblyLoadContext var assemblyLoadContext = new AssemblyLoadContext("DynamicAssemblyContext", isCollectible: true) { Resolving = (alc, name) => { // 优先从主应用上下文加载依赖程序集 var mainAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(name); return mainAssembly ?? alc.LoadFromAssemblyName(name); } }; var assembly = assemblyLoadContext.LoadFromAssemblyPath(dllPath); var componentType = assembly.GetType("MyProject.Shared.Components.Routes"); if (componentType == null) { throw new Exception("未在程序集中找到类型 MyProject.Shared.Components.Routes。"); } Logger.LogInformation("找到组件。正在更新UI。"); // 清理并加载新组件 ReinitializeBlazorWebView(componentType); } catch (Exception ex) { Logger.LogError(ex, "加载程序集或组件时出错。"); DisplayAlert("错误", $"加载组件失败: {ex.Message}", "确定"); } }
3. 验证服务注册一致性
确认MAUI项目中IFormFactor的注册逻辑正确,确保实现类已被注入:
// MauiProgram.cs中已正确注册,无需修改 builder.Services.AddSingleton<IFormFactor, FormFactor>();
关键说明
动态加载程序集时,类型标识由程序集+类型全名+AssemblyLoadContext共同决定,即使类型名称完全相同,来自不同上下文的类型也会被视为不同类型。通过共享接口程序集或让自定义上下文复用主上下文的依赖,可解决这种类型不匹配导致的DI注入失败问题。
内容的提问来源于stack exchange,提问作者JonuKDev

