We’ve been writing a tool on our project that requires late-binding to classes/types in our service layer. It’s typical plugin style coding, similar to how much of the various Microsoft Enterprise Library modules are meant to be loaded when needed.

One of the questions raised was what happens if the assemblies containing our classes/types are not in the execution path? This usually occurs when the requested assembly has a dependency on another assembly.

After some playing about with explicitly loading assemblies into the current AppDomain, I found a couple of useful events; AssemblyLoad and AssemblyResolve. The real saviour is AssemblyResolve – hook this up up in your plugin loading code and they it gives you a chance to handle when the requested assembly fails to load. Both these events hang off the AppDomain.CurrentDomain and so catch any assembly load/failure that occurs within execution scope.

In the sample below, I’ve assumed that we know what the path to the assemblies are by setting a constant (if you don’t know this you’ll either have to set some predetermined hint paths or ask for user input). It’s here that the AssemblyResolve event can piece together the missing assembly path, load it and continue.

   1: const string assemblyPath = @"C:\LoadingAssemblies\Model\ParentAssembly\bin\Debug\";
   3: private void LoadTest()
   4: {
   6:     AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
   7:     AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
   9:     Assembly pluginAssembly = Assembly.LoadFrom(string.Format(@"{0}\{1}", assemblyPath, "PluginAssembly.dll"));
  11:     Type myType = pluginAssembly.GetType("PluginAssembly.ParentClass");
  12:     if (myType != null)
  13:         Debug.WriteLine(string.Format("{0} -- {1}", myType.FullName, myType.AssemblyQualifiedName));
  15:     //fails since it is not defined in this assembly and does not have a full assembly definition
  16:     myType = Type.GetType("ChildAssembly.ChildClass");
  17:     if (myType != null)
  18:         Debug.WriteLine(string.Format("{0} -- {1}", myType.FullName, myType.AssemblyQualifiedName));
  20:     //succeeds since we have the full definition - will fire the AssemblyResolve event which points to the assembly location
  21:     myType = Type.GetType("ChildAssembly.ChildClass, ChildAssembly, Version=, Culture=neutral, PublicKeyToken=null");
  22:     if (myType != null)
  23:         Debug.WriteLine(string.Format("{0} -- {1}", myType.FullName, myType.AssemblyQualifiedName));
  25:     //fails since it is not defined in this assembly and does not have a full assembly definition
  26:     myType = Type.GetType("SubAssembly.SubClass");
  27:     if (myType != null)
  28:         Debug.WriteLine(string.Format("{0} -- {1}", myType.FullName, myType.AssemblyQualifiedName));
  30:     //succeeds since we have the full definition - since the assembly is in the app path it will automatically load
  31:     myType = Type.GetType("SubAssembly.SubClass, SubAssembly, Version=, Culture=neutral, PublicKeyToken=null");
  32:     if (myType != null)
  33:         Debug.WriteLine(string.Format("{0} -- {1}", myType.FullName, myType.AssemblyQualifiedName));
  35:     //fails because we need to fully qualify the namespace of the class
  36:     myType = Type.GetType("SubClass, SubAssembly, Version=, Culture=neutral, PublicKeyToken=null");
  37:     if (myType != null)
  38:         Debug.WriteLine(string.Format("{0} -- {1}", myType.FullName, myType.AssemblyQualifiedName));
  40: }
  42: static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
  43: {
  44:     Debug.WriteLine(string.Format("CurrentDomain_AssemblyResolve reports that '{0}' was unresolved.", args.Name));
  46:     // args.Name == "ChildAssembly, Version=, Culture=neutral, PublicKeyToken=null"
  47:     string[] parts = args.Name.Split(',');
  48:     if (parts.Length > 0)
  49:     {
  50:         Assembly assembly = Assembly.LoadFrom(string.Format(@"{0}\{1}.dll", assemblyPath, parts[0].Trim()));
  51:         return assembly;
  52:     }
  54:     return null;
  55: }
  57: static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
  58: {
  59:     Debug.WriteLine(string.Format("CurrentDomain_AssemblyLoad reports that '{0}' was loaded.", args.LoadedAssembly.FullName));
  60: }


This is a cross post from my EMC blog, mainly for backup duplicity and to aggregate some of my past postings. My EMC blog used to be under the Conchango brand but was acquired by EMC so I’ve also retrospectively refreshed some of the old links and maybe a tweak a bit of content too.

permalink to the original post here