AssemblyLoadContext Dispose .dll

本文关键字:dll Dispose AssemblyLoadContext | 更新日期: 2023-09-27 18:06:12

在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;
}

AssemblyLoadContext Dispose .dll

选项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为真,像这样:

 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,并且使用deps知道引用dll。

在这个例子中使用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://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support load-plugins

当你加载一个dll到你的应用程序域时,这个dll在appDomain被销毁(即你的进程被停止)之前不是空闲的。

关于如何达到你想要的功能的参考,请看看这些已经回答的问题:

    使用AppDomain动态加载和卸载dll
  • 热卸载和重新加载应用程序使用的dll

听起来与MEF (Managed Extensibility Framework)非常相似。它允许注入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的功能。