Rise to distinction

Time & tide wait for none

Category Archives: Strategic needs for CRM

Search shared data across CRM entities

In one of recent project requirements, I implemented a solution to share Personal Email Templates. Requirement was to allow super users to share specific templates with users/teams in readonly or write permissions. Though language filter provides a way to limit templates for specific users, it is still possible for users to see organization visible templates. What we needed was to have personal templates that could be further shared, specially when defined by users, in higher businessunits, that shall be visible for usage to users below.

CRM SDK allows templates to be owned by users / teams, and / or can also be shared, however the templates lists / views interface is not as intuitive as compared to commonly used entities. My solution comprised of a CRM dialog combining with workflow activity code, to implement very specific business logic as per requirements. Implementing this solution was not a big trick, however the more surprising part came later, when it was time to verify whether the specific templates were shared with required users. One way was to login in multiple browsers with different users and other was to somehow query CRM. Honestly, i did not like either of those approaches. So i looked into our favorite: XrmToolBox. There is a nice plugin named “Access Checker”, but I still could not see a list of all records shared with with users & teams. And there I got the idea “why not build one such plugin myself”. So folks, I wrote my first plugin for XrmToolBox, (hopefully not the last).

You can find it here:

Search shared CRM entity records (data across PrincipalObjectAccess)

I am using POA (PrincipalObjectAccess) entity here, but of course there is no direct querying of CRM database to read from this table. If you are not aware of POA => It is one central entity / table where CRM maintains which entity’s which record is shared with which user / team in which access mode.

In order to access this plugin, make sure you are using latest version of XrmToolBox (i am using 1.2018.10.29). In Plugin Store, look for “Search data in PrincipalObjectAccess (POA)”. Select the plugin and install

Search Shared Data in CRM Plugin in Plugin Store

Once installed, the plugin shall show (as below) in the main screen of XrmToolBox.Search POA Landscape

Open the plugin and explore, i hope you will find it interesting and helpful.

Search POA Landscape Plugin in Usage

Note: once i started working on this plugin, I thought of a number of additional features to implement in this interface. I will keep working on improvements, however your suggestions / comments / critics will be extremely helpful in improving this

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.

Why and what pushes the requirements for effective CRM

For any enterprise with multiple lines of business, cross marketing and sales is a key profitability milestone. This demands robust integration needs among diverse line of business systems. No organization starts as an enterprise from the first day in business, however gradually grows into one. This expansion in lines of business brings disparate IT tools & systems in the same organization. Though the working processes and methodologies adopt the changes, value added information integration needs are usually not realized in optimal time. In a business driven IT strategy, due vigilance and vision is required by the officials in strategic roles to anticipate and plan such integration needs well in time. This planning requires collection of case studies from other organizations, and matching similarities against growth trends within own organization. As key requirement in strategic integration often organizations find essential need to implement central CRM system that can serve diverse line of business systems with the most up-to-date customer information, interests, trends and interactions.