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

如何在.NET中将类库插件发布为单个文件?

插件架构下的单文件打包与加载方案

从归档文件加载程序集的实现

你可以在CI/CD流水线中将每个插件的发布目录压缩为单独的ZIP归档,然后在主应用中直接从归档加载程序集,无需解压到磁盘。以下是具体实现步骤和代码示例:

1. CI/CD流水线打包插件(以GitHub Actions为例)

在流水线中完成插件的发布与压缩,输出单个ZIP文件:

# .github/workflows/publish-plugins.yml
name: Publish Plugins
on: [push]
jobs:
  build-plugin1:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x
      - name: Publish Plugin1
        run: dotnet publish ./src/Plugin1/Plugin1.csproj -c Release -o ./publish/Plugin1
      - name: Zip Plugin1
        run: zip -r Plugin1.zip ./publish/Plugin1
      - name: Upload Plugin1 Zip
        uses: actions/upload-artifact@v4
        with:
          name: Plugin1
          path: Plugin1.zip

2. 主应用从ZIP加载插件的代码示例

使用AssemblyLoadContext隔离插件加载环境,避免与主应用依赖冲突,直接从ZIP流中读取程序集:

首先定义自定义加载上下文:

using System.IO.Compression;
using System.Reflection;
using System.Runtime.Loader;

public class PluginAssemblyLoadContext : AssemblyLoadContext
{
    private readonly Dictionary<string, Assembly> _loadedAssemblies = new();
    private readonly ZipArchive _zipArchive;

    public PluginAssemblyLoadContext(ZipArchive zipArchive) : base(isCollectible: true)
    {
        _zipArchive = zipArchive;
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        var assemblyFileName = $"{assemblyName.Name}.dll";
        if (_loadedAssemblies.TryGetValue(assemblyFileName, out var existing))
            return existing;

        // 从ZIP中查找目标程序集
        var entry = _zipArchive.GetEntry(assemblyFileName);
        if (entry == null)
            return null; // 系统依赖交给默认上下文加载

        // 读取程序集字节到内存并加载
        using var stream = entry.Open();
        var assemblyBytes = new byte[stream.Length];
        stream.Read(assemblyBytes, 0, assemblyBytes.Length);
        
        var assembly = LoadFromStream(new MemoryStream(assemblyBytes));
        _loadedAssemblies[assemblyFileName] = assembly;
        return assembly;
    }
}

然后修改主应用的插件加载逻辑:

public void LoadPluginsFromZip(string zipFilePath)
{
    using var zipArchive = ZipFile.OpenRead(zipFilePath);
    var loadContext = new PluginAssemblyLoadContext(zipArchive);

    // 遍历ZIP中的所有DLL文件
    foreach (var entry in zipArchive.Entries.Where(e => e.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)))
    {
        try
        {
            var assemblyName = AssemblyName.GetAssemblyName(entry.FullName);
            var assembly = loadContext.LoadFromAssemblyName(assemblyName);

            // 查找并实例化IPlugin实现类
            foreach (var type in assembly.GetTypes())
            {
                if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
                {
                    var plugin = (IPlugin)Activator.CreateInstance(type)!;
                    plugin.Start(this); // this为IApp的实现实例
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"加载插件 {entry.Name} 失败: {ex.Message}");
        }
    }
}

更优替代方案

1. 使用ILRepack合并插件到单个DLL

ILRepack是开源的程序集合并工具,可将插件及其所有依赖合并为单个DLL,无需压缩包。

  • 安装ILRepack NuGet包到插件项目:
    dotnet add package ILRepack
    
  • 在插件项目文件中添加合并配置:
    <PropertyGroup>
      <TargetFramework>net8.0</TargetFramework>
      <ILRepack>True</ILRepack>
      <ILRepackOutput>$(AssemblyName).dll</ILRepackOutput>
      <ILRepackMergeAllTrue>True</ILRepackMergeAllTrue>
    </PropertyGroup>
    

发布后会生成单个包含所有依赖的DLL,主应用直接扫描加载该文件即可。

2. 自定义AssemblyLoadContext加载合并后的DLL

为避免插件与主应用的依赖冲突,使用独立的加载上下文加载合并后的DLL:

public class IsolatedPluginLoadContext : AssemblyLoadContext
{
    protected override Assembly? Load(AssemblyName assemblyName)
    {
        // 插件内部依赖优先加载,系统依赖交给默认上下文处理
        return null;
    }
}

// 加载合并后的插件DLL
var loadContext = new IsolatedPluginLoadContext();
var assembly = loadContext.LoadFromAssemblyPath("Plugin1.dll");

// 扫描并实例化插件(逻辑同之前的类型查找)
foreach (var type in assembly.GetTypes())
{
    if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
    {
        var plugin = (IPlugin)Activator.CreateInstance(type)!;
        plugin.Start(this);
    }
}

3. 打包为NuGet包部署

将每个插件打包为NuGet包,发布到私有NuGet源,主应用启动时从源下载并提取程序集(本质也是从ZIP加载,NuGet包为ZIP格式),适合团队协作和版本管理。

注意事项

  • 从内存加载程序集时,若插件存在非托管依赖,需单独处理(如将非托管DLL放在指定目录,通过NativeLibrary.Load加载)。
  • 合并程序集时,若插件有强签名,需保留签名或重新生成签名。
  • 使用AssemblyLoadContext的isCollectible: true参数可在卸载插件时回收资源,适合动态插件场景。

内容的提问来源于stack exchange,提问作者Sly Redbeard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 21:47:18