It probably won’t surprise you to learn that Microsoft Dynamics CRM stores workflow definitions in entities, just like most things in Microsoft Dynamics CRM. Conveniently, this allows developers to access and manipulate workflows through the familiar CrmService API.
The entity that contains most of the workflow definition is appropriately named workflow. Table 12-1 lists the attributes of the workflow entity.
Table 12-1. Attributes of the workflow Entity
name | Type | Description |
---|---|---|
activeworkflowid | Lookup | Only populated if the workflow has a type equal to Definition and a statecode of Published. Contains the workflowid for the compiled workflow that is executed when this workflow is triggered. See "Workflow Publication" later in this chapter for more information. |
activities | String | Contains the declarative workflow definition in Extensible Application Markup Language (XAML). For workflows defined in the CRM workflow designer, this attribute is only populated after a workflow is published and only for the workflows with a type equal to Activation. See "Declarative Workflows" later in this chapter for more information. |
createdby | Lookup | The systemuser that created the workflow. |
createdon | CrmDateTime | The date and time the workflow was created. |
description | String | The description of the workflow definition. |
CrmBoolean | True if the workflow was defined in the Microsoft Dynamics CRM 4.0 workflow designer; otherwise false. | |
modifiedby | Lookup | The systemuser that last modified the workflow definition. |
modifiedon | CrmDateTime | The date and time the workflow was last modified. |
name | String | The name of the workflow. |
ondemand | CrmBoolean | True if this workflow is allowed to be executed on demand by users; otherwise false. |
ownerid | Owner | The owner of the workflow. |
owningbusinessunit | Lookup | The workflow owner’s business unit. |
parentworkflowid | Lookup | If the workflow has a type of Activation, parentworkflowid references the workflow that represents the definition. |
plugintypeid | Lookup | References the plugintype for a compiled workflow. Only populated for workflows with a type of Activation. See "Workflow Publication" later in this chapter for more information. |
primaryentity | EntityNameReference | A reference to the type of the primary entity for the workflow definition. |
rules | String | Contains the declarative workflow’s policy rules in XAML. For workflows defined in the CRM workflow designer, this is only populated after a workflow is published and only for the workflows with a type equal to Activation. See "Declarative Workflows" later in this chapter for more information. |
scope | Picklist | The scope of the event that automatically triggers the workflow. Valid values are exposed as constant integer fields from the WorkflowScope class (User = 1, BusinessUnit = 2, Deep = 3, Global = 4). For example, if scope is set to WorkflowScope.User, the workflow only automatically executes if the primary entity is also owned by the workflow owner. See "Automatic Workflows" in Chapter 6 for more information. |
statecode | WorkflowStateInfo | The state of the workflow. Valid values are exposed as constant integer fields from the WorkflowState class. (Draft = 0, Published = 1). |
statuscode | Status | Additional information about the state of the workflow. Currently, each statecode only has a single valid value for statuscode (Draft = 1, Published = 2). |
subprocess | CrmBoolean | Indicates whether the workflow can be included in other workflows as a child process. |
type | Picklist | Used to determine the type of the workflow. Valid values are exposed as constant integer fields from the WorkflowType class (Definition = 1, Activation = 2, Template = 3). A workflow with type equal to Activation indicates that it has been compiled from a definition during publication. See "Workflow Publication" later in this chapter for more information. |
uidata | String | When a workflow is defined in the Microsoft Dynamics CRM workflow designer (iscrmuiworkflow equals true), uidata contains proprietary information about the configuration of the steps and stages. |
workflowid | Key | The primary key for the workflow. |
If you have read Chapter 6, many of these attributes, such as scope and ondemand, are undoubtedly familiar to you because you worked directly with them in the Microsoft Dynamics CRM workflow designer. Others, such as activities, activeworkflowid, and rules, are not as obvious, and for that reason we examine them more closely later in this chapter.
Because Microsoft Dynamics CRM stores workflows within entities, you can access and manipulate them using the familiar CrmService methods. For example, ProgrammingWith-DynamicsCrm4.WorkflowManager uses the code from Example 12-1 whenever it needs to refresh the list of workflows.
Example 12-1. WorkflowLogic’s RetrieveWorkflows method
public IEnumerable<workflow> RetrieveWorkflows(params string[] attributes) { QueryExpression query = new QueryExpression(); query.EntityName = "workflow"; ColumnSet cols = new ColumnSet(); cols.AddColumns(attributes); query.ColumnSet = cols; query.Criteria.AddCondition( "type", ConditionOperator.Equal, WorkflowType.Definition); BusinessEntityCollection results = this.CrmService.RetrieveMultiple(query); foreach (workflow w in results.BusinessEntities) { yield return w; } }
This query returns all workflow definitions in the system, regardless of their state or owner. We use the results of this method to populate the ListView control in the main form of the ProgrammingWithDynamicsCrm4.workflowManager tool.
In addition to workflow retrieval and deletion, you can use the CrmService API for all of the other manipulations such as updating, deleting, and reassigning workflow entities to new owners. See Chapter 3, for more information on how to use the API to execute this functionality.
Once you begin managing workflows through the API, you will quickly find yourself needing to publish and unpublish workflows programmatically. Example 12-2 demonstrates how easily you can accomplish this with the SetStateworkflowRequest.
Example 12-2. WorkflowLogic’s PublishWorkflow and UnpublishWorkflow methods
const int WorkflowStatusDraft = 1; const int WorkflowStatusPublished = 2; public void PublishWorkflow(Guid workflowId) { SetStateWorkflowRequest publishRequest = new SetStateWorkflowRequest(); publishRequest.EntityId = workflowId; publishRequest.WorkflowState = WorkflowState.Published; publishRequest.WorkflowStatus = WorkflowStatusPublished; this.CrmService.Execute(publishRequest); } public void UnpublishWorkflow(Guid workflowId) { SetStateWorkflowRequest unpublishRequest = new SetStateWorkflowRequest(); unpublishRequest.EntityId = workflowId; unpublishRequest.WorkflowState = WorkflowState.Draft; unpublishRequest.WorkflowStatus = WorkflowStatusDraft; this.CrmService.Execute(unpublishRequest); }
Notice that the WorkflowStatusPublished and WorkflowStatusDraft constants are defined locally in the WorkflowLogic class. Although the CRM SDK provides the WorkflowState class to access the statecode definitions, it does not provide a similar class for statuscode. To make your code more maintainable, it is a good idea to define some constants of your own to represent the values.
Although the two methods in Example 12-2 are fairly straightforward, behind the scenes CRM is doing more than simply changing some attribute values in response to these calls.
Back in Example 12-1, you saw that we added a condition to make sure we only retrieve workflows if the type equals WorkflowType.Definition. Without this condition, the CrmService appears to return duplicate workflow definitions to our ListView. In reality, a compiled copy of a workflow definition is created each time you publish a workflow. The compiled version is marked with type set to WorkflowType.Activation and the activeworkflowid attribute on the definition workflow is updated to reference this compiled copy.
Compilation takes the stages and steps you defined in the workflow designer out of the ui-data attribute and converts them into XAML. We’ll take a closer look at XAML in the section "Declarative Workflows" later in this chapter, but for now just understand that the XAML is XML that you use to describe the stages and steps.
XAML is combined with some generated code and compiled into a class in a new assembly. The new assembly and class are stored in a pluginassembly entity and a plugintype entity respectively. The resulting plugintype is associated with the compiled workflow through the workflow’s plugintypeid attribute.
In addition to compiling the workflow, a series of related entities of type workflowdependency are created during publication. These entities contain additional information about when to execute a workflow instance and what data it requires during execution. Because this is a vital and yet complex part of workflow execution, we discuss the workflowdependency entity in more depth in the next section.
The publication process enables Microsoft Dynamics CRM to streamline the execution of workflow instances by optimizing the workflow into a .NET assembly and preparing additional data to dictate how and when to execute it.
As mentioned in the previous section, a series of workflowdependency entities are created during publication to instruct CRM both when a workflow should be executed and what data it needs during execution. A workflowdependency can be one of eight different types. You can use the type attribute to determine the type of the workflowdependency. The valid values for type can be found as constant integer values in the WorkflowDependencyType class, as shown in Table 12-2.
Table 12-2. WorkflowDependencyType Field Values
Value | Description | |
---|---|---|
Attributedefinition | 8 | Creates a dependency on a custom attribute. A custom attribute cannot be deleted if a published workflow references it with this dependency. |
CustomEntitydefinition | 7 | Creates a dependency on a custom entity. A custom entity cannot be deleted if a published workflow references it with this dependency. |
LocalParameter | 2 | Used to define local parameters that exist as properties on the compiled workflow class. These properties are frequently modified and read by PolicyActivity instances as defined in the workflow’s rules attribute. |
PrimaryEntityImage | 3 | Specifies which attributes of the primary entity should be passed in to the workflow instance. |
PrimaryEntityPostImage | 5 | Specifies which attributes of the primary entity’s post image should be passed in to the workflow instance. |
PrimaryEntityPreImage | 4 | Specifies which attributes of the primary entity’s pre image should be passed in to the workflow instance. |
RelatedEntityImage | 6 | Specifies the attributes needed by this workflow from an entity directly related to the primary entity. |
SdkAssociation | 1 | This dependency is used to define which SDK messages should cause the related workflow to execute. |
With these dependency types in hand, we can take a look at the other attributes exposed from the workflowdependency entity. Table 12-3 lists all of the workflowdependency attributes and their uses.
Table 12-3. The workflowdependency Entity
property | Type | Description |
---|---|---|
createdby | Lookup | The user that created the workflowdependency. |
createdon | CrmDateTime | The date and time the workflowdependency was created. |
customentityname | String | If type is CustomEntitydefinition, this is the name of the custom entity the workflow is dependent upon. |
dependentattribute-name | String | If type is Attributedefinition, dependentattributename is the name of the attribute that is required. |
dependententityname | String | If type is Attributedefinition, dependententityname is the name of the entity that the required attribute must exist on. |
entityattributes | String | The entityattributes property has multiple meanings depending on the type attribute. Regardless of the meaning, it always contains a comma-separated list of attribute names. If type is SdkAssociation and sdkmessageid references the Update message, entityattributes contains the names of the attributes that automatically trigger the workflow. For any of the entity image defining types, entityattributes contains the list of attributes that should be populated in the image. |
modifiedby | Lookup | The user that last modified the workflowdependency. |
modifiedon | CrmDateTime | The date and time the workflowdependency was last modified. |
owningbusinessunit | UniqueIdentifier | The business unit that owns the workflowdependency. |
owninguser | UniqueIdentifier | The user that owns the workflowdependency. |
parametername | String | This identifies the name of a property that will be defined on the compiled workflow instance. These properties can be accessed from rules used by PolicyActivity instances. |
parametertype | String | Only populated when type is set to LocalParameter, parametertype contains the name of the .NET type for the local property that is defined on the workflow. |
relatedattributename | String | When type is set to RelatedEntityImage, the relatedattributename property is set to the name of the attribute on the primary entity that contains the related entity. |
relatedentityname | String | When type is set to RelatedEntityImage, the relatedentityname property is set to the name of the related entity type. |
sdkmessageid | Lookup | When type is set to SdkAssociation, sdkmessageid is the related message that automatically triggers the workflow execution. |
type | Picklist | Determines the type of the workflowdependency. See Table 12-2 for a list of possible values. |
workflowdependencyid | Key | The primary ID for the workflowdependency. |
workflowid | Lookup | The workflow this dependency is associated with. |
Workflow dependencies play an important but subtle role in the Microsoft Dynamics CRM workflow processing. To help understand the complexities of workflow dependencies, we will create a simple workflow through the native workflow designer and then examine the workflow dependencies generated as a result.
Examining workflow dependencies created by the native workflow designer
Using the native workflow designer, create a workflow named Dependency Test that automatically executes when an opportunity is created. (Refer to Chapter 6 if you need a refresher on using the native workflow designer.)
Set the workflow’s scope to Organization.
Add a Check Condition step to check whether the opportunity’s estimated revenue is greater than or equal to $10,000.
Add a Create Record step to create a follow-up phone call if the condition evaluates to true. Set the subject of the phone call to Follow Up For New Opportunity and set the due date to three days after the opportunity’s creation date.
At this point, your workflow should look like the following image:
Publish the workflow.
Launch the ProgrammingWithDynamicsCrm4.WorkflowManager tool as described earlier in this chapter and connect to your Microsoft Dynamics CRM server.
Select the Dependency Test workflow from the list and click the Show Dependencies button.
You should see the workflow dependencies list in a grid as shown here. Resize the columns and scroll to examine all of the dependency properties.
By examining the workflow dependencies we can see that the majority of them have a type of AttributeDefinition (8) that defines attributes that need to exist for the workflow to run. Some of the attributes defined in the list are not directly referenced by anything we specified in our steps, but Microsoft Dynamics CRM uses them behind the scene regardless.
A dependency of type SdkAssociation (1) also dictates the workflow should be run in response to a Create message. If we had selected more messages to automatically trigger the workflow, they would show up as additional dependencies.
Two of the dependencies are used to request that images of the primary entity be passed in as local parameters during the workflow execution. These images allow our workflow to reference attributes off of the primary entity such as estimatedrevenue and createdon without needing to retrieve the data manually.
You may have noticed that the name and opportunityid attributes are also included in the primary entity image. This is because those attributes represent the primary attribute and primary key for the entity and they are always included in the primary entity image, regardless of whether you directly reference them.
The final dependency has a type of LocalParameter (2) and is used to define a local DynamicEntity property on the workflow itself. This property is referenced by PolicyActivity instances to populate and create the phonecall entity.
Before we move on from workflow dependencies, let’s look at how ProgrammingWithDynamicsCrm4.WorkflowManager retrieves the workflow dependencies from the CrmService. Example 12-3 shows the RetrieveWorkflowDependencies method from the WorkflowLogic class.
Example 12-3. The RetrieveWorkflowDependencies method
public IEnumerable<workflowdependency> RetrieveWorkflowDependencies( Guid workflowId, params string[] attributes) { QueryExpression query = new QueryExpression(); query.EntityName = "workflowdependency"; query.ColumnSet = new ColumnSet(attributes); query.Criteria.AddCondition("workflowid", ConditionOperator.Equal, workflowId); BusinessEntityCollection results = this.CrmService.RetrieveMultiple(query); foreach (workflowdependency dependency in results.BusinessEntities) { if (dependency.sdkmessageid != null) { RetrieveRequest request = new RetrieveRequest(); request.ReturnDynamicEntities = true; request.ColumnSet = new ColumnSet(new string[] {"name"}); request.Target = new TargetRetrieveDynamic() { EntityId = dependency.sdkmessageid.Value, EntityName = "sdkmessage" }; RetrieveResponse response = (RetrieveResponse)this.CrmService.Execute(request); DynamicEntity message = (DynamicEntity)response.BusinessEntity; dependency.sdkmessageid.name = (string)message["name"]; } yield return dependency; } }
Note that the workflowid passed in to this method should be the activeworkflowid attribute of a workflow with a type equal to definition. This is because workflow dependencies are created for the active workflows during publication, and are not tied to the definition itself.
Notice that RetrieveWorkflowDependencies has special logic around populating the name property on the sdkmessage attribute. This is done to hide a rare inconsistency with the CrmService RetrieveMultiple message, where the name of a Lookup attribute is not populated. The problem is further compounded if you are referencing the CRM SDK assemblies instead of a Web Reference, because the sdkmessage entity is not deserialized correctly from CrmService. To get around this, you must request that a DynamicEntity be returned.
Now that you have an understanding of workflow dependencies and the tools to explore them more deeply when needed, you are ready to move on to the main topic of the chapter: declarative workflows.