I am writing a simple plugin and stumbled upon contractType.IsAssignableFrom(pluginType)
returning different results depending on the order of loading.
Calling IsAssignableFrom
on 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.
[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.
public abstract class Contract
{
public abstract int Version { get; set; }
}
public class Plugin : Contract
{
public override int Version { get; set; }
}
[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. ----
}
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}");
}
}
}
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.
Assembly type, you can get three static types which allow you to load an assembly directly: LoadFrom. LoadFrom. LoadWithPartialName.
LoadFrom(String) Loads an assembly given its file name or path.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With