XrmServiceToolkit (2.0.0) JQueryXrmCustomFilterView in IE10 / IE11 / jQuery 1.7.2

Working with IE 10 or 11, we had the problem at work that we were unable to use the JQueryXrmCustomFilterView due to exceptions being thrown.

We have jQuery 1.7.2 as is stated as the lowest required jQuery version in the XrmServiceToolkit.

When inspecting the XrmServiceToolkit, I’ve found a bug in the function “xmlToString(xmlData)” (line 3624):

[code language=”js”]
//IE
if (window.ActiveXObject) {
xmlString = xmlData[0].xml;
}
[/code]

the correct code should be:

[code language=”js”]
//IE
if (window.ActiveXObject && typeof xmlData[0].xml !== "undefined") {
xmlString = xmlData[0].xml;
}
[/code]

Geplaatst in CRM 2011, CRM 2013, Exceptions and errors | Een reactie plaatsen

Unable to filter custom SSRS Reports

A colleague of mine approached me today about record filtering in custom SSRS Reports. According to the MSDN link at the bottom of this post, using the CRMAF_ prefix enables automatic filtering based on the user’s record selection in Dynamics CRM when running the report. However, we were unable to produce the correct filtering on the report: it would always revert to the default 30-day filter.

After digging in, we found the problem: the first time you upload the report, if you did not use the CRMAF_ prefix, it will not apply any filtering even if you update the report with an new RDL file. You will have to delete your report from Dynamics CRM, and create a new report.

This link is really usefull when creating SSRS reports:

Create Reports for Microsoft Dynamics CRM Using SQL Server Reporting Services

Geplaatst in CRM 2011, CRM 2013, Exceptions and errors, Technical, Usefull links | Een reactie plaatsen

Assignrequest: Unsupported entity type while converting to security principal

The other day at work, I encountered the following error message, one for which I could not find a solution online. (Unfortunately, some of the text is in Dutch. However, the error message is in English and the error code is universal!)

Error detail: Unsupported entity type while converting to security principal
Error code: -2147220891
Error:

[csharp]
Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: Er is een onverwachte fout opgetreden, probeer het later opnieuw. Neem contact op met uw systeembeheerder indien dit zich vaker voordoet. Foutinformatie: Unsupported entity type while converting to security principal
Detail:
-2147220891
OperationStatus
0

Er is een onverwachte fout opgetreden, probeer het later opnieuw. Neem contact op met uw systeembeheerder indien dit zich vaker voordoet.
Foutinformatie: Unsupported entity type while converting to security principal

[(..details left out for customer discretion..)]
[5f789a7e-1037-e311-b1ea-005056b615c4: PostAccountUpdate]
Now running under user id 67e96c02-99d1-e211-b13f-005056b615c4., Correlation Id: 258be446-76fc-423a-bc8b-f8f3587ed188, Initiating User: 67e96c02-99d1-e211-b13f-005056b615c4
Entered (..details left out for customer discretion..).Execute(), Correlation Id: 258be446-76fc-423a-bc8b-f8f3587ed188, Initiating User: 67e96c02-99d1-e211-b13f-005056b615c4
(..details left out for customer discretion..) is firing for Entity: account, Message: Update, Correlation Id: 258be446-76fc-423a-bc8b-f8f3587ed188, Initiating User: 67e96c02-99d1-e211-b13f-005056b615c4
<strong><span style="text-decoration: underline;">Fatal Error: </span><span style="text-decoration: underline;">Unsupported entity type while converting to security principal</span></strong>
Server stack trace:
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at Microsoft.Crm.Sandbox.SandboxOrganizationService.Execute(String operation, Byte[] serializedRequest)
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg, Int32 methodPtr, Boolean fExecuteInContext)
Exception rethrown at [1]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at Microsoft.Crm.Sandbox.ISandboxOrganizationService.Execute(String operation, Byte[] serializedRequest)
at Microsoft.Crm.Sandbox.SandboxOrganizationServiceWrapper.ExecuteInternal(OrganizationRequest request)
at (..details left out for customer discretion..)
at (..details left out for customer discretion..)
Exiting (..details left out for customer discretion..).Execute(), Correlation Id: 258be446-76fc-423a-bc8b-f8f3587ed188, Initiating User: 67e96c02-99d1-e211-b13f-005056b615c4

[/csharp]

The lines of code that caused this error were:

[csharp]
var assignRequest = new AssignRequest()
{
Assignee = team,
Target = account
};

organisationService.Execute(assignRequest);
[/csharp]

Closer inspection of the assignment of the team variable (which in code is assigned to the Assignee property) showed that it was not an EntityReference to a record of entity type SystemUser or entity type Team.

The Assignee must always be an EntityReference to a record of entity type SystemUser or Team, or you will encounter the exception as shown in this post.

Geplaatst in Exceptions and errors | Getagged , , | Een reactie plaatsen

Win or Lose opportunity from code

