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 ?

Unexpected triggering of update plugins on Appointment Completion / Cancellation

Synopsis:

In one of my previous posts here: (‘Avoid update plugin steps triggering during Assign request’), i identified a scenario on unexpected continuation of plugins. Continuing on the same course on critical diagnosis of plugins pipeline, I have come across yet another similar situation.

Scenario: Appointment is closed or cancelled by a user. This triggers “SetStateDynamicEntity” message in pipeline on appointment instance. Custom business logic is implemented for plugin(s) on this message. In addition there are plugin(s) registered on appointment-update message. Not a recommended and yet overlooked deployment consideration in plugins design: not deciding correct filtering attributes — so registration is set to “All attributes”.

Problem: Update message plugins on appointment entity are also triggered when user closes the appointment.

Solution: I have implemented, a framework feature in our plugins solution, so after each execution of any plugin, diagnostic details of execution are logged in a custom entity:

  • Full plugin trace (every trace message done using tracingService.Trace(“”)), so developer does not need to throw deliberate exception to see the trace
  • Context received by plugin
  • Error details (if execution failed)
  • Plugin class name
  • Pipeline message and stage
  • Primary & Secondary entity
  • Correlation Id, so one can identify which plugins triggered during one request
  • Source of request (Async, WebService, Application) i.e, CallerOrigin
  • Time taken by each plugin execution
  • Calling user

With these diagnostic details, i have managed to make a number of improvements in a number of existing plugins.

In this specific scenario, i found following context received in update message step (after appointment is closed). For simplicity i have removed the namespaces URIs and qualifiers.

<BusinessUnitId>e603c74c-2a2a-e411-a702-005056b0000b</BusinessUnitId>
<CorrelationId>616b7cc4-236e-4d7b-ae00-f3eb4612335d</CorrelationId>
<Depth>1</Depth>
<InitiatingUserId>66b6698c-41be-e311-9d0e-005056b0000b</InitiatingUserId>
<InputParameters>
   <KeyValuePairOfstringanyType>
      <key>Target</key>
      <value type="Entity">
         <Attributes>
            <KeyValuePairOfstringanyType>
               <key>activityid</key>
               <value>60110d55-bdcc-e311-b40b-005056b0000b</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>statecode</key>
               <value type="OptionSetValue">
                  <Value>1</Value>
               </value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>statuscode</key>
               <value type="OptionSetValue">
                  <Value>3</Value>
               </value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>modifiedon</key>
               <value>2014-09-28T22:27:12Z</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>modifiedby</key>
               <value type="EntityReference">
                  <Id>66b6698c-41be-e311-9d0e-005056b0000b</Id>
                  <LogicalName>systemuser</LogicalName>
                  <Name nil="true"/>
               </value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>modifiedonbehalfby</key>
               <value nil="true"/>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>timezoneruleversionnumber</key>
               <value type="int">0</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>actualdurationminutes</key>
               <value type="int">30</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>actualend</key>
               <value type="dateTime">2014-09-28T23:33:07Z</value>
            </KeyValuePairOfstringanyType>
         </Attributes>
         <EntityState nil="true"/>
         <FormattedValues/>
         <Id>60110d55-bdcc-e311-b40b-005056b0000b</Id>
         <LogicalName>appointment</LogicalName>
         <RelatedEntities/>
      </value>
   </KeyValuePairOfstringanyType>
</InputParameters>
<IsInTransaction>true</IsInTransaction>
<IsOfflinePlayback>false</IsOfflinePlayback>
<IsolationMode>1</IsolationMode>
<MessageName>Update</MessageName>
<Mode>0</Mode>

Besides above, the update message context also received information of parent context i.e, from the “SetStateDynamicEntity” message on appointment.

