RSS

InvalidCastException when using Assembly.LoadFile

11 Nov

Here is something that I have come across which puzzled me. If you are required to write an extensible application that uses plugins, and you have used dynamically loaded assemblies to accomplish that, then you might have encountered the following exception ([path] and [other path] are placeholders for actual file paths):

“[A]Plugin.MyPlugin cannot be cast to [B]Plugin.MyPlugin. Type A originates from ‘Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ in the context ‘LoadNeither’ at location ‘[path]Plugin.dll’. Type B originates from ‘Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ in the context ‘Default’ at location ‘[other path]Plugin.dll’.”

Setup
I’ll describe a sample scenario how this exception might occur. In this sample I’ll be using Xml Serialization for storing the plugin’s state, but the issue is not unique to Xml Serialization and you might encounter it regardless.

So, in this sample the plugin is an ordinary class, within a Class Library, that implements a regular .NET Interface (in a different Class Library). There is one executable that will use reflection to load the plugin and use it via the interface:

  1. IPlugin is the interface plugins are required to implement (Class Library project ‘PluginLib’).
  2. MyPlugin class is the inheriting plugin (Class Library project ‘Plugin’ referencing ‘PluginLib’).
  3. MainProg is the main extensible application (exe referencing PluginLib, which will dynamically load ‘MyPlugin’, residing in the ‘Plugin’ assembly).

PluginLib consists of a simple interface:

namespace PluginLib
{
    public interface IPlugin
    {
        void DoSomething();
    }
}

‘Plugin’ project is a very simply one (the ‘State’ property is not required for demonstrating this issue, and only serves as an excuse to use Xml Serialization in this sample):

namespace Plugin
{
    public class MyPlugin : PluginLib.IPlugin
    {
        public string State { get; set; }
        public void DoSomething()
        {
            // do something
        }
    }
}

MainProg dynamically loads the plugin for use (note that no try-catch clauses or any validations are shown here, but you would probably want to have those in “real world” code):

// [path] is a place holder for an absolute path located elsewhere
string pluginPath = @"[path]Plugin.dll";

// load the plugin from the specified path
Assembly assembly = Assembly.LoadFile(pluginPath);

// detect the first Type that implements IPlugin (you should test the result for 'null')
var type = assembly.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));

// instantiate the plugin using the detected Type
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);

// use the plugin
plugin.DoSomething();

So far so good. This works perfect. Now let’s assume that we would like to use Xml Serialization to maintain the plugin’s state. Although there are many ways to maintain a state, Xml Serialization is a very convenient method for doing so. I’ll revise the code a little:

// [path] is a place holder for an absolute path located elsewhere
string pluginPath = @"[path]Plugin.dll";
Assembly assembly = Assembly.LoadFile(pluginPath);

var type = assembly.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
plugin.DoSomething();

XmlSerializer serializer = new XmlSerializer(plugin.GetType());
using (MemoryStream ms = new MemoryStream())
{
    serializer.Serialize(ms, plugin);
}
  • Line 9: Notice that the XmlSerializer uses the actual Type of the created instance.
  • Line 12: This is where the exception will eventually occur.

For now, this will also work well. However, as I wanted to use the plugin “out of the box” without having the end-user to configure anything, I added a reference from MainProg directly to Plugin (“Add Reference…”). This way MyPlugin will be available at “design time” and will surely exist in the bin folder for deployment. This is when I got the exception on line 12.

Explanation
After googling for this exception and making several attempts to modify my code in order to try and get it to work, I came across this excellent post, which sums up the main differences between the different static Load methods that exist in the Assembly class. As the exception states, and as can be understood from the post, there are different contexts where assemblies are loaded to:

  • The ‘Load’ context is the Default context. ‘Design time’ referenced assemblies would load there (i.e. GAC assemblies or assemblies located in the private Bin folder etc.) In other words, where .NET assemblies are normally loaded by the process of probing. In her interview, Suzanne defines the Load method as the recommended good practice for loading assemblies.
  • The ‘LoadFrom’ context to my understanding is when you would like to have the assembly loader load an assembly which can’t be found by regular probing. Assemblies can be loaded to this context by explicitly calling Assembly.LoadFrom(). There are several advantages to this method, for example if there are other dependencies to be loaded referenced by this assembly.
  • The ‘Neither’ (or rather ‘LoadNeither’) context is used when Fusion in not involved (In her interview, Suzanne explains that Fusion is the part that is responsible for locating an assembly, for example in the private Bin folder, or GAC. So Fusion is not the Loader). Assemblies are loaded here when using Assembly.Load(byte[]), Reflection.Emit or Assembly.LoadFile(). To my understanding, this context is to be used when you would like save probing costs or have more control over the assemblies loaded. There are many articles and blogs that relate to Assembly.LoadFile() as a bad alternative, but I’m not sure that it is. As in other programming areas, I assume that this context addresses a need for particular projects. In the interview, Suzanne explains that there might be situations that you are required to recompile your assembly and reload it without using a different AppDomain, so that why the Neither context may come in handy.
  • There is another context not mentioned in that post (as it’s from 2003), and that’s the ReflectionOnly context. In this context you can load assemblies only to be examined using reflection, but you cannot run the code. For example, you may want to examine an assembly compiled for 64 bit on a 32 bit application, or check if an assembly is compatible with specific requirements prior to loading it into an executable context.

