So, I've come by many articles referencing the use of AppDomain.CurrentDomain.AssemblyResolve
to load DLLs from embedded resources into a plugin at runtime (without the use of IlMerge). However, when I insert this code, the event handler never receives the thread before the plugin throws a TypeLoadException message for my main library.
I have tried placing the code in a static constructor, inside of the Execute method and in the main constructor; and although the event handler is registered, breakpoints in the handler do not get hit.
The environment is Dynamics CRM 2011 with latest roll up and using the SDK developer tools Plugin project and plugin class generation.
Anyone know what I need to be doing to get this to work?
It's important that the AssemblyResolve event registration happen before you reference any types from the assemblies you plan on loading via that callback. Even if you don't reference the types in your static constructor, you have to also ensure you have no static variables in the class or it's base classes that reference types in the external assemblies.
Here's how I do it: I have a separate class to handle the assembly loading, aptly named AssemblyLoader:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
internal static class AssemblyLoader
{
internal static void RegisterAssemblyLoader()
{
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve -= OnResolveAssembly;
currentDomain.AssemblyResolve += OnResolveAssembly;
}
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
return LoadAssemblyFromManifest(args.Name);
}
private static Assembly LoadAssemblyFromManifest(string targetAssemblyName)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = new AssemblyName(targetAssemblyName);
string resourceName = DetermineEmbeddedResourceName(assemblyName, executingAssembly);
using (Stream stream = executingAssembly.GetManifestResourceStream(resourceName))
{
if (stream == null)
return null;
byte[] assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
return Assembly.Load(assemblyRawBytes);
}
}
private static string DetermineEmbeddedResourceName(AssemblyName assemblyName, Assembly executingAssembly)
{
//This assumes you have the assemblies in a folder named "EmbeddedAssemblies"
string resourceName = string.Format("{0}.EmbeddedAssemblies.{1}.dll",
executingAssembly.GetName().Name, assemblyName.Name);
//This logic finds the assembly manifest name even if it's not an case match for the requested assembly
var matchingResource = executingAssembly.GetManifestResourceNames()
.FirstOrDefault(res => res.ToLower() == resourceName.ToLower());
if (matchingResource != null)
{
resourceName = matchingResource;
}
return resourceName;
}
}
Then, in my plugin class, I use a static constructor to call into my AssemblyLoader. By moving the logic into the separate class, I limit the number of types I'm referencing in my plugin class's static context, thus reducing the risk I accidentally reference something that's in the external assemblies.
using System;
using Microsoft.Xrm.Sdk;
public class MyPlugin : IPlugin
{
static MyPlugin()
{
AssemblyLoader.RegisterAssemblyLoader();
}
public void Execute(IServiceProvider serviceProvider)
{
//...
}
}
I also moved pretty much all of the usages of the external assemblies into other classes so that my plugin class doesn't use the at all. Mostly, this is out of an abundance of caution.
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