Today, I needed to close an opportunity from a plug-in without having an early bound context. The following code sample shows a function to generate an OpportunityClose Entity:

[csharp]

public static Entity CreateOpportunityClose(Guid opportunityId, string subject)
{
var opportunityClose = new Entity("opportunityclose");
opportunityClose.Attributes.Add("opportunityid", new EntityReference("opportunity", opportunityId));
opportunityClose.Attributes.Add("subject", subject);
return opportunityClose;
}

[/csharp]

Using this function, we can generate either a WinOpportunityRequest or a LoseOpportunityRequest.

Won:

[csharp]
var opportunityWinRequest = new WinOpportunityRequest
{
OpportunityClose = CreateOpportunityClose(opportunity.Id, "Reason for this win"),
    Status = new OptionSetValue(3)  // Status won
};
[/csharp]

Lost:

[csharp]
var opportunityLoseRequest = new LoseOpportunityRequest
{
    OpportunityClose = CreateOpportunityClose(opportunity.Id, "Reason for this loss."),
    Status = new OptionSetValue(4)  // cancelled (or your own status)
};
[/csharp]

Finally, you will have to execute the request using the IOrganizationService.Execute Method.

Geplaatst in Technical, Tutorial | Getagged , , , , , | Een reactie plaatsen

Unable to create workflow process due to Workflow Activity (error code -2147220970)

Today, at our customer’s site, I needed to create a Workflow Process. When I first created the Workflow Process, it was successfully created and the included into the solution. However, when I wanted to edit the Workflow Process, I encountered an error. Since the development machine is shared across multiple Organizations, at first I thought this could be due to somebody importing a new solution or creating a new Organization.

I deleted the Workflow Process and tried to recreate it. However, this time even the creation of the workflow failed. CRM 2011 threw me the following exception: System.Xaml.XamlObjectReaderException

Unhandled Exception:
System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault,
Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35]]: System.Xaml.XamlObjectReaderException:
Microsoft Dynamics CRM has experienced an error. Reference number for
administrators or support: <a href="javascript:void(0)">#B3BF2554Detail</a>:

<OrganizationServiceFault xmlns:i="<a href="http://www.w3.org/2001/XMLSchema-instance&quot;">(link)</a> xmlns="<a href="http://schemas.microsoft.com/xrm/2011/Contracts&quot;>">(link)</a>
<ErrorCode>-2147220970</ErrorCode>
<ErrorDetails
xmlns:d2p1="<a href="http://schemas.datacontract.org/2004/07/System.Collections.Generic&quot;">(link)</a>
/>
<Message>System.Xaml.XamlObjectReaderException: Microsoft
Dynamics CRM has experienced an error. Reference number for administrators or
support: <a href="javascript:void(0)">#B3BF2554</a></Message>
<Timestamp>2013-09-04T11:56:32.1920606Z</Timestamp>
<InnerFault>
<ErrorCode>-2147220970</ErrorCode>
<ErrorDetails
xmlns:d3p1="<a href="http://schemas.datacontract.org/2004/07/System.Collections.Generic&quot;">(link)</a>
/>
<Message>System.TypeLoadException: Microsoft Dynamics CRM has
experienced an error. Reference number for administrators or support: <a href="javascript:void(0)">#C9C21CAC</a></Message>
<Timestamp>2013-09-04T11:56:32.1920606Z</Timestamp>
<InnerFault
i:nil="true" />
<TraceText i:nil="true"
/>
</InnerFault>
<TraceText i:nil="true"
/>
</OrganizationServiceFault>

After some research on the web, I was hinted by the error code that this exception could be caused by a Workflow Activity programmed for CRM 4.0 and deployed to CRM 2011. I looked at the most recently added asset that was just deployed to our server. This asset indeed had a Workflow Activity as part of the solution’s deployment project that was not adapted to CRM 2011. After removing this Workflow Activity from CRM using the Plug-in Registration Tool, CRM 2011 operated normally again.

Geplaatst in Exceptions and errors | Getagged , , , , | Een reactie plaatsen

Programatically Updating the Secure Configuration of a Plug-in Step

When writing a plug-in for CRM 2011 you have the ability to use two strings passed to the constructor of your plug-in, the Unsecure Configuration string and the Secure Configuration string. These configuration strings are stored in the message processing step that are registered to your plug-in. You could use these configuration strings to store data that is not to be hard-coded into your plug-in.

The differences between the Unsecure Configuration and the Secure Configuration are:

  1. The Unsecure Configuration information could be read by any user in CRM whereas the Secure Configuration information could be read only by CRM Administrators.
  2. When exporting a solution, the Unsecure Configuration information on an activated step that is included in that solution will be exported, whereas the Secure Configuration information will not be included in that solution.[1]

The difference described in (2) is logical: if it would be included in the exported solution, anyone getting their hands on the solution file could extract the Secure Configuration from the solution file. However, when deploying your solution in a DTAP street, this is a pain since you’re missing your Secure Configuration on your plug-in steps after deployment of the solution.

If you do not have a lot of plug-in steps with a Secure Configuration, you could manually update your Secure Configuration using the Plug-in Registration Tool provided in the CRM 2011 SDK. I was working on a project that had 240+ registered steps which all shared a single Secure Configuration. Since we deployed our solution to our Test environment on a daily base, manually updating the Secure Configuration was not a viable option.

Described here is a simple console application that reads a Secure Configuration text from a text file and update the required plug-in steps with this text. You could easily transform this logic into a WPF or Windows Forms application if you need something fancier!

First, create a new Console Application:New projectSecond, add to following references to your project:

  1. microsoft.xrm.client
  2. microsoft.xrm.sdk

Third, add a CRM Connection String to your App.config

In order to connect to CRM, we use a connection string in the configuration file. Open your App.config, and add the

[xml]
<connectionStrings>
<add name="MyCRMConnection" connectionString="Authentication Type=Integrated; Server=http://192.168.1.12:5555/MyOrganization"/>
</connectionStrings>
[/xml]

section including the connection string to your CRM Server:

connectionstringFor more information on the connection string to connect to CRM, see MSDN article: Simplified Connection to Microsoft Dynamics CRM

Fourth, read the Secure Configuration from a file

In order to enter the Secure Configuration, we add a text file to our project named secureconfiguration.txt:

configuration fileMake sure to set the Copy to Output Directory to Copy always, so the file will be deployed on building your project.

Now open Program.cs, find the Main function:

[csharp]
/// <summary>
/// The program’s main function.
/// </summary>
/// <param name="args">The program arguments</param>
public static void Main(string[] args)
{
[/csharp]

then add:

[csharp]
Console.WriteLine("Starting update of Secure Configurations");
var secureConfig = string.Empty;
using (var fileStream = File.OpenRead("secureconfiguration.txt"))
{
using (var streamReader = new StreamReader(fileStream))
{
secureConfig = streamReader.ReadToEnd();
Console.WriteLine("Configuration loaded:\r\n{0}\r\n", secureConfig);
}
}
[/csharp]

Fifth, connect to CRM

Next, we need to connect to CRM using the connection string:

[csharp]

// connect to CRM
var connection = new CrmConnection("MyCRMConnection");
Console.WriteLine("Connecting to server {0}", connection.ServiceUri);
using (var serviceContext = new CrmOrganizationServiceContext(connection))
{

[/csharp]

Sixth, filter which plug-ins to add a secure configuration to

You need a way to filter CRM’s sdkmessageprocessingsteps because CRM has a lot of it’s own and you possibly have built other plug-ins. Say now, we have a Plug-In Type with the following properties:

  • Name: My.Name.Space.PluginName
  • FriendlyName: MyPlugIn
  • Assembly name: MyPlugInAssembly

and all of it’s registered steps need a Secure Configuration set to the value of the contents of our secureconfiguration.txt file.

In order to filter the plug-in steps we retrieve, we only look for plug-in steps that are registered to the plug-in defined above. We retrieve the id of this Plug-In Type using the following code (note that you can use either the name or the friendlyname property):

[csharp]
// Define plug-in type properties to retrieve correct plug-in type:
var pluginTypeName = "My.Name.Space.PluginName";
var assemblyname = "MyPlugInAssembly";

// Create the QueryExpression object to retrieve plug-in type
var query = new QueryExpression();
query.EntityName = "plugintype";
query.Criteria.AddCondition("name", ConditionOperator.Equal, pluginTypeName);
query.Criteria.AddCondition("assemblyname", ConditionOperator.Equal, assemblyname);
var retrievedPluginTypes = serviceContext.RetrieveMultiple(query);
if (retrievedPluginTypes.Entities.Count != 1)
{
Console.WriteLine("Error. Plug-in type was not found! Assembly: [{0}], Plug-in Type Name [{1}]", assemblyname, pluginTypeName);
Console.ReadKey();
return;
}

var pluginTypeId = (Guid)retrievedPluginTypes.Entities[0].Attributes["plugintypeid"];
Console.WriteLine("Retrieved plugin with Id = {0}", pluginTypeId);

[/csharp]

Seventh, find the plug-in steps associated with the previously found Plug-in Type

The following query will retrieve all the Plug-In Steps registered to the Plug-in Type.

[csharp]

// Create the QueryExpression object to retrieve your steps.
query = new QueryExpression();

// Set the properties of the QueryExpression object.
query.EntityName = "sdkmessageprocessingstep";
query.ColumnSet = new ColumnSet(new[] { "sdkmessageprocessingstepid", "sdkmessageprocessingstepsecureconfigid", "plugintypeid" });
query.Criteria.AddCondition(new ConditionExpression("plugintypeid", ConditionOperator.Equal, pluginTypeId));
var retrievedSteps = serviceContext.RetrieveMultiple(query);

[/csharp]

Last but not least, we iterate over all Plug-In Steps

Iterating over all Plug-In Steps, we either set the existing Secure Configuration to a new value or create a new Secure Configuration entity if one did not exist yet.

[csharp]
// Update all individual steps
foreach (var step in retrievedSteps.Entities)
{
// Test if plugin already has a Secure Configuration:
if (!step.Attributes.Contains("sdkmessageprocessingstepsecureconfigid"))
{
Console.WriteLine("Plugin has no Secure Configuration yet, so create one.");

// create new secure configuration record
var processingStepSecureConfiguration = new Entity("sdkmessageprocessingstepsecureconfig");
processingStepSecureConfiguration.Attributes.Add("secureconfig", secureConfig);
processingStepSecureConfiguration.Id = serviceContext.Create(processingStepSecureConfiguration);

// now attach secure configuration record to processing step
step.Attributes["sdkmessageprocessingstepsecureconfigid"] = processingStepSecureConfiguration.ToEntityReference();
serviceContext.Update(step);
}
else
{
Console.WriteLine("Plug-in step already has a Secure Configuration entity, so retrieve that entity and update the configuration.");

// Retrieve and update secure configuration record
var secureConfigurationReference = step.Attributes["sdkmessageprocessingstepsecureconfigid"] as EntityReference;
var secureConfiguration = serviceContext.Retrieve(secureConfigurationReference.LogicalName, secureConfigurationReference.Id, new ColumnSet(new[] { "sdkmessageprocessingstepsecureconfigid", "secureconfig" }));
secureConfiguration.Attributes["secureconfig"] = secureConfig;
serviceContext.Update(secureConfiguration);
}
}

[/csharp]

And we will close our console application with a friendly message 🙂

[csharp]
Console.WriteLine("… Secure Configuration succesfully uploaded to the steps.");
Console.WriteLine(string.Empty);
Console.WriteLine("All done. Press a key to continue.");
Console.ReadKey();
[/csharp]

And that’s it!

One final note: the secure configuration string is not passed to plug-ins running in the CRM Outlook Client!

Here is the complete code for Program.cs:

[csharp]
// ———————————————————————-
// <author>Tomas Schulkes</author>
// ————————————————————————
namespace PluginSecureConfigUpdater
{
using System;
using System.IO;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

/// <summary>
/// Main program
/// </summary>
public class Program
{
/// <summary>
/// The program’s main function.
/// </summary>
/// <param name="args">The program arguments</param>
public static void Main(string[] args)
{
Console.WriteLine("Starting update of Secure Configurations");
var secureConfig = string.Empty;
using (var fileStream = File.OpenRead("secureconfiguration.txt"))
{
using (var streamReader = new StreamReader(fileStream))
{
secureConfig = streamReader.ReadToEnd();
Console.WriteLine("Configuration loaded:\r\n{0}\r\n", secureConfig);
}
}

// connect to CRM
var connection = new CrmConnection("MyCRMConnection");
Console.WriteLine("Connecting to server {0}", connection.ServiceUri);
using (var serviceContext = new CrmOrganizationServiceContext(connection))
{
Console.WriteLine("Retrieving message steps…");

// Define plug-in type properties to retrieve correct plug-in type:
var pluginTypeName = "My.Name.Space.PluginName";
var assemblyname = "MyPlugInAssembly";

// Create the QueryExpression object to retrieve plug-in type
var query = new QueryExpression();
query.EntityName = "plugintype";
query.Criteria.AddCondition("name", ConditionOperator.Equal, pluginTypeName);
query.Criteria.AddCondition("assemblyname", ConditionOperator.Equal, assemblyname);
var retrievedPluginTypes = serviceContext.RetrieveMultiple(query);
if (retrievedPluginTypes.Entities.Count != 1)
{
Console.WriteLine("Error. Plug-in type was not found! Assembly: [{0}], Plug-in Type Name [{1}]", assemblyname, pluginTypeName);
Console.ReadKey();
return;
}

var pluginTypeId = (Guid)retrievedPluginTypes.Entities[0].Attributes["plugintypeid"];
Console.WriteLine("Retrieved plugin with Id = {0}", pluginTypeId);

// Create the QueryExpression object to retrieve your steps.
query = new QueryExpression();

// Set the properties of the QueryExpression object.
query.EntityName = "sdkmessageprocessingstep";
query.ColumnSet = new ColumnSet(new[] { "sdkmessageprocessingstepid", "sdkmessageprocessingstepsecureconfigid", "plugintypeid" });
query.Criteria.AddCondition(new ConditionExpression("plugintypeid", ConditionOperator.Equal, pluginTypeId));
var retrievedSteps = serviceContext.RetrieveMultiple(query);

// Update all individual steps
foreach (var step in retrievedSteps.Entities)
{
// Test if plugin already has a Secure Configuration:
if (!step.Attributes.Contains("sdkmessageprocessingstepsecureconfigid"))
{
Console.WriteLine("Plugin has no Secure Configuration yet, so create one.");

// create new secure configuration record
var processingStepSecureConfiguration = new Entity("sdkmessageprocessingstepsecureconfig");
processingStepSecureConfiguration.Attributes.Add("secureconfig", secureConfig);
processingStepSecureConfiguration.Id = serviceContext.Create(processingStepSecureConfiguration);

// now attach secure configuration record to processing step
step.Attributes["sdkmessageprocessingstepsecureconfigid"] = processingStepSecureConfiguration.ToEntityReference();
serviceContext.Update(step);
}
else
{
Console.WriteLine("Plug-in step already has a Secure Configuration entity, so retrieve that entity and update the configuration.");

// Retrieve and update secure configuration record
var secureConfigurationReference = step.Attributes["sdkmessageprocessingstepsecureconfigid"] as EntityReference;
var secureConfiguration = serviceContext.Retrieve(secureConfigurationReference.LogicalName, secureConfigurationReference.Id, new ColumnSet(new[] { "sdkmessageprocessingstepsecureconfigid", "secureconfig" }));
secureConfiguration.Attributes["secureconfig"] = secureConfig;
serviceContext.Update(secureConfiguration);
}
}

Console.WriteLine("… Secure Configuration succesfully uploaded to the steps.");
Console.WriteLine(string.Empty);
Console.WriteLine("All done. Press a key to continue.");
Console.ReadKey();
}
}
}
}
[/csharp]

[1] – Difference between Secure / Unsecure Configuration of Plugin Registration tool in CRM 2011

Geplaatst in Technical, Tutorial | Getagged , , , , , , , , | Een reactie plaatsen

Designing Maintainable Workflows in Microsoft Dynamics CRM

Below is an article I’ve written with Sander Bockting, a CRM Service Line colleague at Avanade, about designing maintainable workflows in Microsoft Dynamics CRM.

Designing Maintainable Workflows in Microsoft Dynamics CRM

The powerful and user-friendly workflow solution in Microsoft Dynamics CRM can be leveraged to easily build relatively straightforward business processes. However, when business processes become more complex, the effort required to translate them into a maintainable workflow increases disproportionately. This two-part article provides practical guidelines for translating complex business processes into maintainable, easily testable workflows.

Helping the business get their complex workflows

While working on a large enterprise Dynamics CRM solution, we were handed seven PowerPoint slides containing nearly one hundred human tasks. The tasks were part of two key business processes. Many of the tasks were to be executed in parallel, some were to be executed conditionally, and others functioned as approval tasks – only to be executed once the preceding tasks were canceled or completed. The processes even contained multiple recursions. Of course, each task had it is unique details, such as the owner to whom it should be assigned and due dates that were dependent on fields in CRM.

It quickly became apparent that simply building a large workflow would not result in a maintainable solution, should it even be possible at all. A more structured approach was required that would be resilient to change and where the workflow execution state could be understood at a glance.

In an attempt to make effective use of our computer science background, we decided to take a more academic approach to formalize complex business processes – written by domain experts – into a model decomposition where each model could be implemented using a CRM workflow.  Here we share our methodology and experiences.

Reduced maintainability of larger workflows

Dynamics CRM workflows provide great flexibility and extensibility for automation of system-driven business processes. Conditional checks, waits and branches provide for enough expressiveness to implement most business processes. Custom workflow activities can be leveraged when things get really complicated. These tools are especially applicable to smaller workflows; larger workflows tend to come with challenges in maintainability. Workflows can be considered maintainable when:

  • It is possible to quickly grasp its functional intent
    This becomes increasingly difficult when depth of the workflow steps increase or when there is recursion involved in the workflow steps. Recursion means steps may need to be looped until a certain condition is met.
  • Grasping the current execution state of a workflow (set) does not take much effort
    Dynamics CRM provides out-of-the-box functionality to see the current execution state for one workflow. But this does not help the system administrator in great detail when multiple workflows depend on each other.
  • Small changes do not impact a large portion of the workflow
    Many developers that have built workflows with CRM are familiar with the frustration where you have to delete and rebuild a workflow branch, because the “Insert Before step” did not do the trick in accommodating the required change.
  • Long running and complex (multi-staged) workflows can be changed without disrupting operation
    Without proper planning ahead, it may be necessary to let the old workflow instances finish while the new instances run in parallel. This is not always acceptable for the business.

Approach

To overcome the maintainability issues of larger workflows and to help in the process of implementing complex business processes, we have adopted a six-staged structured methodology:

  1. Capture the desired functionality of the workflows in a model using a functional language that can be easily understood by the customer and the developers.
  2. The functional model is used to break up the monolithic large workflow into smaller and more maintainable workflows.
  3. Create a status record that tracks completion details of the individual workflows. The record shows the total progress of the combined workflow.
  4. Each of those workflows is described in a technical model.
  5. Each technical model allows for translation into an actual CRM workflow.
  6. Create the Starter Workflow to initialize the composite workflow.

A more detailed explanation of each stage is given below.

Stage 1: Design Functional Model

The idea for a workflow frequently arises at non-technical users. Our experience is that these users are perfectly capable of creating a schematic view of their desired business processes. Their schematic views can be used to discuss the flow and act as a starting point for the technical workflow. Among many different customers, we have found that they all came up with comparable notations. We collected them into a uniform set of functional process symbols.

Functional Process Symbols

Functional process symbols describe the business process from a functional point of view. The representation is used as an input for the translation from a business process into a workflow. The symbols should be seen as an accessible notation for the customer to define their processes. Although not obligatory, getting a design based on these symbols may speed up your work in translating it into a technical design.

To increase diagram readability, we chose to let the customer describe details of actions or tasks (for example, start date, priority, subject, etc.) in a separate document. Letters and digits were used as a reference between the two design artifacts.

crm-workflow-functional-symbols

Figure 1: Legend of functional process symbols

Symbol Definition
Start of diagram Indicates start of the business process.
State A state indicates a task or action to perform or finish to be able to take the next transition.
Branch A branch models the paths and their preconditions that could be traversed. Only 1 path can be traversed simultaneously. A branch does not represent an action or task.
Transition A transition models a possible one-way path from one state to another or to a branch. The transition could hold a guard that models the precondition required to traverse the path. For example, the completion of a task, the passing of a certain amount of time, etc.
Parallel activity streams start This one-to-many fork models multiple activity paths that take place in parallel.
Parallel activity streams convergence This many-to-one fork models the convergence of multiple parallel paths. The outgoing path can only be traversed when all parallel paths are finished.

Using Functional Process Symbols to create schematic views

The customer uses the functional process symbols to create schematic views of their business processes. The most common situations and their compositions are provided below.

Situation description Schematic view
Sequential execution of workflow items.  Sequential execution of workflow itemsSequential execution of workflow itemsSequential execution of workflow itemseersteSequential execution of workflow itemsParallel execution of workflow items
Parallel execution of workflow items.  Parallel execution of workflow items
Repeat a workflow item until a condition is met (e.g. ensure that an end-user entered a valid value in a record).  Repeat a workflow item until a condition is met
The first state is a task or an action to be performed by a person. The second tasks is assigned to a supervising entity, and is meant to make a judgment about the outcome of the first state. If undesirable, the state or action is to be performed again (e.g. useful in an approval flow).  Repeat a set of workflow items until a workflow item was completed successfully
Repeat a set of workflow items from the beginning each time the outcome of a task or action was not desirable (e.g. a complex approval flow with multiple approvers).  Repeat a set of workflow items from the beginning each time a task was not completed successfully
Wait until a specific point in time is reached.  Wait until a specific point in time is reached

Stage 2: Box Identification – Identifying child workflows

When a schematic view of the desired business process is complete, the next step is to break up the view into smaller, workable components. The task is to identify the areas that must be modeled as child workflows within the main workflow to encapsulate the entire business process. These areas are called boxes. The procedure of finding them in a business process flow is called box identification. Box identification is characterized by finding the right start and end of a box.  Let’s look at the mechanics of this process in more detail, as well as an example.

Finding the start of a box

A box should have one incoming and one outgoing transition. Breaking up the business process into smaller boxes is done by traversing the business process path from the start. Three situations have been identified as good fits for a new box. While traversing the path:

  • A branch element is encountered.
  • A state is found that has an incoming transition from a state that has not been traversed yet; the transition is a recursive call from a child element.

Finding the end of a box

The end of the box depends on the situation that started the creation of a box.

  1. For a box that was created after encountering a branch element, the end of it is marked by the first transition that is traversed by all possible paths that started after the branch element.
  2. For a box identified by a recursive call, its end is identified recursively:
    1. Include all possible states between the start state of the box and the state making the recursive call to the start state, including the start state and the state making the recursive call.
    2. If this set of states has any state making a recursive call to a state that is not yet in the box, repeat step A for these states, also including them in the box.
    3. Repeat this process until there are no more recursive calls found.

The resulting set of states together form the box.

Box identification example

Box identification exampleFigure 3: An example of box identification that breaks down one large functional process design into smaller boxes. Each box eventually results in a CRM workflow.

Figure 3 shows a large functional process design. The black box covers the entire business process or Complete Workflow; it is the main workflow and will be the first box (1) (see Figure 4). Within the black box, traverse the path until the branch element is found, indicating the need for a new box (2): Subflow A. The end of Subflow A is found by the first single transition going out of the box.

Breakdown into three boxesFigure 4: Breakdown into three boxes

Within Subflow A, observe there is yet another branch element in the business process. The blue box indicates a child workflow (Subflow A’) of Subflow A: box 3. The end of Subflow A’ is also found by identifying the single arrow going out of the box.

Stage 3: Design and Build Status Record Entity

Due to the lack of a native CRM mechanism to have a workflow pause while a child workflow runs, we have devised our own method to track the execution state of a child workflow. We have done this by means of defining a custom entity for the composite workflow, called the status record entity. The idea is to have this status record entity contain a number of datetime fields equal to the number of workflows identified in stage 2 that are to be waited upon. The names of these fields on the status record entity should preferably be descriptive for the child workflow that is to be waited upon for better readability.

After initiating a child workflow, the parent workflow will pause until the status record field corresponding to the child workflow is filled with a datetime.

After execution, the child workflow will set the value of its status record field to the current date and time and then end. This signals the parent workflow to resume duty.

The primary entity of the workflow is linked to the status record by means of a new field preferably hidden from the entities forms. The status record is initiated at the start of the overhead workflow.

It can frequently occur that a parent workflow resume after waiting (because the child workflow has set the date and time in the appropriate field), and see that the result of the child workflow is not satisfactory. When the child workflow is to be run again, the value in the status record field should be emptied before initiating the child workflow.

Note that when a child workflow is a ‘fire and forget’ workflow, it will not need representation in the status record entity.

Stage 4: Create a technical model for each identified box

The previous stages described the identification of boxes that function as separate (child) workflows and the creation of a status record entity that maintains the connection between each of the (child) workflows. Next, each box is translated into a technical model. Just like the Functional Process Symbols are used to create functional models, the Technical Process Symbols are used to create technical blueprints of the workflows.

Technical Process Symbols

Besides functional process symbols – to capture business processes in diagrams interpretable by your customer – a set of technical process symbols is given below. The symbols serve as intermediate language and allow you to translate the functional diagrams into technical diagrams in a more formal way. They provide just the right amount of expressivity to create diagrams for implementable workflows.

Technical Process SymbolsFigure 5: Legend of technical process symbols

Symbol Definition
Workflow start Indicates start of workflow.
Recursive Call Recursive call to current workflow.
Workflow End Indicates end of workflow.
Record Update / Create Operation Models a create operation of a data model record (for example, a Task) or an update operation on a data model record.
Status Record Update Models an update operation on the status record related to the primary entity.
Composite State The Composite state models a child workflow. It is a call to a child workflow.
Branch A branch models the paths and their preconditions that could to be traversed. Only 1 path can be traversed simultaneously. A branch does not represent an action or task.
Guard on workflow records The guard describes the condition that must have been met in order to traverse the path it guards. A guard can consist of multiple conditions joined by logical operators. A guard can also be a mix of workflow record guards and status record guards, however the labels will then be written in blue to show the guard’s dependency on the status record.
Guard on status record field(s) The guard describes the condition on one or multiple fields of the status record that must have been met in order to traverse the path it guards. A guard can consist of multiple conditions joined by logical operators. A guard can also be a mix of workflow record guards and status record guards.
Parallel paths Parallel paths show actions and states that take place in parallel.

Creating technical models using the symbols

For each identified box, a technical model is created using the Technical Process Symbols. An example is given that shows how a parent workflow (Workflow X) is connected to a child workflow (Workflow Y). With the use of a status record, Workflow X will only continue processing once Workflow Y filled the appropriate status record field with a date.

Workflow XFigure 6: Workflow X – it depends on a subworkflow Y that should be finished before it can continue. It waits until the appropriate status record field contains data.

Workflow YFigure 7: Workflow Y stores the current date and time in a status record field when it has successfully reached the end. Workflow X waits until the appropriate status record field contains data.

Stage 5: Translate technical models into CRM workflows using the editor

Translating the technical models created in stage 4 into CRM workflows should be pretty straightforward if a sufficient breakdown of boxes has been made. It is assumed the reader has a good understanding of creating processes in Dynamics CRM 2011. If not, the internet has various tutorials on how to build a workflow.

All child workflows in the composite workflow need to be defined for the same primary entity. When creating the workflows in Dynamics CRM, be sure to uncheck the ‘Start when’ options. Also, make the workflows ‘available to run’ only as ‘child workflow’, unless you are testing or debugging a specific child workflow. In this case, do not forget to manually attach a new Status record. Otherwise you run the risk of either not having a Status record attached yet, or that some datetime fields have already been filled with data.

Translation of the technical symbols is done with the following components.

  1. The ‘recursive call’ symbol is implemented by the Start Child Workflow step, calling itself as a child workflow.
  2. The ‘status record update’ symbol is implemented by the Update Record step.
  3. The ‘record update / create operation’ symbol is implemented by either the Create Record step or the Update Record step.
    1. If the create operation is used and the outgoing transition of this state has a guard, the ‘record create operation’ is followed by a wait step on the guard.
    2. The wait step is omitted in case the outgoing transition of the composite state is connected directly to the end point of a ‘parallel paths’ step.
  4. The ‘composite state’ symbol represents a child workflow and is therefore implemented by a Start Child Workflow step, starting the appropriate child workflow.
    1. If the outgoing transition of this composite state has a guard, the Start Child Workflow step is followed by a wait step on the guard.
    2. This wait step is omitted in case the outgoing transition of the composite state is connected directly to the end point of a ‘parallel paths’ step.
  5. The ‘branch’ symbol is implemented by a Check Condition step.
  6. The ‘guard on workflow records’ and ‘guard on status record field(s)’ symbols are implemented by a Wait Condition step, waiting on the designated field to fulfill the guard defined on the step.
  7. The ‘parallel paths’ symbol is implemented by creating all of the steps for the individual paths and then defined a wait step that guards all of the waiting conditions in each path. In case an individual path contains more than one symbol, it should be boxed into a new workflow as described previously.
  8. The ‘workflow start’ symbol needs no translation.
  9. The ‘workflow end’ symbol is implemented by the Stop Workflow step. If this is the last step of the workflow, it does not need to be created.

Stage 6: Build Workflow Starter

The sixth and final stage is to build the workflow that starts the composite workflow. Now that all of the workflows have been designed and defined, a starter workflow must be constructed. This starter workflow is the workflow that will be defined to run as the initial workflow for the primary entity.

When run, the starter workflow constructs a new instance of the Status Record entity. After creating the new Status record, the Workflow Starter starts the workflow that is defined for the outer most box of the business model. This, in essence, starts the desired workflow.

In our solution, the starter workflow is also used as a prerequisites test. It tests if the conditions are met that allow the workflow to run. Although not required, it could abstract the actual workflow from the logic that checks whether or not the workflow should be triggered to run. One could even imagine defining one starter workflow for starting one or more workflows from a set of possible workflows.

Discussion

The introduced workflow design methodology introduces several major advantages. It allows for special constructs that are not possible with large monolithic workflows such as loops. It also allows for easier maintenance when workflows should be changed. Changes are limited to shorter-running workflows, which allows the support team to replace these workflows by new versions without disrupting the long running process. When making changes to child workflows, it is required to have a thorough understanding of the dependencies between the different child workflows, and this is where the technical designs come in play. If the workflows would not have been split up into smaller components, it would have been a lot more difficult to modify the large workflow and several changes may require the removal of entire branches (e.g. when an extra branch condition should be added before a tree of workflow items). The smaller child workflows also improve testing possibilities because smaller sets of workflows can be tested individually and with less associated test conditions.

Each set of workflows requires a new entity in the form of a workflow status record. Although this implies additional work, the workflow status record allows for direct insight workflow execution progress, removing the need for tooling such as BAM (Business Activity Monitoring) when there are no complex requirements in this respect. And when requirements become more complex, the status record can be leveraged to measure workflow execution durations.

The design methodology lowers the threshold to create complex workflows in Dynamics CRM. However, workflows are still complex so it is wise to be cautious to prevent certain problems:

  • It is a Microsoft best practice to create one single long workflow instead of child workflows called by a parent workflow when it comes to maximum throughput[1]. If workflow definitions do not change frequently and the workflows are not that complex that they should be split up into separate workflows, it is worth considering one single workflow.
  • An important step in the design methodology is decomposing the large functional model into smaller child workflows through the process of box identification. When mistakes are made during this boxing process, it is possible that you end up finding yourself unable to create the actual Dynamics CRM workflows because a recursive call was required at a level where it cannot be implemented through standard Dynamics CRM workflow steps. In that case, all stages starting from stage 2 will have to be redone for the affected workflows.
  • To reduce complexity, this methodology ‘forces’ you to create many child workflows. This could mean that you run into the Maximum Workflow Depth (default set at 8), meaning that your workflows will stop when a chain of child workflows is called. When this occurs, increase the Maximum Workflow Depth[2]. This can be done using PowerShell.

Summary

This article described a structured approach to translate complex business processes into maintainable workflows. Two modelling notations were introduced that can be used to create a functional model and a technical model. A methodology was given to translate the functional model into a technical model. Finally, this technical model is the basis for the actual Dynamics CRM workflow set implementation. The child workflows have dependencies with each other that are managed through a status record. The status record makes it easy to grasp the current execution state of the workflow by looking which child workflow completed last. This setup ensures more flexibility; a business process change likely affects a (small number of) child workflow(s), minimizing the need to modify a long running workflow with the corresponding maintenance support issues.


[1] http://msdn.microsoft.com/en-us/library/gg509027.aspx

[2] http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.remoteexecutioncontext.depth.aspx

Geplaatst in Functional, Technical | Getagged , , , , , | Een reactie plaatsen

Hello CRM World

[csharp]
public static string StartBlog()
{
return "My blog has started!";
}
[/csharp]

Geplaatst in Geen categorie | Een reactie plaatsen