Rise to distinction

Time & tide wait for none

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 ?

6 responses to “Generate smaller CRM plugins dll using LibZ than ILMerge

  1. Claus Appel November 5, 2014 at 1:34 pm

    Thanks for the article. It is unclear to me what you mean by the last paragraph where you say: “Last but not the least, CRM custom development frameworks should use late bound types instead of early bound proxy types.”

    What happened to the error you were getting (“A proxy type with the name account has been defined by multiple types”)? Did you get rid of that error by using LibZ instead of ILMerge? Or do you mean that the error was still there even when using LibZ instead of ILMerge, and that you had to avoid using proxy types entirely in order to solve the error?

    • Ameed November 5, 2014 at 1:38 pm

      Hi Claus,

      Thanks for comment. The reason i say that in the end is the framework developer will never know where and how it is used. So if the proxy classes are exposed through framework also, then there are more chances of ambiguity than not having those.

      I got rid of the ambiguity using LibZ, but i just have to specify, the assembly where proxy classes are defined, in proxy behavior.

      Hope this answers your question ?

  2. Pingback: Resolve ambiguous proxy in early bound types in CRM 2011 | Rise to distinction

  3. Ameed February 6, 2015 at 3:10 pm

    I have now been using this approach with significant success in last 4-5 months for our implementation. However recently i faced a problem :

    Scenario:
    1. Added new custom fields to Account entity
    2. Regenerated proxy types in Core.Model.Entities
    3. Used get/set properties of new fields in Core.Plugins.dll
    4. Rebuilt and redeployed output assembly (Core.Plugins.dll)
    5. Plugin failed while throwing “System.MissingMethodException: Method not found: ‘System.String Core.Models.Entities.Account.get_new_customfield()’

    Observations:
    – Did not occur in Dev, single server deployment for Application
    – Occurred in QA, multiple servers deployment with separate backend and frontend roles. load balanced frontends.
    The output assembly generated by LibZ holds the constituent assemblies as compressed embedded resources. I suspect the issue occurred due to one of CRM servers (hosting deployment service or unzip service) cached the Embedded resource (resource corresponding to Core.Model.Entities.dll). For now it resolved by iisreset on server, but i believe assembly version change can resolve this issue in future.

  4. Mina January 16, 2017 at 10:03 am

    Hi Ameed, did you manage to debug the generated assembly ?

    • Ameed January 18, 2017 at 1:59 pm

      Hi Mina, good question, but no. I hardly ever need to debug my plugins, as i use ways to test the logic that i build in plugins. But i can assume, this kind of packaging wont work with debugging plugins 😦

Leave a comment