PowermacFan
PowermacFan

Reputation: 49

Loading MEF2 (System.Composition) plugins with a custom AssemblyLoadContext

(Using .NET 8.0)

This is an update to the problem I posted here. I attempted to fix the issue with a custom AssemblyLoadContext like so:

public class PluginLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver;
    public PluginLoadContext(string pluginPath) : base(true)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }
    protected override Assembly? Load(AssemblyName assemblyName)
    {
        var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
        if (assemblyPath != null) return LoadFromAssemblyPath(assemblyPath);
        return null;
    }
    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
    {
        var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
        if (libraryPath != null) return LoadUnmanagedDllFromPath(libraryPath);
        return IntPtr.Zero;
    }
}

Which loads assemblies from the plugin directory with this extension method:

    public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path,
        AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)
    {
        ArgumentNullException.ThrowIfNull(configuration);
        ArgumentNullException.ThrowIfNull(path);
        foreach (var dir in Directory.GetDirectories(path))
        {
            var assemblies = Directory
                .GetFiles(dir, "*.dll", searchOption)
                .Select(
                    assemblyFilePath =>
                    {
                        AssemblyName? assemblyName = null;
                        try
                        {
                            assemblyName = AssemblyName.GetAssemblyName(assemblyFilePath);
                        }
                        catch (BadImageFormatException)
                        {
                            
    // Ignored, means the assembly is native.
                        
    }
                        return assemblyName is null
                            ? null
                            : new PluginLoadContext(assemblyFilePath).LoadFromAssemblyPath(assemblyFilePath);
                    })
                .Where(assembly => assembly != null);
            configuration = configuration.WithAssemblies(assemblies, conventions);
        }
        return configuration;
    }

Here is the plugin code:

//...
    [ImportMany]
    public IEnumerable<Lazy<IMyContract, MyContractMetadata>> AvailableContracts{ get; set; }

//...

    public void Import()
    {
        var configuration = new ContainerConfiguration()
            .WithAssembliesInPath(_settings.Value.PluginDirectory, SearchOption.AllDirectories);

        try
        {
            using var host = configuration.CreateContainer();
            host.SatisfyImports(this);
        }
        catch (Exception ex)
        {
// Log this
        }
    }

However, when using the custom assembly load context the parts are not imported. But, when I try to load the parts in the default load context like so:

    public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path,
        AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)
    {
        ArgumentNullException.ThrowIfNull(configuration);
        ArgumentNullException.ThrowIfNull(path);
        foreach (var dir in Directory.GetDirectories(path))
        {
            var assemblies = Directory
                .GetFiles(dir, "*.dll", searchOption)
                .Select(
                    assemblyFilePath =>
                    {
                        AssemblyName? assemblyName = null;
                        try
                        {
                            assemblyName = AssemblyName.GetAssemblyName(assemblyFilePath);
                        }
                        catch (BadImageFormatException)
                        {
                            
    // Ignored, means the assembly is native.
                        
    }
                        return assemblyName is null
                            ? null
                            : AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFilePath); // This is the change
                    })
                .Where(assembly => assembly != null);
            configuration = configuration.WithAssemblies(assemblies, conventions);
        }
        return configuration;
    }

It will successfully load the parts. The problem with using the default load context is it doesn't load the dependencies for the parts properly. What I am doing wrong with the custom load context?

Upvotes: 1

Views: 32

Answers (0)

Related Questions