求助:C#插件加载时*.resources.dll找不到的问题及解决方案
问题原因
- 资源程序集加载逻辑不匹配:.NET中资源DLL(
*.resources.dll)默认存放在语言特定子目录(如en、zh-CN),CLR会根据当前线程的UICulture自动去对应目录查找。但使用Assembly.LoadFrom加载插件时,插件所在的程序集上下文未正确继承主程序的资源查找规则,导致CLR直接在输出根目录搜索资源DLL,而非语言子目录。 - 资源DLL未被复制到输出目录:部分NuGet包的资源DLL可能因项目构建配置(如仅指定特定语言资源)、或
PrivateAssets设置,未被复制到插件输出目录;此外,若插件依赖的库未明确包含资源DLL的引用,构建时也不会自动输出这些文件。 - 插件加载上下文隔离:
Assembly.LoadFrom会将插件加载到独立的程序集上下文,该上下文无法共享主程序的资源查找路径,进一步加剧了资源DLL的定位失败问题。
可行解决方案
1. 全局注册AssemblyResolve事件,手动定位资源DLL
通过监听AppDomain.CurrentDomain.AssemblyResolve事件,在CLR找不到资源DLL时,手动根据当前文化信息去语言子目录查找并加载。这种方式无需修改插件加载核心逻辑,适配性较强:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { var assemblyName = new AssemblyName(args.Name); // 仅处理资源程序集 if (!assemblyName.Name.EndsWith(".resources")) return null; var baseDir = AppContext.BaseDirectory; var cultureName = assemblyName.CultureInfo.Name; // 拼接语言子目录路径 var resourcePath = Path.Combine(baseDir, cultureName, $"{assemblyName.Name}.dll"); // 优先加载当前语言的资源DLL,不存在则尝试默认语言(如en) if (File.Exists(resourcePath)) return Assembly.LoadFrom(resourcePath); var defaultResourcePath = Path.Combine(baseDir, "en", $"{assemblyName.Name}.dll"); return File.Exists(defaultResourcePath) ? Assembly.LoadFrom(defaultResourcePath) : null; };
2. 使用自定义AssemblyLoadContext加载插件(推荐)
自定义AssemblyLoadContext可以更精细地控制插件及其依赖的加载逻辑,避免上下文隔离问题。重写Load方法,专门处理资源程序集的路径查找:
public class PluginLoadContext : AssemblyLoadContext { private readonly string _pluginDir; public PluginLoadContext(string pluginDir) : base(isCollectible: true) { _pluginDir = pluginDir; } protected override Assembly? Load(AssemblyName assemblyName) { // 处理资源程序集 if (assemblyName.Name.EndsWith(".resources")) { var cultureDir = assemblyName.CultureInfo.Name; var resourcePath = Path.Combine(_pluginDir, cultureDir, $"{assemblyName.Name}.dll"); return File.Exists(resourcePath) ? LoadFromAssemblyPath(resourcePath) : null; } // 加载普通依赖程序集 var assemblyPath = Path.Combine(_pluginDir, $"{assemblyName.Name}.dll"); return File.Exists(assemblyPath) ? LoadFromAssemblyPath(assemblyPath) : null; } }
使用该上下文加载插件的示例:
var pluginDir = @"C:\MyProject\bin\Debug\net5.0"; var loadContext = new PluginLoadContext(pluginDir); var pluginAssembly = loadContext.LoadFromAssemblyPath(Path.Combine(pluginDir, "My.Plugin.dll")); // 后续实例化插件逻辑不变 Type pluginInterface = typeof(IPlugin); var pluginType = pluginAssembly.DefinedTypes.First(t => t.IsAssignableTo(pluginInterface)); Activator.CreateInstance(pluginType.AsType());
3. 调整项目构建配置,确保资源DLL输出
在插件项目的.csproj文件中添加配置,强制复制所有依赖的资源DLL到输出目录,并确保资源包被正确引用:
<PropertyGroup> <!-- 指定要包含的本地化资源,all表示所有语言 --> <SatelliteResourceLanguages>all</SatelliteResourceLanguages> </PropertyGroup> <ItemGroup> <!-- 确保依赖包的资源文件被复制到输出目录 --> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.12"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> </ItemGroup>
4. 临时禁用本地化资源加载(慎用)
如果业务不需要本地化支持,可以通过设置当前线程的UICulture为不变文化,避免CLR尝试加载资源DLL:
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
注意:此方法会全局禁用所有本地化文本显示,仅适合无本地化需求的场景。
内容的提问来源于stack exchange,提问作者monty
相关产品推荐
相关产品推荐