<Parent>
   <BusinessUnitId>e603c74c-2a2a-e411-a702-005056b0000b</BusinessUnitId>
   <CorrelationId>616b7cc4-236e-4d7b-ae00-f3eb4612335d</CorrelationId>
   <Depth>1</Depth>
   <InitiatingUserId>66b6698c-41be-e311-9d0e-005056b0000b</InitiatingUserId>
   <InputParameters>
      <KeyValuePairOfstringanyType>
         <key>EntityMoniker</key>
         <value type="EntityReference">
            <Id>60110d55-bdcc-e311-b40b-005056b0000b</Id>
            <LogicalName>appointment</LogicalName>
            <Name nil="true"/>
         </value>
      </KeyValuePairOfstringanyType>
      <KeyValuePairOfstringanyType>
            <key>State</key>

            <value type="OptionSetValue">
               <Value>1</Value>
            </value>
      </KeyValuePairOfstringanyType>
      <KeyValuePairOfstringanyType>
         <key>Status</key>
         <value type="OptionSetValue">
            <Value>-1</Value>
         </value>
      </KeyValuePairOfstringanyType>
   </InputParameters>
   <IsExecutingOffline>false</IsExecutingOffline>
   <IsInTransaction>true</IsInTransaction>
   <IsOfflinePlayback>false</IsOfflinePlayback>
   <IsolationMode>1</IsolationMode>
   <MessageName>SetStateDynamicEntity</MessageName>
   <Mode>0</Mode>
</Parent>

The context above showed update steps triggering on “SetStateDynamicEntity” message for appointment entity, which was triggering update message business logic, not needed in this case. Removing following filtering attributes from update step did the trick:

  • activityid
  • statecode
  • statuscode
  • modifiedon
  • modifiedby
  • modifiedonbehalfby
  • timezoneruleversionnumber
  • actualdurationminutes
  • actualend

Though the last two attributes could still be needed in filtering attributes, as per designed business logic.

Conclusion: Always carefully decide filtering attributes as per the business logic implemented in plugin. This greatly helps reducing overall system complexity.

Plugins chain and usage of SYSTEM context

Synopsis:

Plugins provide a typical solution to implement point to point integration in sending information out of CRM. However, as the scope of integration requirements expands, both the number of plugins and complexity increase alongside. A number of design considerations come into play for example synchronous vs asynchronous, controlling cross system loops (ping-pong), pre vs post operation and calling user’s impersonation in plugin execution context.

Working on a system redesign, with a number plugins and workflows in existing system, I came across some interesting scenarios in the plugin execution pipeline. One of those i shared in my previous post ‘Avoid update plugin steps triggering during Assign request’. In this post I am sharing a few caveats in plugins implementation as the execution chain extends in a point to point integration scenario

Scenario: In a web request received from a LOB system, account and some related entities records are created in CRM. On creation of related entities’ records, specific business rules trigger, implemented as plugins. Account plugin is registered to execute in Post Create under calling user’s context. Related entities’ plugins are also registered to execute in calling user’s context on specific messages. In this implementation system user entity is customized to specify attributes / relations to support business logic for ownership of records by different teams across org hierarchy.

Problem: As all plugins are either in pre or post operation stages (within transaction), exception thrown from any will rollback the transaction (initiated on web request). This is expected and accepted behavior, however in some situations plugin (on related entities) fails to find data in user’s custom attributes / relations even though plugin is registered to execute in calling user context.

Solution: Look at the following code in account plugin, that is point of entry to execution pipeline. So account is createdby the user that initiated the request

IOrganizationService service = ServiceFactory.CreateOrganizationService(null) 
service.Create(new new_RelatedCustom{ new_name = "Other" });

Based on this create request, any pre & post create plugins on new_RelatedCustom entity will receive following as part of context:

context.InitiatingUserId = <<Id of internal SYSTEM user>> //because in account plugin the entity is created with System context
context.UserId = <<Id of interenal SYSTEM user>> //this is because the custom entity plugin is registered to run calling user's context, which in this case is also SYSTEM user

Following sequence explains the scenario better:
Plugin Chain and SYSTEM context

The problem is that the subsequent plugin lost track of who actually initiated the plugin on account entity. As SYSTEM user is by default disabled that cannot be enabled and cannot be updated, so any queries using context.InitiatingUserId or context.UserId do not give desired results when joined with this user. This scenario was overlooked at the time of writing plugin on related entity, while relying on context’s Depth property as exit criteria, which is still not good enough. Though it is much required to use SYSTEM context in plugins it makes significant impact on subsequent plugins triggering in the same request due to CRUD (performed by one of the plugins in chain) using SYSTEM context.   SharedVariables provide a solution to pass the initiating user in context, however one has to play it safe using CorrelationId. I used the code as following to resolve request initiating user from context, in cases also using parent context:

