You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:C#插件加载时*.resources.dll找不到的问题及解决方案

问题原因
  1. 资源程序集加载逻辑不匹配:.NET中资源DLL(*.resources.dll)默认存放在语言特定子目录(如en、zh-CN),CLR会根据当前线程的UICulture自动去对应目录查找。但使用Assembly.LoadFrom加载插件时,插件所在的程序集上下文未正确继承主程序的资源查找规则,导致CLR直接在输出根目录搜索资源DLL,而非语言子目录。
  2. 资源DLL未被复制到输出目录:部分NuGet包的资源DLL可能因项目构建配置(如仅指定特定语言资源)、或PrivateAssets设置,未被复制到插件输出目录;此外,若插件依赖的库未明确包含资源DLL的引用,构建时也不会自动输出这些文件。
  3. 插件加载上下文隔离: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 06:54:32