Rise to distinction
Time & tide wait for none
Category Archives: MS Dynamics CRM plugin execution pipeline internals
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 ?
Expected non-empty Guid Error when using OutArgument
Wonder why you have come across following exception in your custom activity execution.
Error:
Workflow paused due to error: Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxxx]]: Expected non-empty Guid.Detail:
-2147220989
Expected non-empty Guid.
2012-07-28T23:38:16.1998292Z
-2147220970
System.ArgumentException: Expected non-empty Guid.
Parameter name: id
2012-07-28T23:38:16.1998292Z
[Microsoft.Xrm.Sdk.Workflow: Microsoft.Xrm.Sdk.Workflow.Activities.RetrieveEntity]
[RetrieveEntity]
Entered FindActiveSubscription.Execute(), Activity Instance Id: 75, Workflow Instance Id: 4824f47e-591a-4393-af02-e3cb9decc427
FindActiveSubscription.Execute(), Correlation Id: 914f2e28-b555-4812-b0f2-a5c515d26f0f, Initiating User: 5ee8d8e7-45b1-e111-9c7f-00155d013820
Exiting FindActiveSubscription.Execute(), Correlation Id: 914f2e28-b555-4812-b0f2-a5c515d26f0f
Synopsis:
While working with workflows, there are situations when one has to retrieve an entity using a custom criteria which is not possible to be built in out-of-box workflows user interface. For example in a most recent situation, i needed to fetch the latest active subscription of a contact based on its membership status. Cutting long story short i used a codeactivity to find the needed instance of subscription entity “new_subscription”, so i defined an OutputArgument
[Output("The related subscription")]
[ReferenceTarget(new_subscription.EntityLogicalName)]
public OutArgument<EntityReference> RelatedSubscription { get; set; }
Problem:
Then in the Execute method one would naturally only set RelatedSubscription if there could be found one such in CRM. However, workflow engine requires this to be set to a non-empty Guid value. Even with the latest rollup it is not able to gracefully handle a nullable situation.
Solution:
Set to a new Guid value as below:
IList<new_subscription> list = <<Whatever fetched from system>>;
if (list.Count > 0)
{
RelatedSubscription.Set(executionContext, new EntityReference(new_subscription.EntityLogicalName, list[0].Id));
}
else
{
RelatedSubscription.Set(executionContext, new EntityReference(new_subscription.EntityLogicalName, Guid.NewGuid()));
}
Then after execution of this activity in workflow, one just needs to enclose the dependent business logic steps in a check condition such as:
Note: Find Active Subscription is the name of step where custom activity is executed
so after that a check condition could look like:
"Find Active Subscription:Related Subscription" "Subscription" "Contains Data"
With this approach i managed to fix all such situations where a null result could be expected from custom code activity.
Hope this helps you too