private Guid GetRequestInitiatingUser()
{
	Guid initUser = Guid.Empty;
	User SYSTEM_USER = _DataContextAsSystem.UserSet.FirstOrDefault(user => user.FullName == "SYSTEM" && user.IsDisabled == true && user.DomainName == string.Empty);
	TracingService.Trace("ExecutionContext.InitiatingUserId: "
						+ ExecutionContext.InitiatingUserId
						+ Environment.NewLine
						+ "ExecutionContext.UserId: "
						+ ExecutionContext.UserId
						+ Environment.NewLine
						+ "SYSTEMUSER.Id: "
						+ SYSTEM_USER.Id);
	if (ExecutionContext.InitiatingUserId == SYSTEM_USER.Id && ExecutionContext.UserId == SYSTEM_USER.Id)
	{
		TracingService.Trace("Both TRUE ExecutionContext.InitiatingUserId == SYSTEM_USER.Id && ExecutionContext.UserId == SYSTEM_USER.Id");
	}
	string key = ExecutionContext.CorrelationId + "-InitBy";
	if (!ExecutionContext.SharedVariables.ContainsKey(key))
	{
		if (ExecutionContext.InitiatingUserId != SYSTEM_USER.Id)
		{
			TracingService.Trace("Not found SharedVariable: '" + key + "' in context && ExecutionContext.InitiatingUserId != SYSTEM_USER.Id"
								+ Environment.NewLine
								+ "Setting to ExecutionContext.InitiatingUserId: " + ExecutionContext.InitiatingUserId);
			ExecutionContext.SharedVariables.Add(key, ExecutionContext.InitiatingUserId);
			initUser = ExecutionContext.InitiatingUserId;
			return initUser;
		}
		TracingService.Trace("Not found SharedVariable: '" + key
							+ "' in current context && ExecutionContext.InitiatingUserId == SYSTEM_USER.Id, so looking into parent context");
		IPluginExecutionContext parentContext = ExecutionContext.ParentContext;
		if (parentContext == null)
		{
			//TODO: Case when there is no parent context and current context's InitiatingUserId is same as SYSTEM_USER.Id
			//So not adding to sharedvariable deliberately
			TracingService.Trace("parentContext == null, adding SharedVariable: '" + key + "' using InitiatingUserId from current context, value: " + ExecutionContext.InitiatingUserId);
			initUser = ExecutionContext.InitiatingUserId;
			return initUser;
		}
		while (parentContext != null)
		{
			if (parentContext.SharedVariables.ContainsKey(key))
			{
				initUser = (Guid)parentContext.SharedVariables[key];
				TracingService.Trace("Found a parentcontext with SharedVariable: '" + key + "', Value: " + initUser);
				ExecutionContext.SharedVariables.Add(key, initUser);
				return initUser;
			}
			parentContext = parentContext.ParentContext;
		}
	}
	//TODO: case when no parent context had the shared variable, should this ever happen ? 
	//howver not adding to shared variable
	if (!ExecutionContext.SharedVariables.ContainsKey(key))
	{
		initUser = ExecutionContext.InitiatingUserId;
		return initUser;
	}
	initUser = (Guid)ExecutionContext.SharedVariables[key];
	TracingService.Trace("Found SharedVariable: '" + key + "', Value: " + initUser);
	return initUser;
}

The code above, looks for a shared variable in current context, specific to correlation id. It tries to figure out initiating user other than SYSTEM user. However shared variables are scoped by the combination of entity & operation stage within same request (same correlation id), so it is needed to skim through plugin execution’s parent context that might have the shared variable.

Note: i am still testing this code however it has helped significantly in spotting few intermittent exceptions occurrences in an a point-to-point integration of LOB system with CRM.

Some Conclusions: 

  1. Make a careful decision when to use SYSTEM context in plugins and workflow activities
  2. Though CRM provides Depth property in context, it alone does not give a good enough reason when to exit from a plugin.
  3. Carefully decide owner of workflows, which fall under the umbrella of such scenarios, as the owning user of workflow translates into initiating user in plugins triggered from actions performed in respective workflow.
  4. Where it is absolutely sure that a plugin should use a specific user’ identity set that user in plugin step registration and use context.UserId property in plugin code to get the user.
  5. Avoid “Golden Hammer” plugins i.e, limit the scope of plugins by business actions, entity and message to handle.
  6. Within the budget constraints of an implementation, it does make sense to design point-to-point integration, however allocate and spend reasonable time to design it good enough to avoid cross system loops.