Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do my assemblies need to be loaded in a specific order?

Tags:

c#

.net

I am writing a simple plugin and stumbled upon contractType.IsAssignableFrom(pluginType) returning different results depending on the order of loading.

Calling IsAssignableFromon the Plugin returns True as expected.
But if I load the Contract assembly before loading the Plugin, IsAssignableFrom on the Plugin returns False.

I am running Win10 and dotnet4.7 but I doubt that has any relevancy.


Code

[TestMethod]
public void SimplyLoadingPlugin_Succeeds()
{
    var plugin = Assembly.LoadFrom(PluginPathFilename);
    var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

    Assert.IsTrue(res); // Succeeds.
}

[TestMethod]
public void LoadingContractAndThenPlugin_Fails()
{
    var contract = Assembly.LoadFrom(ContractPathFilename);
    var plugin = Assembly.LoadFrom(PluginPathFilename);
    var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

    Assert.IsTrue(res); // Fails.
}

To make it harder to test:
If i run the LoadingContractAndThenPlugin_Fails test by itself is fails. But If i run the tests together it is dependent on order. Running SimplyLoadingPlugin_Succeeds first and LoadingContractAndThenPlugin_Fails last, makes both tests green but running them in the reverse order makes both fail.
So somehow the very loading of Contract before Plugin messes up something for me.
I can se nothing related in the GAC.


Below are all files needed. The paths in the probably have to be updated.
4 project with one file in each. 1 solution.

Contract.cs (a library project)

public abstract class Contract
{
    public abstract int Version { get; set; }
}

Plugin.cs (a library project)

public class Plugin : Contract
{
    public override int Version { get; set; }
}

Tests.cs (a test project)

[TestClass]
public class Tests
{
    private const string PluginPath = @"C:\DATA\Projekt\LoadFromOrder\Plugin\bin\Debug";
    private string PluginPathFilename = Path.Combine(PluginPath, "Plugin.dll");
    private string ContractPathFilename = Path.Combine(PluginPath, "Contract.dll");

    [TestMethod]
    public void SimplyLoadingPlugin_Succeeds()
    {
        var plugin = Assembly.LoadFrom(PluginPathFilename);
        var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

        Assert.IsTrue(res); // Succeeds.
    }

    [TestMethod]
    public void LoadingContractAndThenPlugin_Fails()
    {
        var contract = Assembly.LoadFrom(ContractPathFilename);
        var plugin = Assembly.LoadFrom(PluginPathFilename);
        var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

        Assert.IsTrue(res); // Fails.
    }

    // BEGIN ---- Update. ----
    [TestMethod]
    public void LoadingPluginFromTestProject_Succeeds()
    {
        var contract = Assembly.LoadFrom(
            @"C:\DATA\Projekt\LoadFromOrder\TestProject\bin\Debug\Contract.dll");
        var plugin = Assembly.LoadFrom(PluginPathFilename);
        var res = typeof(Contract.Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

        Assert.IsTrue(res); // Succeeds.
    }
    // END ---- Update. ----

}

Program.cs (a console project)

class Program
{
    static void Main(string[] args)
    {
        var tests = new Tests();
        try
        {
            System.Console.WriteLine("Press A for Success and B for Fail.");
            switch (System.Console.ReadKey(true).Key)
            {
                case ConsoleKey.A:
                    tests.SimplyLoadingPlugin_Succeeds();
                    break;
                case ConsoleKey.B:
                    tests.LoadingContractAndThenPlugin_Fails();
                    break;
            }
            System.Console.WriteLine("SUCCESS");
        }
        catch (Exception exc)
        {
            System.Console.WriteLine($"FAIL: {exc.Message}");
        }
    }
}
like image 274
LosManos Avatar asked Aug 09 '17 10:08

LosManos


People also ask

How does assembly load work?

Loads the assembly with a common object file format (COFF)-based image containing an emitted assembly. The assembly is loaded into the application domain of the caller. Loads an assembly with the specified name. Loads an assembly given its AssemblyName.

Which of the following static type allows you to load an assembly directly?

Assembly type, you can get three static types which allow you to load an assembly directly: LoadFrom. LoadFrom. LoadWithPartialName.

What is the method to load assembly given its file name and its path?

LoadFrom(String) Loads an assembly given its file name or path.

When assembly will load on AppDomain?

If an assembly is loaded into the same AppDomain, then the class can be instantiated in the usual way. But if an assembly is loaded into a different AppDomain then it can be instantiated using reflection. Another way is an interface.


1 Answers

By loading the Contract using Assembly.LoadFrom you are creating a reference ambiguity. That library is already loaded that is why we can do typeof(Contract) no need to load it again...

My recommendation: use reflection to determine what references you have and only load those that are not already there, here is a sample code snippet:

        var dllFiles = Directory.GetFiles(DIR, "*.DLL", SearchOption.AllDirectories);
        var plugins = new HashSet<Assembly>();

        var references = typeof(Program).Assembly.GetReferencedAssemblies();
        foreach (var dllPath in dllFiles)
        {
            string name = Path.GetFileNameWithoutExtension(dllPath);
            if (!references.Any(x => x.Name == name) && !plugins.Any(x => x.GetName().Name == name))
                plugins.Add(Assembly.LoadFrom(dllPath));
        }

On that sample we get every DLL for a given directory (subdirectories included) the DIR can be a relative path like ..\..\.. and only load those that are not already in the assembly references.


And here is the full solution with two plugin projects:
https://github.com/heldersepu/csharp-proj/tree/master/PluginSystem

like image 104
Helder Sepulveda Avatar answered Nov 15 '22 15:11

Helder Sepulveda