【问题标题】:AssemblyLoadContext Dispose .dllAssemblyLoadContext 处置 .dll
【发布时间】:2016-09-08 05:15:56
【问题描述】:

在 MVC 控制器中,我使用 AssemblyLoadContext.Default.LoadFromAssemblyPath(pathToDll); 加载程序集。我想在运行时删除或替换给定的 .dll 文件。这是不可能的,因为文件没有被释放。有没有办法处理 .dll 文件?有使用AppDomain 类的解决方案,该类在asp.net core 中不可用。

背景: 用户可以上传包含给定接口实现的自定义 .dll 文件。用户还应该能够替换他的文件。我在控制器中使用以下代码来访问实现:

    var conventions = new ConventionBuilder();
    conventions
        .ForTypesDerivedFrom<IPluginContract>()
        .Export<IPluginContract>()
        .Shared();

    var configuration = new ContainerConfiguration().WithAssembliesInPath(path, conventions);

    using (var container = configuration.CreateContainer())
    {
        var plugins = container.GetExports<IPluginContract>();
        return plugins;
    }

public static ContainerConfiguration WithAssembliesInPath(
    this ContainerConfiguration configuration,
    string path, AttributedModelProvider conventions,
    SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
    var fileNames = Directory
        .GetFiles(path, "*.dll", searchOption);

    List<Assembly> assemblies = new List<Assembly>();
    foreach (string relativePath in fileNames)
    {
        Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(relativePath));
        assemblies.Add(assembly);
    }


    configuration = configuration.WithAssemblies(assemblies, conventions);
    return configuration;
}

【问题讨论】:

  • 该问题的解决方案是使用 AppDomain 类,该类在 asp.net core 中不可用。
  • 除了AppDomain,没有其他方法可以卸载程序集。根据您的用例,您可以尝试采取艰难的方式,启动一个新进程,然后使用 WCF 之类的东西。
  • asp.net core中没有AppDomain的替代品?程序集只能为调用控制器而加载。

标签: c# .net asp.net-core asp.net-core-mvc .net-assembly


【解决方案1】:

选项 1:
尝试用LoadFromStream方法加载dll,然后就可以毫无异常的删除dll了。

例如:

foreach (string relativePath in fileNames)
{
        using (var fs = File.Open(relativePath , FileMode.Open))
        {
              Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(fs);
              assemblies.Add(assembly);
        }
        File.Delete(relativePath); //It doesn't throw exception
}

注意:使用 Net Core 3.1 进行测试,但可以与以前的版本一起使用。

选项 2:
如果您在尝试使用LoadFromStream 重新加载程序集时遇到问题,您应该尝试在LoadFromStream() 之前调用AssemblyLoadContext.Default.Unload()

但我不确定它是否适用于AssemblyLoadContext.Default,所以如果您仍然保留任何异常,您应该创建任何从AssemblyLoadContext 继承的类,并将标志isCollectible 设置为true,如下所示:

 public class PluginLoadContext : AssemblyLoadContext
 {
     public PluginLoadContext() : base(isCollectible: true)
     {
     }
 }

代码应该是:

//var pluginContext = new PluginLoadContext(); //In some place to call unload later

pluginContext.Unload();

foreach (string relativePath in fileNames)
{
        using (var fs = File.Open(relativePath , FileMode.Open))
        {
              Assembly assembly = pluginContext.LoadFromStream(fs);
              assemblies.Add(assembly);
        }
        File.Delete(relativePath); //It doesn't throw exception
}

选项 3:
还有一个选项可以覆盖你自定义PluginLoadContext的Load方法,你只需要加载你的入口dll,引用dll通过你入口dll的deps.json文件知道。

在这个例子中使用MemoryStream 来防止附加插件dll。

public class PluginLoadContext : AssemblyLoadContext
{
    private AssemblyDependencyResolver _resolver;

    public PluginLoadContext(string pluginPath) : base(isCollectible: true)//isCollectible doesn't appear in netstandard2.1
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly Load(AssemblyName assemblyName)
    {
        string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);

        if (assemblyPath != null)
        {
            //Using  MemoryStream to prevent attach dll to this .exe
            MemoryStream ms = new MemoryStream();

            using (var fs = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                fs.CopyTo(ms);
            }

            ms.Position = 0;
            return LoadFromStream(ms);
        }

        return null;
    }
}

然后你可以像这样加载你的入口插件dll。

var dllPath = "<path to your entry dll>" // dll and deps.json file together .
var pc = new PluginLoadContext(dllPath);
var assembly = pc.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(dllPath)));

//You can load a reference dll too if you need it
var referenceAssembly = pc.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension("<path of reference dll>")));

参考: https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#load-plugins

【讨论】:

【解决方案2】:

当您将 dll 加载到您的应用程序域中时,该 dll 在 appDomain 被销毁(即您的进程已停止)之前不是免费的,因此不会对 dll 进行处置。

有关如何实现所需功能的参考资料,请查看已回答的这些问题:

【讨论】:

  • 但是它们依赖于AppDomain 类,如果我没记错的话,它在asp.net core 中不可用。
  • 问题是关于AssemblyLoadContext 而不是AppDomain
【解决方案3】:

这听起来与MEF(托管可扩展性框架)非常相似。它允许注入 DLL,还有助于管理生命周期。

例子:

public static class MefInjection
{
   private static CompositionContainer mycontainer;
   public static CompositionContainer MyContainer
   {
      get
      {
         if (mycontainer == null)
         {
            var catalog =
               new DirectoryCatalog(".", "MyMEFProject.*");

            mycontainer = new CompositionContainer(catalog);

         }

         return mycontainer;
      }
   }
}

前面的代码将从“MyMEFProject”开始的同一目录中的所有程序集中获取所有导出的值。然后你可以使用mycontainer 来获取加载的DLL 的功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    相关资源
    最近更新 更多