Spätestens wenn man für CRM Online ein Plugin oder eine Custom Workflow Activity entwickelt, kann man keine Assemblies mehr in den GAC oder auf die Disk deployen.
Wie lässt sich nun Code in mehrere Projekte aufteilen oder ein Framework benutzen?
Es gibt drei Optionen.
Option eins und zwei sind aus meiner Sicht nicht empfehlenswert. Der Vollständigkeit halber führe ich sie jedoch auf.
Option 1 Template:
Das CRM Package Project liefert beim erstellen bereits eine Plugin Base Klasse. Das Template kann natürlich angepasst werden.
So hat man mit jedem Projekt sein Framwork mit dabei.
Erstellen von Visual Studio-Vorlagen
Option 2 Linked Items:
Der Code aus einer anderen Library wird als Link in das Projekt eingefügt.
Siehe hier.
Option 3 ILMerge:
Mithilfe von ILMerge lassen sich mehrere Assemblies zu einem mergen.
Davon macht z.B. Log4net gebrauch.
Die Anteilung zeigt wie ILMerge verwendet werden kann.
Dabei habe ich auf die folgenden Kriterien Rücksicht genommen:
- Es soll weiterhin das CRM Developer Toolkit verwendet werden könnenn (Rechtsklick –> Deploy).
- Debuggen soll weiterhin möglich sein (PDB Merge).
- Der Merge muss automatisiert werden.
- Die Assemblies müssen mit dem bestehenden SNK signiert werden.
- Early-Bound Types müssen weiterhin verwendet werden können.
1. Project-Struktur
Wir haben ein CRM Projekt auf Basis des CRM Developer Toolkits.

In diesem Beispiel haben wir vier Projekte. Eines für die Plugins (Plugins), eines für die Workflows (Workflow), eines mit gemeinsam verwendetem Code (Core) und das CrmPackage (CrmPackage). Das CRM Package Project muss das Workflow und das Plugin Project referenzieren. Das Workflow und Plugin Project referenzieren jeweils das Core Project.
2. ILMerge MSBuild Tasks installieren
Das kann ganz einfach über NuGet gemacht werden.

3. AfterBuild-Task einfügen
Dem Plugin und dem Workflow Project muss folgender AfterBuild Task hinzugefügt werden.
<UsingTask TaskName="ILMerge.MSBuild.Tasks.ILMerge" AssemblyFile="$(SolutionDir)packagesILMerge.MSBuild.Tasks.1.0.0.3toolsILMerge.MSBuild.Tasks.dll" />
<Target Name="AfterBuild">
<!-- Your configuration goes here -->
<!--- *************************** -->
<!-- Specify the name of the dll files (without file extension) of the referenced assemblies
which should be merged into the plugin assembly -->
<ItemGroup>
<ReferencedAssemblyToMerge Include="Core" />
</ItemGroup>
<PropertyGroup>
<SnkFileName>Key.snk</SnkFileName>
</PropertyGroup>
<ItemGroup>
<!-- The assembly from the current project should also be merged -->
<MergeAsm Include="$(OutputPath)$(TargetFileName)" />
<MergeAsm Include="@(ReferencedAssemblyToMerge -> '$(TargetDir)%(identity).dll')" />
</ItemGroup>
<!-- Constants -->
<!--- *************************** -->
<PropertyGroup>
<ILMergeDirectory>ILMerge</ILMergeDirectory>
<MergedAssembly>$(ProjectDir)$(OutDir)$(ILMergeDirectory)$(TargetFileName)</MergedAssembly>
<SnkFilePath>..$(SnkFileName)</SnkFilePath>
</PropertyGroup>
<ItemGroup>
<FilesToCopy Include="$(TargetDir)$(ILMergeDirectory)$(TargetName).dll" />
<FilesToCopy Include="$(TargetDir)$(ILMergeDirectory)$(TargetName).pdb" />
</ItemGroup>
<!-- Print out informations -->
<!--- *************************** -->
<Message Text="ILMerge: Merge '@(MergeAsm)' -&gt; '$(MergedAssembly)'" Importance="high" />
<Message Text="ILMerge: Sign with '$(SnkFilePath)'" Importance="high" />
<Message Text="ILMerge: Merge Temp Directory '$(ILMergeDirectory)'" Importance="low" />
<Message Text="ILMerge: Files to copy from temp '$(FilesToCopy)'" Importance="low" />
<Message Text="ILMerge: Files to delete after merge '$(FilesToDelete)'" Importance="low" />
<!-- Merge process -->
<!--- *************************** -->
<!-- Create temp directory -->
<MakeDir Directories="$(TargetDir)$(ILMergeDirectory)" />
<!-- Perform the merge -->
<ILMerge InputAssemblies="@(MergeAsm)" OutputFile="$(MergedAssembly)" TargetKind="SameAsPrimaryAssembly" SnkFile="$(SnkFilePath)" DebugInfo="True" Closed="True" CopyAttributes="True" />
<!-- Replace the original file with the merged files -->
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="$(TargetDir)%(Filename)%(Extension)" SkipUnchangedFiles="true" />
<!-- Remove temp directory -->
<RemoveDir Directories="$(TargetDir)$(ILMergeDirectory)" />
<!-- Delete the files from the merged projects -->
<Delete Files="@(ReferencedAssemblyToMerge-> '$(TargetDir)%(identity).dll')" />
<Delete Files="@(ReferencedAssemblyToMerge-> '$(TargetDir)%(identity).pdb')" />
</Target>
Konfiguration:
MergeAssemblyName: Der Name des Projects welches in das Original-Assembly hinein gemergt werden soll.
SnkFileName: Der Name des Snk-Files, mit dem das gemergte Assembly signiert werden soll.
Erklärung:
Weil weiterhin das CRM Package Project verwendet werden soll, muss das gemergte Assembly, dass Assembly des Projects, (Plugin.dll bzw. Workflow.dll) ersetzen.
Das liesse sich grundsätzlich mit einem einzigen Merge Task erledigen. Will man nun auch das PDB-File mergen (DebugInfo=true) gibt es eine Exception, weil das ursprüngliche PDB-File nicht überschrieben werden kann.
Deshalb wird zuerst ein ein Subdirectory “ILMerge” angelegt und die gemergten Files (DLL und PDB) werden in diesem Ordner abgelegt.
Anschliessend wird das originale DLL und PDB mit den gemergten Files ersetzt und der Ordner gelöscht.
Da nun das Core Assembly zwei Mal im selben Bin Folder liegt, werden z.B. Unit Tests mit der Meldung “ambiguous reference” abbrechen.
Damit das nicht geschieht werden in einem letzten Schritt diese Files gelöscht.
ILMerge Flags:
SnkFile: Damit wird das Assembly signiert. CRM Plugins müssen signiert sein.
CopyAttributes: Damit werden assembly Attribute kopiert. Wenn EarlyBound types verwendet werden, muss das Core Projekt folgende Zeile enthalten: [assembly: Microsoft.Xrm.Sdk.Client.ProxyTypesAssemblyAttribute()]
DebugInfo: Wird hier True gesetzt, wird für das gemergte assembly ein PDB file generiert.