1

In a MVC controller I use AssemblyLoadContext.Default.LoadFromAssemblyPath(pathToDll); to load an assembly. I want to delete or replace the given .dll file during runtime. This is not possible because the file is not disposed. Is there any way to dispose the .dll file? There are solutions using the AppDomain class, which is not available in asp.net core.

Background: The user is able to upload a custom .dll file which contains implementations of a given interface. The user should also be able to replace his file. I use the following code in a controller to access the implementations:

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

With

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;
}
jasdefer
  • 757
  • 12
  • 24
  • Possible duplicate of [Hot Unload/Reload of a DLL used by an Application](http://stackoverflow.com/questions/4887847/hot-unload-reload-of-a-dll-used-by-an-application) – Tobias Breuer Sep 08 '16 at 05:32
  • 1
    The solution of that question is using the `AppDomain` class, which is not available in asp.net core. – jasdefer Sep 08 '16 at 05:46
  • There is no other way to unload an assembly beside an `AppDomain`. Depending on your use case you could try to go the hard way, spin up a new process and then use something like WCF. – thehennyy Sep 08 '16 at 06:47
  • There is no alternative to AppDomain in asp.net core? The assembly must only be loaded for the call of a controller. – jasdefer Sep 08 '16 at 09:33

3 Answers3

3

OPTION 1:
Try loading dll with method LoadFromStream, then you can remove dll without exceptions.

Ex:

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
}

NOTE: tested with Net Core 3.1 but could work with previous versions.

OPTION 2:
If you have a problem when try to reload assemblies with LoadFromStream you should try to call AssemblyLoadContext.Default.Unload() before to LoadFromStream()

But I'm not sure if it works with AssemblyLoadContext.Default, so if you still keep any exception you should create any class that inherit from AssemblyLoadContext with flag isCollectible to true like this:

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

And the code should be:

//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
}

OPTION 3:
There is another option that override Load method of your custom PluginLoadContext, you only need to load your entry dll, and the reference dll is knew with deps.json file of your entry dll.

In this example is using MemoryStream to prevent attach plugin 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;
    }
}

Then you can load your entry plugin dll like this.

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>")));

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

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Zaha
  • 846
  • 10
  • 21
  • This applies for .Net Core 3.0, 3.1. Older versions (Like 2.2) does not have the `.Unload()` method for example. https://learn.microsoft.com/pt-br/dotnet/api/system.runtime.loader.assemblyloadcontext.unload?view=netcore-3.1#System_Runtime_Loader_AssemblyLoadContext_Unload – Bruno Miquelin Jul 07 '20 at 16:00
0

When you load a dll into your application domain, this dll is not free before the appDomain is being destroyed (i.e. your process is stopped) there is no dispose for a dll.

For references on how to reach your desired functionality please have a look at these questions that are answered already:

Community
  • 1
  • 1
Tobias Breuer
  • 825
  • 1
  • 11
  • 19
  • But they rely on the `AppDomain` class, which is, if I am not wrong, not available in asp.net core. – jasdefer Sep 08 '16 at 05:42
  • 2
    The question is about [AssemblyLoadContext](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext?view=netcore-3.0) and not about [AppDomain](https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.8) – Augusto Vasques Dec 01 '19 at 14:38
0

It sounds very similar to MEF ( Managed Extensibility Framework ). It allows inject DLL's and also helps to manage the lifecycle.

Example:

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

The preceding code will grab all the exported values from all the assemblies in the same directory starting with "MyMEFProject". Then you can use mycontainer to get loaded DLL's functionality.

Daniil T.
  • 1,145
  • 2
  • 13
  • 33