Rise to distinction
Time & tide wait for none
Monthly Archives: September 2014
Generate smaller CRM plugins dll using LibZ than ILMerge
Synopsis:
In this post i am sharing my experience on why and how to use assemblies embedding capability of LibZ as compared to merging of ILMerge. Where most of us are very well aware of ILMerge, LibZ is likely not that commonly used for as an alternate. However my case to commend LibZ is not just based on assembly size.
Scenario: With a heavily custom developed plugins solution any horizontal change implemented at the framework level risks breaking complete solution. ILMerge is a commonly used tool to get single assembly for deployment, out of merging dependents (including CRM early bound proxy assembly). However, just recently i needed to merge another framework into our existing plugins dll.
Problem: The additional framwork also uses early bound proxy classes of OOTB crm entities for some common plugins. It did not give any problems during compile time and things seemed fine as ILMerge did not complain either. However at runtime plugins broke throwing early bound proxy types error during deserialization, as below:
The Web Service plug-in failed in OrganizationId:
SdkMessageProcessingStepId: 5afc1b7f-09e8-e311-b0c2-005056b0000b;
EntityName: appointment; Stage: 40; MessageName: SetStateDynamicEntity;
AssemblyName: Core.Plugins.Entity_Appointment.BusinessPlugin, Core.Plugins.Merged, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7e7c44ffd3e5c71d;
ClassName: Core.Plugins.Entity_Appointment.BusinessPlugin;
Exception: Unhandled Exception: System.ArgumentException: A proxy type with the name account has been defined by multiple types.
Current type: AdditionalFramework.Entities.Account, Core.Plugins.Merged, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7e7c44ffd3e5c71d,
Existing type: Core.Models.Entities.Account, Core.Plugins.Merged, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7e7c44ffd3e5c71d
Parameter name: account
at Microsoft.Xrm.Sdk.AssemblyBasedKnownProxyTypesProvider.AddTypeMapping(Assembly assembly, Type type, String proxyName)
at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.LoadKnownTypes(Assembly assembly)
at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.RegisterAssembly(Assembly assembly)
at Microsoft.Xrm.Sdk.AssemblyBasedKnownProxyTypesProvider.GetTypeForName(String name, Assembly proxyTypesAssembly)
at Microsoft.Xrm.Sdk.ProxySerializationSurrogate.System.Runtime.Serialization.IDataContractSurrogate.GetDeserializedObject(Object obj, Type targetType)
at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserializeWithSurrogate(XmlReaderDelegator xmlReader, Type declaredType
, DataContract surrogateDataContract, String name, String ns)
at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlDictionaryReader reader)
at Microsoft.Crm.Extensibility.InprocessServiceProxy.ConvertToEarlyBound[T](Object o)
at Microsoft.Crm.Extensibility.InprocessServiceProxy.RetrieveCore(String entityName, Guid id, ColumnSet columnSet)
at Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy.Retrieve(String entityName, Guid id, ColumnSet columnSet)
at Core.Plugins.BusinessLogic.AppointmentEntity.BusinessLogic(EntityReference appointmentReference, Appointment preAppointment)
at Core.Plugins.Entity_Appointment.BusinessPlugin.Execute(LocalPluginContext localContext)
at Core.Plugins.Common.Plugin.Execute(IServiceProvider serviceProvider)
at Microsoft.Crm.Extensibility.V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)
at Microsoft.Crm.Extensibility.VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)
In this case existing proxy assembly is Core.Model.Entities.dll which is merged into Core.Plugins.Merged.dll asembly. While the additional framework assembly is AdditionalFramework.Entities.dll, also being merged into Core.Plugins.Merged.dll assembly. Error is specifically thrown from the first call to casting Entity into a specific early bound proxy type.
Solution: I tried forcing proxy assembly in OrganizationServiceFactory instance using a class type from proxy assembly (as described here: http://www.datazx.cn/Forums/en-US/bc7e93d4-1b36-4e21-9449-f51b67a2e52c/action?threadDisplayName=crm-2011-online-early-binding-plugin&forum=crmdevelopment), but of no use.
The reason this could not work is the way ILMerge works. Due to merge, container assembly of all types resolved to Core.Plugins.Merged.dll, even though types were defined in separate assemblies being merged. That brought to the situation where merged assembly contained two types for each of OOTB proxy entities. Though one should ideally have single proxies assembly but in this scenario that meant significant changes in existing code or the framework.
I still needed a solution with one dll with proxies available from both underlying assemblies. ILMerge’s internalize could not suffice here, with having its own risks attached. So, any alternates to ILMerge ? Luckily LibZ rescued here (https://libz.codeplex.com many thanks to the project team), “embedding” instead of “merging“. This worked quite well though i have to enfornce proxy assembly on OrganizationServiceFactory, as described in the link above. There is some housekeeping to do when using DataContext from additionalframework proxies, code must enforce correct proxy in OrganizationServiceFactory.
IOrganizationServiceFactory organizationServiceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
TracingService.Trace("setting proxy behavior ...");
organizationServiceFactory.GetType().GetProperty("ProxyTypesAssembly").SetValue(organizationServiceFactory, typeof(DataContext).Assembly, null);
TracingService.Trace("... proxy behavior set");
Fact of the matter is, size of the resulting assembly reduced from 6.33 MB to 1.57 MB, which took away problems like timeouts during plugins deployment and out-of-memory errors. Secondly, when using crm toolkit, the register file does not need to be manually modified for newly added plugin, as the output assembly name of plugins project remains same. Though this is not that significant, still a value add!!!
REM -- adding a little bit of post build event script
REM -- just needed changing some lines from ILMerge to LibZ here
set LIBZ=$(SolutionDir)packages\LibZ.Bootstrap.1.1.0.2\tools\libz.exe
%LIBZ% inject-dll -a "$(TargetDir)$(TargetName).dll" -i "$(TargetDir)Core.Models.dll" -i "$(TargetDir)Core.Repositories.dll" -i "$(TargetDir)AdditionalFramework.Entities.dll"
REM -- copy plugins dll to toolkit packaging project
copy "$(TargetDir)$(TargetName).dll" "$(SolutionDir)Core.Plugins\CrmPackage\$(OutDir)"
Last but not the least, CRM custom development frameworks should use late bound types instead of early bound proxy types. Does this scenario make a good case to commend that ?