Now the exception is a lot clearer. By using Assembly.LoadFile(), the assembly plugin was loaded into the ‘LoadNeither’ context, whereas the Xml Serializer attempts to use a MyPlugin class already loaded into the Default Load context. Although these are the same classes as far as I’m concerned, they differ in the contexts used and therefore are considered as different classes by .NET.

Solution
So, a decision is to be taken here:

  • Either remove the “direct reference” to the plugin and always load it dynamically using LoadFile (or better, use LoadFrom). Only a single MyPlugin will exist in the different contexts and therefore the exception will be prevented. Or,
  • Figure out how to load the assembly into the Load context (or rather, use the already existing “design time” Plugin assembly in the private Bin folder even if differs from the file path specified by LoadFile).

I wanted the second option because as far as my app, it was OK to assume that no two plugins of the same name would co-exist, and if the dll was to be loaded dynamically (just like the plugin that it is), it will be OK to prefer the dll within the private Bin folder. So, I just needed to figure out how to load my plugin using the Load context, because Assembly.Load() does not have an overload for loading from a file path.

Luckily, reading the comments in above post provided a solution. Turns out that you can use the static AssemblyName.GetAssemblyName(string assemblyFile) to retrieve a valid AssemblyName object for assemblyFile. Once you have an AssemblyName, you can pass it as an argument to Assembly.Load(…). If an assembly by that AssemblyName is already loaded, that assembly would be used; or, if an assembly that corresponds to the AssemblyName is found within the probing paths, it will be favored and loaded. Only if an assembly that corresponds to that AssemblyName was not found, then the assembly you specified in the file path will be loaded. Suzanne commented that this might be a little costly in probing performance, but the behavior is exactly what I wanted it to be, and I wasn’t bothered by a possible performance issue in my particular case as my app doesn’t load assemblies all day long.

So, the modified code is as follows:

// [path] is a place holder for an absolute path located elsewhere
string pluginPath = @"[path]Plugin.dll";
AssemblyName name = AssemblyName.GetAssemblyName(pluginPath);
Assembly assembly = Assembly.Load(name);

var type = assembly.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);

XmlSerializer serializer = new XmlSerializer(plugin.GetType());

using (MemoryStream ms = new MemoryStream())
{
    serializer.Serialize(ms, plugin);
}

The problem is solved, although one must take into account that this solution means that the specified plugin in the file path might not be the one actually used at run-time. If you have to ensure that the specified plugin is indeed the one used, you’ll have to use one of the other Load contexts and handle the possibility of receiving the exception which started it all.

Other references
Here are some interesting references that I came across when reading about this (no particular order):

Summary
When using reflection for dynamically loading your assemblies, there are different methods of doing so. As a rule of thumb, you should always try to load your assemblies to the default Load context. Next alternative is the LoadFrom context, and LoadFile is your last alternative and should be used only for particular cases when the other two might not be adequate.

If you are going to use the method described in this post (i.e. loading using AssemblyName in order to ensure loading into the Load default context), you should remember that plugins might have identical names to assemblies found in the probing path or simply other assemblies that are already loaded. If your application is one that requires extensive plugin usage, you should probably develop some method of checking whether those dynamically loaded plugins have matching AssemblyNames, and possibly consider notifying users that there is a problem. Otherwise you might run into unexpected behavior. Same goes for development and debugging: you may think that you have loaded an assembly by specifying a valid path name, but end-up using an assembly from a different path (or from the GAC), with a different behavior and classes.

Advertisements
 
2 Comments

Posted by on 11/11/2012 in Software Development

 

Tags: , , ,

2 responses to “InvalidCastException when using Assembly.LoadFile

  1. Liam

    20/03/2013 at 21:58

    Many thanks for applying some time in order to post Shades “InvalidCastException when using Assembly.
    LoadFile | I Came, I Learned, I Blogged”. Many thanks for a second time -Jacelyn

     
  2. Matthew Smith

    24/04/2014 at 13:43

    Here’s another example of how to run into this issue without even referencing the type directly: http://stackoverflow.com/questions/23255892/how-to-reproduce-invalidcastexception-when-binding-to-an-assembly-in-the-loadfro/23255893#23255893

     

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: