Monday, April 11, 2016

Including Reference Assemblies in plugins

I recently had to reference the Newtonsoft.Json dll from within a CRM plugin and run into the infamous problem that there is no real support for referenced assemblies from custom plugins or workflow activities. This post provides a workaround using ILMerge.

The Problem


This has been a feature gap since plugins were introduced in CRM, you often want to reference external dll libraries from within your plugin. First, let’s look at how the CLR (.Net) loads referenced assemblies in order to understand the problem a little better. During runtime, what happens with referenced assemblies is that the CLR will try to load the referenced assemblies from some predetermined locations. For example, it will try to locate and load that assembly from the GAC or from the same directory as where the current process is executing. When you compile an assembly and it has other references, what happens is that the referenced assemblies get dropped in the same bin folder as your compiled assembly so they can be easily located and loaded at runtime.

That works great, and in CRM there is also the concept of the “bin” folder but the usage of it is a bit deprecated or not recommended (see ….). So in theory if you have access to the CRM server you could drop all your referenced assemblies in the “assembly\bin” folder or you can install your referenced assemblies in the server GAC and ten they will be able to be loaded at runtime. However, this is a big problem because you don’t always have access to the server and because depending on a specific server is risky as your servers might change, there could be a load balancer with multiple servers, etc.

So what else can you do if you have to register your plugin in the database? Well, when you register your plugin in the database what happens is that CRM will deserialize your dll content and load your plugin type explicitly, but you have no way of telling CRM that it must also load another assembly and you have no way to upload a reference assembly to the database. Certainly all .Net native assemblies like System.dll can be loaded because you can assume .Net to be available from all CRM servers, but what if your reference assembly is a third party like Newtonsoft.Json.dll ? In cloud environments (plugins in sandbox mode), reference assemblies can be dangerous because you do not know what code they are running in your shared cloud servers, so this probably explains why there is no support for reference assemblies in CRM. For plugin assemblies this risk is easily mitigated by running your plugin under partial trust in the CRM sandbox which uses code access security, .Net framework’s way of allowing external code to execute in a safe manner. This is why your plugins would fail to do things like try to read a local file or shut down the current server (you wouldn’t expect that to work in cloud environments!).


The Solution


So at this point you are left with 2 options: You can either compile the source code of your reference assemblies into your plugin assembly (if you have the source code) or you can ILMerge the reference assembly with your plugin assembly. In this example I will focus on the second option and I will illustrate exactly how I did it for the example of Newtonsoft.Json.dll in the following 3 simple steps:



1.        Add ILMerge Nuget to your plugins project

2.       Add Newtonsoft.Json Nuget package to your plugins project


3.       Now you simply need to edit your project configuration in Visual Studio such that when you build your plugin it automatically merges it with your reference assemblies (so you will never have to manually do ilmerge). To do this go to the “Build Events” tab of your project settings and under “Post Build event command line” box enter the following:

$(SolutionDir)packages\ilmerge.2.14.1208\tools\ILMerge.exe /keyfile:$(SolutionDir)/.snk /target:"library" /copyattrs /out:$(TargetDir)$(TargetName)$(TargetExt) $(ProjectDir)$(IntermediateOutputPath)$(TargetName)$(TargetExt) $(SolutionDir)packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll

Note you might need to modify the command above according to the version of the Nuget package you installed in previous step and you have to specify the location of your SNK file that you use to sign your plugin assembly.

Now when you build your plugin assembly, the Newtonsoft.Json.dll is embedded inside your plugin so CRM will not need to load the reference assembly. There you go, I hope you find this useful and simple enough.