AssemblyLoadContext, dynamically load an assembly, instantiate an object and cast to a shared interface
Asked Answered
L

3

6

I'm trying to load an assembly with the AssemblyLoadContext (exists from the version 3.0 of netcore), instantiate an object and cast this object to an interface, but I get a cast exception error.

The interface is shared between the project that loads the assembly and the implementation instantiated. The object apparently is instantiated correctly but I get the unexpected error when I do (T)instance.

Trying with watcher I'm able to cast the instance correctly to the interface, following the code I'm using and a screenshot of the watcher:

private (ExecutionAssemblyLoadContext, T) LoadTheAssemblyAndInstance<T>(string assemblyName, string typeNameToInstance)
{
    var basePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
    var assemblyContext = new ExecutionAssemblyLoadContext($"{basePath}/{assemblyName}.dll");
    var assembly = assemblyContext.LoadFromAssemblyPath($"{basePath}/{assemblyName}.dll");
    var externalCodeEvent = typeNameToInstance != null ? assembly.ExportedTypes
        .Where(x => x.FullName == typeNameToInstance)
        .Single() : assembly.ExportedTypes.First();
    var instance = Activator.CreateInstance(
            externalCodeEvent,
            _defaultConstructorParameters
        );
    return (assemblyContext, (T)instance);
}

enter image description here

this is the full exception message:

System.InvalidCastException: 'Unable to cast object of type 'Expriva.NewWorkflow.BPMN.ExecutionCodeTest.ExecutionContractTest' to type 'Expriva.NewWorkflow.ExternalShared.Interfaces.IExecutionContract'.'

Following a screenshot that shows that T is implemented by the instance:

enter image description here

Sample code

An example repository has been added on GitHub to make it easier to see and test what the problem is. It would be great to have a way to use the type (interface of other) transparently either if it is loaded from AssmemblyLoadContext or not.

Lyte answered 2/10, 2019 at 9:0 Comment(6)
If you get a cast exception; the returned object just doesn't implement TSacrament
the object implement T, i will add a photoLyte
added the exception messageLyte
The exception indicates ExecutionContractTest does not implement IExecutionContract. Please make sure it does.Sacrament
i'm sure i have post a photo, look the edited questionLyte
the interface definition is in a shared netcore shared projectLyte
L
2

AssemblyLoadContext to support dynamic code loading and unloading, it creates an isolated context for loading code and its dependencies in their own AssemblyLoadContext instance.

The problem was that in the ExecutionAssemblyLoadContext implementation, the dependencies was resolved and isolated. Using the default implementation, siggested by the documentation of AssemblyLoadContext the shared types will not be isolated. Following the correct implementation to use to share the interface.

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

    protected override Assembly Load(AssemblyName name)
    {
        return null;
    }
}
Lyte answered 2/10, 2019 at 10:1 Comment(3)
The solution you provided is the default ALC, so you can simply use: Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("path/to/plugin");Gilkey
The default ALC is not marked collectable, the example is correct if you want to unload the loaded assemblies. If you don't care about unloading then the default ALC is fine to use.Leif
this is quite old and reading this now after trying to find answers to my problem im not sure how this adds value, how is this different from just querying from main default, aka you are not setting a folder for resolution so all dll are being resolved from some point? am i missing something, if i am not then please remove this as an answer as its more confusion in the mess of context loadingGoldsmith
L
3

I also came across this problem, think of it as if the different AssemblyLoadContext have their own copies of loaded types, so although you may reference the shared assembly in both contexts as far as they are concerned the types contained are unique.

The solution is to use the Load override in the derived ALC to resolve the assembly from the shared context.

This is simple if the shared types are loaded into the default ALC, for example.

public class ModAssemblyLoadContext : AssemblyLoadContext
{
    public ModAssemblyLoadContext()
        : base("ModAssemblyLoadContext", isCollectible: true)
    {
    }

    protected override Assembly Load(AssemblyName assemblyName)
    {
        return Default.Assemblies
            .FirstOrDefault(x => x.FullName == assemblyName.FullName);
    }
}
Leif answered 22/2, 2020 at 17:35 Comment(0)
L
3

I found a solution to my problem. It don't know if it fixes the one from the question here. I found the solution in the Microsoft code sample at line 24 of this .cs file: Github AssemblyLoadcontext Microsoft sample. (It is the same code which I started my sample).

I was referencing twice the same dll where my interfaces were defined. One dll was in my main app folder and the other copy was in the folder where the dynamic loaded dll was. I removed the copy of the dll from the folder where the dynamic dll was loaded. BOOM -> Problem solved!!!

You can find the message (warning) in provided code in Github. You can also try the solution with the same code. Ref: Sample code to reproduce the error

Limey answered 14/2, 2024 at 20:59 Comment(0)
L
2

AssemblyLoadContext to support dynamic code loading and unloading, it creates an isolated context for loading code and its dependencies in their own AssemblyLoadContext instance.

The problem was that in the ExecutionAssemblyLoadContext implementation, the dependencies was resolved and isolated. Using the default implementation, siggested by the documentation of AssemblyLoadContext the shared types will not be isolated. Following the correct implementation to use to share the interface.

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

    protected override Assembly Load(AssemblyName name)
    {
        return null;
    }
}
Lyte answered 2/10, 2019 at 10:1 Comment(3)
The solution you provided is the default ALC, so you can simply use: Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("path/to/plugin");Gilkey
The default ALC is not marked collectable, the example is correct if you want to unload the loaded assemblies. If you don't care about unloading then the default ALC is fine to use.Leif
this is quite old and reading this now after trying to find answers to my problem im not sure how this adds value, how is this different from just querying from main default, aka you are not setting a folder for resolution so all dll are being resolved from some point? am i missing something, if i am not then please remove this as an answer as its more confusion in the mess of context loadingGoldsmith

© 2022 - 2025 — McMap. All rights reserved.