Declarative Workflows

One of the main features of Windows Workflow Foundation is the ability to run declarative workflows, which are described in a specific XML format called Extensible Application Markup Language, or XAML.

By abstracting a workflow definition from a programming language, WF has opened the door to the creation of workflow designers that target business users and end users instead of software developers. In addition, a program that uses declarative workflows does not need to be aware of them until run time, which allows workflow definitions to be modified without recompiling the host application.

XAML Syntax

While you may typically depend on a workflow designer to generate the XAML for you, we recommend that you should take a look at a sample XAML file to gain a basic understanding of the syntax. Example 12-4 demonstrates what a simple sequential workflow might look like in XAML.

Example 12-4. Sample XAML syntax

<SequentialWorkflowActivity
    x:Class="SampleWorkflowProject.SampleWorkflow"
    x:Name="SampleWorkflow"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

     <DelayActivity TimeoutDuration="00:01:00" x:Name="DelayOneMinute" />

</SequentialWorkflowActivity>

This sample is not very useful other than serving as a demonstration of the XAML syntax. It simply waits for one minute and then exits.

In this case, both of the element names (SequentialWorkflowActivity and DelayActivity) map to classes in the .NET Framework. The .NET namespace is determined from the element’s XML namespace. In this case the XML namespace http://schemas.microsoft.com/winfx/2006/xaml/workflow is mapped to the System.Workflow.Activities .NET namespace. This mapping comes from the application of the XmlnsdefinitionAttribute within the System.Workflow. Activities assembly.

The attributes with the x: prefix denote special XAML-specific properties that the XAML parser uses. The x:Name attribute assigns a name to the element that can be used to reference the elements later during execution. The x:Class attribute can only be assigned to the root element, and is used to define a class for that element that derives from the element’s natural type. This is used to create code-behind files similar to ASP.NET pages.

The TimeoutDuration attribute maps to a .NET property with the same name of type TimeSpan on the DelayActivity. Various type converters are available to the XAML parser that can convert string values into complex types such as TimeSpan. Properties can also be contained in child elements prefixed with the parent element’s name. For example, if you want to set a value to the Description property on the DelayActivity, you can add a Description attribute to the DelayActivity element, or you can assign a value by creating a child element inside the DelayActivity element shown by the following code:

<DelayActivity.Description>
    This is a one minute delay.
</DelayActivity.Description>

XAML is a powerful tool with an intuitive syntax that has many books and Web sites dedicated to it, but explaining the full details of XAML is outside the scope of this book. For more information on XAML, you can read about it at http://msdn.microsoft.com/en-us/library/ms747122.aspx. The topics in that section of MSDN specifically talk about XAML and its use in Windows Presentation Foundation (WPF), but the XAML syntax descriptions are excellent and still apply to WF.

XAML in Microsoft Dynamics CRM

Microsoft Dynamics CRM uses XAML behind the scenes during the publication of workflows defined within the native workflow designer. The XAML that describes the flow of activities is stored in the activities attribute in a workflow entity and the XAML that contains the policy rules are stored in the policy attribute (more on policy rules in a little bit).

The more interesting part is that Microsoft Dynamics CRM allows us to create workflow definitions and specify our own values for activities and rules.

The following benefits apply when you use XAML-based workflow definitions:

  • Although Microsoft Dynamics CRM Online does not currently support custom workflow activities, you can deploy XAML-based workflows to Microsoft Dynamics CRM Online.

  • You can access native WF activities that are not exposed by the native workflow designer in CRM (such as the ConditionedActivityGroup used for looping).

  • If you are already familiar with a different stand-alone XAML-based WF designer, you can continue to use it.

Creating Your First Declarative Workflow

In this section, we’ll create a declarative workflow in Visual Studio .NET that creates three follow-up tasks for a newly created lead. Each follow-up task is due a week apart. We use a simple loop to accomplish this. Looping is something that can only be accomplished in the native workflow designer by recursively calling the same workflow definition as a child process. By using a simple activity to achieve the same thing, we can create a more maintainable workflow that has less overhead.

Because we will only be uploading the XAML to the Microsoft Dynamics CRM server, we will not need to compile the workflow into an assembly. But in order to take advantage of the workflow designer in Visual Studio .NET, we’ll still create a workflow project.

Creating a workflow project

  1. In Visual Studio .NET, select File > New > Project... from the top menu.

  2. In the New Project dialog box, click Workflow under the Visual C# group in the Project types tree.

  3. Select Empty Workflow Project from the list of templates.

  4. Type SampleWorkflowProject in the Name box and click OK.

  5. Right-click SampleWorkflowProject in Solution Explorer and then click Add Reference.

  6. On the Browse tab, navigate to the CRM SDK’s bin folder and select microsoft.crm.sdk. dll and microsoft.crm.sdktypeproxy.dll. Click OK.

Now that we set up our project, we can add our workflow definition. Because there is not a CRM-specific workflow template in Visual Studio, we will tweak the files produced by the default WF template after we add them.

Adding a workflow definition

  1. Right-click SampleWorkflowProject in Solution Explorer and select Add > Sequential Workflow.

  2. Select Sequential Workflow (with code separation) from the list of Templates.

  3. Change the name to LeadFollowUpWorkflow.xoml and click OK.

    Note

    Note

    Even though the filename ends with .xoml instead of .xaml it is still a XAML file. The .xoml file extension is used to differentiate Windows Workflow Foundation XAML files from Windows Presentation Foundation XAML files. (WPF uses the .xaml file extension.)

    You should now see something like this image:

    Note
  4. Right-click the background of the workflow and select Properties.

  5. In the Properties window, click the Base Class property and click the ellipsis button that becomes visible.

  6. Type Microsoft.Crm.Workflow.CrmWorkflow in the Type Name box and click OK.

You now have a workflow definition created that you can start to edit in the Visual Studio .NET workflow designer. Before we begin, though, let’s add the CRM-specific activities to the Toolbox so that you can easily access them.

Adding the Microsoft Dynamics CRM assemblies to the Toolbox

  1. Right-click inside the Toolbox and select Add Tab.

  2. Type Microsoft Dynamics CRM 4.0 for the tab name and press Enter.

  3. Right-click within the new tab and select Choose Items.

  4. Click the Browse button at the bottom of the .NET Framework Components tab.

  5. Browse to the CRM SDK’s bin folder, select the Microsoft.Crm.Sdk.dll, and then click OK.

  6. Click OK to exit the Choose Toolbox Items dialog box.

You should now see the Toolbox populated with the familiar Microsoft Dynamics CRM workflow activities:

Note

Now we are ready to start designing our workflow definition. One of the first things you should think about when starting on a new workflow definition is which local parameters you need. It is important to have these local parameters defined before you begin writing the workflow policies that reference them because the policy editor is not forgiving of invalid syntax. In our case we need two local parameters to serve as temporary storage for the task entities we will create and for the count of tasks we have created. We can use the existing PrimaryEntity property from the base CrmWorkflow class to hold the lead image we receive from CRM.

Ultimately these parameters will be defined using workflow dependencies, but to use the Visual Studio .NET workflow designer without errors, we need to define them in the code-behind file.

Important

Important

You should not put anything in the code-behind file that cannot be emulated with workflow dependencies. The C# file is not uploaded to the CRM server when you deploy the XAML, so properties in the code-behind file need to be re-created using workflow dependencies.

Defining local parameters on the workflow

  1. Right-click the workflow in the designer and select View Code.

  2. Add using Microsoft.Crm.Sdk; to the end of the existing using statements.

  3. Within the class definition, add the following two properties:

    public DynamicEntity TaskEntity { get; set; }
    public int TaskCount { get; set; }
  4. Save and close the code-behind file.

Now with our local parameters set up, we can begin designing our workflow. For now we will just lay out the activities and assign them names, and then come back and fill out the additional rules that control the local parameters.

Designing the LeadFollowUpWorkflow

  1. Open the LeadFollowUpWorkflow.xoml file if it is not already open.

  2. In the Toolbox, drag a Policy activity from the Windows Workflow 3.0 tab into the workflow definition.

  3. In the Properties window for the newly created Policy activity, change the name to InitializeLoop. For now ignore the warning icon on the Policy activity and continue.

  4. Drag a ConditionedActivityGroup activity from the Toolbox into the workflow directly beneath the Policy you just configured. ConditionedActivityGroup is one of the activities that allows looping in Windows Workflow Foundation. Other activities such as the WhileActivity are simpler, but are not included in the list of types supported by CRM for workflows and therefore aren’t publishable. See the topic "Supported Types for Workflow" in the Microsoft Dynamics CRM 4.0 SDK for a list of supported workflow types.

  5. In the Properties window for the ConditionedActivityGroup, change the Name property to Loop.

  6. Drag a Sequence activity from the Toolbox into the ConditionedActivityGroup.

  7. In the properties window, rename the Sequence activity LoopSequence.

  8. Within the Loop activity, click the icon next to the text Previewing [1/1]. This switches ConditionedActivityGroup into edit mode and allows us to edit the contained activities.

  9. Drag another Policy activity into the LoopSequence activity and name it InitializeTask. Again ignore the warning icon for now and continue.

  10. From within the Microsoft Dynamics CRM 4.0 tab in the Toolbox, drag in a CreateActivity activity directly underneath the InitializeTask activity and then rename it CreateTask.

The following image shows what your workflow should look like now:

Important

The next step is to go through and tie all the various activities together through the local parameters. We will make heavy use of the policy activities to update the local parameters. PolicyActivity references declarative rules located within a separate rules file to execute statements when a condition evaluates to true. The rules file is also written in XAML and can perform simple programmatic statements without the need for a compiled assembly. Fortunately for us, the Microsoft Visual Studio .NET workflow designer automatically creates and maintains the rules file as we type in simple code statements.

Adding rules to the workflow definition

  1. Select the InitializeLoop activity in the workflow designer.

  2. Click the RuleSetReference property in the Properties window and then click the ellipsis button that appears.

  3. In the Select Rule Set dialog box, click the New toolbar button.

  4. In the Rule Set Editor dialog box, click the Add Rule toolbar button.

  5. Change the rule name from Rule1 to InitializeLoopRule.

  6. Enter true in the Condition text area. By doing this we are saying that we want this rule to always be executed. This is fairly common practice when using rules to manipulate local parameters.

  7. In the Then Actions text area, type this.TaskCount = 0 and click OK.

  8. In the Select Rule Set dialog box click the Rename toolbar button and type in the new name InitalizeLoopRuleSet when prompted. Click OK.

  9. Click OK to exit the Select Rule Set dialog box.

  10. Next you want to set up the LoopSequence so that it repeats three times. Click the LoopSequence activity, which is inside the Editing area of the Loop activity.

  11. Set the WhenCondition property in the Properties window to Declarative Rule Condition.

  12. Expand the WhenCondition property, click the ConditionName property beneath it, and then click the ellipsis button that appears.

  13. In the Select Condition dialog box, click the New toolbar button.

  14. Type this.TaskCount < 3 in the Condition text area and then click OK.

  15. Click Rename in the Select Condition dialog box and change the condition name to ContinueCondition. Click OK.

  16. Click OK to exit the Select Condition dialog box.

  17. Next you create a policy that increments the TaskCount local parameter and then initializes the TaskEntity local parameter so that it is ready for the CreateTask activity. Select the InitializeTask activity and click the ellipsis button for the RuleSetReference property.

  18. Click the New toolbar button within the Select Rule Set dialog box.

  19. Click the Add Rule toolbar button within the Rule Set Editor dialog box.

  20. Rename the rule InitializeTaskRule and set the Condition to True.

  21. Type the following code into the Then Actions text area and then click OK.

    this.TaskCount = this.TaskCount + 1
    this.TaskEntity = Microsoft.Crm.Workflow.CrmWorkflow.CreateEntity("task")
    this.TaskEntity["subject"] = string.Format("Follow up with lead ({0} days old)",
        this.TaskCount * 7)
    this.TaskEntity["scheduledend"] = Microsoft.Crm.Sdk.CrmDateTime.FromUser(
        System.DateTime.Now.AddDays(this.TaskCount * 7))
    this.TaskEntity["regardingobjectid"] =
        Microsoft.Crm.Workflow.CrmWorkflow.ConvertToLookup(
        this.PrimaryEntity["leadid"], "lead")

    Note

    Note

    Notice how the preceding code creates a new DynamicEntity and assigns it to the TaskEntity local parameter. It assigns values to the subject, scheduledend, and regarding-objectid attributes as well. For the regardingobjectid attribute, it gets the leadid from the primary entity image. This is a common pattern you will use whenever you need to create new entities in your custom workflows.

  22. Rename the rule set InitializeTaskRuleSet and click OK to close the Select Rule Set dialog box.

  23. The last thing we need to do is to associate the CreateTask with the TaskEntity local parameter. Start by clicking the CreateTask activity.

  24. Click the Entity property in the Properties window and then click the ellipsis button that appears.

  25. In the Bind dialog box, select TaskEntity from the Bind To An Existing Member tab and click OK.

At this point, we completely defined our workflow and the only thing that remains is deploying it to the CRM server. Fortunately the ProgrammingWithDynamicsCrm4.WorkflowManager tool can help with this process. The tool does, however, require us to set up an XML file to describe how the workflow should be deployed. Add a new XML file to your project named LeadFollowUpWorkflow.config. Type in the configuration text as it is shown in Example 12-5 into the newly created file.

Example 12-5. LeadFollowUpWorkflow.config contents

<?xml version="1.0" encoding="utf-8" ?>
<WorkflowConfiguration
  PrimaryEntity="lead"
  Name="Lead Follow Up">

  <Dependencies>
    <SdkAssociationDependency MessageName="Create" />
    <PrimaryEntityImageDependency
      ParameterName="PrimaryEntity" EntityAttributes="leadid" />
    <LocalParameterDependency
      ParameterName="TaskEntity" ParameterType="Microsoft.Crm.Sdk.DynamicEntity"/>
    <LocalParameterDependency
      ParameterName="TaskCount" ParameterType="System.Int32"/>
  </Dependencies>

</WorkflowConfiguration>

Here we define the name and primaryentity attributes for our workflow instance as well as four workflow dependencies. Notice that the two properties we created (TaskEntity and TaskCount) are represented here by the LocalParameterDependency elements. Programming-WithDynamicsCrm4.WorkflowManager looks for a .xoml and .rules file with the same name as the configuration file to assign the activities and rules attributes as well. If you have files that do not match that pattern for some reason, two attributes on the WorkflowConfiguration element, ActivitiesFile and RulesFile, allow you to specify the appropriate files.

Tip

Tip

If you edit your configuration file in Visual Studio .NET you can include a reference to the schema file to get IntelliSense while working with the XML. To include the schema information, modify the opening workflowConfiguration element to match the following code:

<WorkflowConfiguration
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="workflow-config.xsd"
  PrimaryEntity="lead"
  Name="Lead Follow Up">

Copy the workflow-config.xsd file from the ProgrammingWithDynamicsCrm4.WorkflowManager folder into the folder that contains your workflow configuration file. Now when you start a new element or attribute you are prompted with possible values, as shown here:

Tip

Deploying the workflow

  1. Make sure that all the files you have been working with are saved in Visual Studio .NET by selecting File and then Save All from the menu.

  2. Launch ProgrammingWithDynamicsCrm4.WorkflowManager and connect to your CRM server as described earlier in the chapter.

  3. Click the Import toolbar button.

  4. Navigate to the LeadFollowUpWorkflow.config file and then click OK.

    Within a few seconds the list of workflows should refresh and include a new one with the name Lead Follow Up.

  5. Select the Lead Follow Up workflow from the list and click the Publish toolbar button to publish it.

Now your declarative workflow is published and deployed. You can create a new lead in Microsoft Dynamics CRM, and after a few seconds the lead should have three tasks associated with it, each with due dates one week apart.

Declarative Workflow Deployment

Now that we have our first declarative workflow up and running, it is important to understand what is involved in deploying a declarative workflow. In this section we’ll examine the code behind the import functionality within the ProgrammingWithDynamicsCrm4.WorkflowManager tool. Example 12-6 shows the ImportWorkflow method of the WorkflowLogic class.

Example 12-6. The Importworkflow method

public void ImportWorkflow(string configurationFileName)
{
    WorkflowConfiguration config =
        WorkflowConfiguration.Load(configurationFileName);

    workflow workflow = config.ToEntity();

    Guid workflowId = this.CrmService.Create(workflow);
    foreach (WorkflowDependency dependencyConfig in config.Dependencies)
    {
        workflowdependency dependency = dependencyConfig.ToEntity(workflowId);

        if (dependency.sdkmessageid != null)
        {
            dependency.sdkmessageid.Value =
                GetSdkMessageId(dependency.sdkmessageid.name);
        }

        this.CrmService.Create(dependency);
    }
}

The method first loads the workflow configuration from the specified file and then asks the configuration class to generate a workflow instance, which it passes along to the CrmService Create method. Next, the method loops through the Dependencies property on the WorkflowConfiguration and gets a workflowdependency instance for each of them. If a value is provided for the sdkmessageid property, the actual ID is looked up by the GetSdkMessageId method based on the name of the SDK message. This is done so that the end user editing the configuration file does not need to know the message ID and can just specify the name. Finally, each workflowdependency is passed in to the CrmService Create method as well.

Let’s continue by taking a look at the WorkflowConfiguration class load method, as shown in Example 12-7.

Example 12-7. The WorkflowConfiguration load method

public class WorkflowConfiguration
{
    [XmlAttribute]
    public string Name { get; set; }

    [XmlAttribute]
    public string Description { get; set; }

    [XmlAttribute]
    public string PrimaryEntity { get; set; }

    [XmlAttribute]
    public string ActivitiesFile { get; set; }

    [XmlAttribute]
    public string RulesFile { get; set; }

    [XmlArrayItem(typeof(AttributeDefinitionDependency))]
    [XmlArrayItem(typeof(CustomEntityDefinitionDependency))]
    [XmlArrayItem(typeof(SdkAssociationDependency))]
    [XmlArrayItem(typeof(PrimaryEntityImageDependency))]
    [XmlArrayItem(typeof(PrimaryEntityPreImageDependency))]
    [XmlArrayItem(typeof(PrimaryEntityPostImageDependency))]
    [XmlArrayItem(typeof(RelatedEntityImageDependency))]
    [XmlArrayItem(typeof(LocalParameterDependency))]
    public WorkflowDependency[] Dependencies { get; set; }

    public static WorkflowConfiguration Load(string configurationFileName)
    {
        ...
    }

    public workflow ToEntity()
    {
        ...
    }


    public void Save(string configFileName)
    {
        ...
    }
}

The WorkflowConfiguration class mainly represents the state of a workflow configuration, but it does have a few methods that help in the loading, saving, and conversion to a workflow instance. We’ll take a look at these methods shortly, but first notice the XmlAttribute and XmlArrayItem attributes assigned to the property definitions in WorkflowConfiguration. These attributes are used to aid serialization of the class to and from XML with the aid of the System.Xml.Serialization.XmlSerializer class. XmlAttribute tells XmlSerializer to use an xml attribute instead of a child element to contain the property value. XmlArrayItem is used to list the acceptable types that can be included in an array property. Example 12-8 demonstrates how simple it is to load an XML file into an instance of WorkflowConfiguration when using XmlSerializer.

Example 12-8. WorkflowConfiguration’s Load method

public static WorkflowConfiguration Load(string configurationFileName)
{
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(WorkflowConfiguration));
    WorkflowConfiguration config;
    using(Stream file = File.OpenRead(configurationFileName))
    {
        config = (WorkflowConfiguration)xmlSerializer.Deserialize(file);
    }

    if (String.IsNullOrEmpty(config.PrimaryEntity))
    {
        throw new InvalidOperationException(
            "PrimaryEntity is a required attribute.");
    }

    if (String.IsNullOrEmpty(config.ActivitiesFile))
    {
        config.ActivitiesFile = Path.ChangeExtension(
            configurationFileName, ".xoml");
    }

    if (!Path.IsPathRooted(config.ActivitiesFile))
    {
        config.ActivitiesFile = Path.Combine(
            Path.GetDirectoryName(configurationFileName),
            config.ActivitiesFile);
    }

    if (String.IsNullOrEmpty(config.RulesFile))
    {
        string rulesFile = Path.ChangeExtension(config.ActivitiesFile, ".rules");
        if (File.Exists(rulesFile))
        {
            config.RulesFile = rulesFile;
        }
    }

    if (!String.IsNullOrEmpty(config.RulesFile) &&
           !Path.IsPathRooted(config.RulesFile))
    {
        config.RulesFile = Path.Combine(
            Path.GetDirectoryName(configurationFileName),
              config.RulesFile);
    }


    if (String.IsNullOrEmpty(config.Name))
    {
        config.Name = Path.GetFileNameWithoutExtension(config.ActivitiesFile);
    }

    return config;
}

The first six lines of the method de-serialize the XML file into a WorkflowConfiguration instance. Next the method confirms that a primary entity value was specified and then determines intelligent defaults for any other values that were omitted.

The ToEntity method is responsible for converting the WorkflowConfiguration into a workflow instance that can be passed along to CrmService’s Create method. After the Load method has set up all of the properties, performing this conversion is straightforward, as shown in Example 12-9.

Example 12-9. WorkflowConfiguration’s ToEntity method

public workflow ToEntity()
{
    workflow entity = new workflow();
    entity.name = this.Name;

    entity.type = new Picklist(WorkflowType.Definition);
    entity.primaryentity = new EntityNameReference(this.PrimaryEntity);
    entity.activities = File.ReadAllText(this.ActivitiesFile);

    if (!String.IsNullOrEmpty(this.RulesFile))
    {
        entity.rules = File.ReadAllText(this.RulesFile);
    }

    return entity;
}

The last method in WorkflowConfiguration is actually used during the workflow export process, and we examine it later in the chapter.

Next let’s look at the dependencies and how they are imported. If you recall from Example 12-6, the ImportWorkflow method loops through each of the WorkflowDependency instances in the WorkflowConfiguration.Dependencies property and calls ToEntity on each of them to generate a workflowdependency instance.

WorkflowDependency is an abstract base class that has no properties of its own. The ToEntity method it implements is only provided as a helper for the inherited classes. Example 12-10 shows the simple ToEntity implementation on WorkflowDependency.

Example 12-10. WorkflowDependency’s ToEntity method

public virtual workflowdependency ToEntity(Guid workflowId)
{
    workflowdependency entity = new workflowdependency();
    entity.workflowid = new Lookup("workflow", workflowId);
    return entity;
}

If you refer back to Example 12-7 you can see eight applications of the XmlArrayItem attribute on the WorkflowConfiguration.Dependencies property. Each application of this attribute specifies a class that can be entered into the XML-based configuration file and ultimately passed in as an item to the Dependencies property.

You can specify the following eight valid types for the Dependencies property:

  • AttributedefinitionDependency

  • CustomEntitydefinitionDependency

  • LocalParameterDependency

  • SdkAssociationDependency

  • PrimaryEntityImageDependency

  • PrimaryEntityPreImageDependency

  • PrimaryEntityPostImageDependency

  • RelatedEntityImageDependency

You have probably noticed that these dependencies correspond very closely to the values found on WorkflowDependencyType as shown in Table 12-2. This is because each of these classes is used to specify one of the different dependency types in the workflow configuration file. Let’s take a brief look at each of the individual classes to get a better understanding of how the dependencies are mapped to their CRM counterparts.

The first of these classes, AttributedefinitionDependency, is used when you want to keep a custom attribute from being deleted when you have a workflow that depends on it. The full source for AttributedefinitionDependency is shown in Example 12-11.

Example 12-11. The AttributedefinitionDependency class

using System;
using System.Xml.Serialization;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public class AttributeDefinitionDependency: WorkflowDependency
    {
        public AttributeDefinitionDependency()
        {

        }

        public AttributeDefinitionDependency(workflowdependency dependency)
        {
            this.DependentEntityName = dependency.dependententityname;
            this.DependentAttributeName = dependency.dependentattributename;
        }

        [XmlAttribute]
        public string DependentEntityName { get; set; }

        [XmlAttribute]
        public string DependentAttributeName { get; set; }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.type = new Picklist(WorkflowDependencyType.AttributeDefinition);
            entity.dependententityname = this.DependentEntityName;
            entity.dependentattributename = this.DependentAttributeName;
            return entity;
        }
    }
}

This class is not very complex. During workflow import only the parameterless constructor is used. The other constructor, which accepts a workflowdependency, is used during workflow export. The two properties are both tagged with XmlAttribute again to ensure that they are both serialized as attributes instead of child elements.

The method of most interest, however, is ToEntity, which calls the base implementation to create an instance of workflowdependency and assign the proper workflowid. It then proceeds to set the type attribute to WorkflowDependencyType.Attributedefinition. The remaining attribute assignments, dependententityname and dependentattributename both have one-to-one mappings with properties on AttributedefinitionDependency.

The next workflow dependency configuration class is CustomEntitydefinitionDependency, which you use when you want to keep users from deleting custom entities that your workflow depends on. Example 12-12 shows CustomEntityDefinitionDependency in its entirety.

Example 12-12. The CustomEntityDefinitionDependency class

using System;
using System.Xml.Serialization;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public class CustomEntityDefinitionDependency: WorkflowDependency
    {
        public CustomEntityDefinitionDependency()
        {

        }

        public CustomEntityDefinitionDependency(workflowdependency dependency)
        {
            this.CustomEntityName = dependency.customentityname;
        }

        [XmlAttribute]
        public string CustomEntityName { get; set; }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.type =
                new Picklist(WorkflowDependencyType.CustomEntityDefinition);
            entity.customentityname = this.CustomEntityName;
            return entity;
        }
    }
}

CustomEntityDefinitionDependency follows the same pattern as AttributeDefinitionDependency, but assigns a different value to the type attribute and then only populates the customentityname attribute.

Both CustomEntityDefinitionDependency and AttributeDefinitionDependency define workflow dependencies on the CRM metadata. You can use these to verify that the metadata is in an acceptable state for your workflow to execute, but they do not modify the workflow definition or change when it executes. Because of this, you only need to apply these dependencies to prevent a user from deleting custom entities and attributes that your workflow depends on. If you do not apply these dependencies, and a custom entity or attribute that your workflow requires is deleted, Microsoft Dynamics CRM 4.0 will raise an error and put the workflow instance in a suspended state when it tries to access the unavailable attribute or entity.

Next we take a look at SdkAssociationDependency. You can use this configuration entry to specify one or more system messages that should automatically trigger workflow execution. Example 12-13 shows the full source code for SdkAssociationDependency.

Example 12-13. The SdkAssociationDependency class

using System;
using System.Xml.Serialization;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public class SdkAssociationDependency : WorkflowDependency
    {
        public SdkAssociationDependency()
        {
        }

        public SdkAssociationDependency(workflowdependency dependency)
        {
            if (dependency.sdkmessageid != null)
            {
                this.MessageName = dependency.sdkmessageid.name;
            }
            this.EntityAttributes = dependency.entityattributes;
        }

        [XmlAttribute]
        public string MessageName { get; set; }

        [XmlAttribute]
        public string EntityAttributes { get; set; }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);

            entity.type = new Picklist(WorkflowDependencyType.SdkAssociation);
            entity.sdkmessageid = new Lookup("message", Guid.Empty);
            entity.sdkmessageid.name = this.MessageName;
            entity.entityattributes = this.EntityAttributes;

            return entity;
        }
    }
}

SdkAssociationDependency is provided with a message name, but ultimately needs to map the human-readable name to the internal CRM SDK message ID. It creates the Lookup and populates the name property, but assumes that the caller will look up the ID if needed. SdkAssociationDependency also can assign a value to the entityattributes attribute. This is used for the Update message so that the workflow is only executed if the specified attributes are modified.

Next up is the LocalParameterDependency class, which you can use to add local parameters to the workflow definition. We used these in our first declarative workflow sample to create the TaskEntity and TaskCount parameters. Example 12-14 shows the source code for the LocalParameterDependency class.

Example 12-14. The LocalParameterDependency class

using System;
using System.Xml.Serialization;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public class LocalParameterDependency : WorkflowDependency
    {
        public LocalParameterDependency()
        {

        }

        public LocalParameterDependency(workflowdependency dependency)
        {
            this.ParameterName = dependency.parametername;
            this.ParameterType = dependency.parametertype;
        }

        [XmlAttribute]
        public string ParameterName { get; set; }

        [XmlAttribute]
        public string ParameterType { get; set; }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.type = new Picklist(WorkflowDependencyType.LocalParameter);
            entity.parametername = this.ParameterName;
            entity.parametertype = this.ParameterType;
            return entity;
        }
    }
}

The only unique aspect of LocalParameterDependency is the ParameterType property, which you use to specify the .NET type for the parameter. The name should include the namespace, but exclude the assembly information. You can find the full list of supported types in the CRM SDK under the topic "Supported Types for Workflow."

The remaining workflow dependency configuration classes are similar to each other because they are all related to entity images of one form or another. Therefore they have an additional abstract class they inherit from that implements the common functionality they share. EntityImageDependency is the base class that provides population of the parametername and entityattributes attributes on the workflowdependency. This class is shown in Example 12-15.

Example 12-15. The EntityImageDependency class

using System;
using System.Xml.Serialization;
using Microsoft.Crm.SdkTypeProxy;
namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public abstract class EntityImageDependency : WorkflowDependency
    {
        public EntityImageDependency()
        {
        }

        public EntityImageDependency(workflowdependency dependency)
        {
            this.ParameterName = dependency.parametername;
            this.EntityAttributes = dependency.entityattributes;
        }

        [XmlAttribute]
        public string ParameterName { get; set; }

        [XmlAttribute]
        public string EntityAttributes { get; set; }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.parametername = this.ParameterName;
            entity.entityattributes = this.EntityAttributes;
            return entity;
        }
    }
}

All of the entity image dependencies need to specify a local parameter name that will be populated with the entity image. This parameter is always of type DynamicEntity. The attributes that should be included in the image are also handled by the EntityImageDependency base class.

The first and most commonly used entity image dependency is PrimaryEntityImage-Dependency. By adding this dependency to your workflow you can request which attributes from the workflow’s primary entity should be populated on the CrmWorkflow’s PrimaryEntity property. You’ll frequently want to access attributes off of the primary entity, and your workflow can run faster if you register a dependency for those attributes instead of retrieving them during execution yourself. Example 12-16 shows the full source code for PrimaryEntityImageDependency.

Example 12-16. The PrimaryEntityImageDependency class

using System;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public class PrimaryEntityImageDependency : EntityImageDependency
    {
        public PrimaryEntityImageDependency()
        {

        }

        public PrimaryEntityImageDependency(workflowdependency dependency)
            : base(dependency)
        {

        }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.type = new Picklist(WorkflowDependencyType.PrimaryEntityImage);
            return entity;
        }
    }
}

PrimaryEntityImageDependency has almost everything done by its base class. It is only responsible for setting the appropriate WorkflowDependencyType value.

The next two dependency configuration classes are very similar to PrimaryEntityImage-Dependency. You can use PrimaryEntityPreImageDependency and PrimaryEntityPostImage-Dependency to request images of the primary entity as it existed before the core operation is performed and after it is completed. Because these two classes are so similar to each other and to the previous example, they are both included in Example 12-17.

Example 12-17. The PrimaryEntityPreImageDependency and PrimaryEntityPostImageDependency classes

using System;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public class PrimaryEntityPreImageDependency : EntityImageDependency
    {
        public PrimaryEntityPreImageDependency()
        {

        }

        public PrimaryEntityPreImageDependency(workflowdependency dependency)
            : base(dependency)
        {

        }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.type = new
                Picklist(WorkflowDependencyType.PrimaryEntityPreImage);
            return entity;
        }
    }

    public class PrimaryEntityPostImageDependency : EntityImageDependency
    {
        public PrimaryEntityPostImageDependency()
        {

        }

        public PrimaryEntityPostImageDependency(workflowdependency dependency)
            : base(dependency)
        {

        }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.type = new
                Picklist(WorkflowDependencyType.PrimaryEntityPostImage);
            return entity;
        }
    }
}

The last of the entity image dependency classes is slightly different. You use RelatedEntityIma geDependency to specify that your workflow would like an image of an entity directly related to the workflow’s primary entity. This is useful when you need access to the attributes on the related entity. For example, if you want to generate an e-mail message when a case is open too long, you can use a RelatedEntityImageDependency in your configuration file to get the e-mail address of the case’s owner. Example 12-18 shows the full source code for the RelatedEntityImageDependency class.

Example 12-18. The RelatedEntityImageDependency class

using System;
using System.Xml.Serialization;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace ProgrammingWithDynamicsCrm4.WorkflowManager.Configuration
{
    public class RelatedEntityImageDependency : EntityImageDependency
    {
        public RelatedEntityImageDependency()
        {

        }

        public RelatedEntityImageDependency(workflowdependency dependency)
            : base(dependency)
        {
            this.RelatedEntityName = dependency.relatedentityname;
            this.RelatedAttributeName = dependency.relatedattributename;
        }

        [XmlAttribute]
        public string RelatedEntityName { get; set; }

        [XmlAttribute]
        public string RelatedAttributeName { get; set; }

        public override workflowdependency ToEntity(Guid workflowId)
        {
            workflowdependency entity = base.ToEntity(workflowId);
            entity.type = new Picklist(WorkflowDependencyType.RelatedEntityImage);
            entity.relatedentityname = this.RelatedEntityName;
            entity.relatedattributename = this.RelatedAttributeName;
            return entity;
        }
    }
}

RelatedEntityImageDependency has two additional attributes beyond the other EntityImageDependency-derived classes. RelatedAttributeName is the name of the Lookup attribute on the primary entity that references the related entity. RelatedEntityName is the name of the related entity type. The name of the local parameter that is populated—as well as which attributes are included in the image—are handled by the base EntityImageDependency class.

Workflow dependencies are fairly advanced and not well-documented in the CRM SDK. Although the approach used in ProgrammingWithDynamicsCrm4.WorkflowManager uses many classes to accomplish workflow dependency management, none of them is very complicated and the end result is a very user-friendly configuration file. Now that you have an understanding of declarative workflow deployment, you can use the concepts described in this section to implement your own deployment tools that may be more streamlined for your environment.

Examining Native Workflow XAML

You may find yourself trying to emulate a workflow that you created in the native CRM workflow designer in a declarative workflow. The natively designed workflows still generate XAML behind the scenes during publication, and you can retrieve the XAML and inspect it when you need some inspiration for your own declarative workflows.

ProgrammingWithDynamicsCrm4.WorkflowManager has a feature that allows workflows to be exported as XAML even if they were originally created in the native designer. We’ll demonstrate this by exporting the Dependency Test workflow created earlier in this chapter and adding it to the sample workflow project we created.

Adding a natively designed workflow to a declarative workflow project

  1. Start ProgrammingWithDynamicsCrm4.WorkflowManager and connect to your CRM server as described earlier in the chapter.

  2. Select the Dependency Test workflow from the list and click the Export toolbar button.

    Examining Native Workflow XAML
  3. In the Browse For Folder dialog box, navigate to your SampleWorkflowProject that contains the NewLeadFollowUp.xoml file and click OK.

  4. Close the ProgrammingWithDynamicsCrm4.WorkflowManager.

  5. Launch Visual Studio .NET and open the SampleWorkflowProject you created earlier.

  6. Right-click SampleWorkflowProject within Solution Explorer and select Add > Existing Item.

  7. In your project folder you should see the recently exported files DependencyTest.xoml, DependencyTest.rules, and DependencyTest.config. Select them all and click Add.

  8. At this point if you open the DependencyTest.xoml file in Visual Studio .NET you will see a workflow like the one shown here:

    Examining Native Workflow XAML

    The workflow designer indicates errors within the rules. This is because the local parameters that are normally created on the fly during workflow compilation do not exist in our declarative workflow. To get rid of the errors in the workflow designer we need to add a code-behind file to define the local parameters.

  9. Right-click SampleWorkflowProject in Solution Explorer and select Add > Class.

  10. Type in the name DependencyTest.xoml.cs and click Add.

  11. Update the contents of DependencyTest.xoml.cs to match the following code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Crm.Workflow;
    using Microsoft.Crm.Sdk;
    
    namespace SampleWorkflowProject
    {
      public partial class DependencyTest: CrmWorkflow
      {
    
            public DynamicEntity primaryEntity { get; set; }
            public DynamicEntity CreateStep4_localParameter { get; set; }
            public DynamicEntity PreImageParameter { get; set; }
      }
    }
  12. Determine the names of the properties you need by looking at the DependencyTest. config file and using the ParameterName attributes off of the LocalParameterDepende ncy, PrimaryEntityImageDependency, PrimaryEntityPreImageDependency, PrimaryEntityPostImageDependency, and RelatedEntityImageDependency elements.

  13. Next, update the .xoml file to reference the code-behind file. Right-click DependencyTest.xoml in Solution Explorer and select Open With.

  14. Select XML Editor from the list and click OK. If you already have DependencyTest.xoml open in the workflow designer, you may receive a warning asking if you want to close it. If that happens click Yes and continue.

  15. In the root element in DependencyTest.xoml, find the x:Name attribute and add x:Class="SampleWorkflowProject.DependencyTest" directly after it. This ties the declarative workflow definition to the code-behind class.

  16. Close the DependencyTest.xoml file in the XML Editor and then double-click it in Solution Explorer to open it in the workflow designer. The workflow definition now contains no errors and you can inspect the native workflow designer’s version of a declarative workflow.

Exporting Workflows Programmatically

The code that allows ProgrammingWithDynamicsCrm4.WorkflowManager to export the XAML behind CRM’s workflows uses all of the same classes that we reviewed during our examination of the import process. However, a few unique methods are worth taking a look at.

The main method, ExportWorkflow, is found in the WorkflowLogic class. Example 12-19 displays the contents of this method.

Example 12-19. WorkflowLogic’s ExportWorkflow method

public void ExportWorkflow(Guid workflowId, string exportFolder)
{
    ColumnSet cols = new ColumnSet();
    cols.AddColumns(
        "name",
        "description",
        "primaryentity",
        "activities",
        "rules",
        "plugintypeid");

    workflow workflow = (workflow)this.CrmService.Retrieve(
        "workflow", workflowId, cols);

    String configFileName = Path.Combine(exportFolder, String.Concat(
        Regex.Replace(workflow.name, "[^a-zA-Z0-9]", ""),
        ".config"));

    WorkflowConfiguration config = new WorkflowConfiguration();
    config.Name = workflow.name;
    config.Description = workflow.description;
    config.PrimaryEntity = workflow.primaryentity.Value;

    File.WriteAllText(
        Path.ChangeExtension(configFileName, ".xoml"), workflow.activities,
        Encoding.Unicode);

    if (!String.IsNullOrEmpty(workflow.rules))
    {
        File.WriteAllText(
            Path.ChangeExtension(configFileName, ".rules"), workflow.rules,
            Encoding.Unicode);
    }

    IEnumerable<workflowdependency> dependencies =
        this.RetrieveWorkflowDependencies(workflowId,
        "type",
        "parametername",
        "parametertype",
        "relatedentityname",
        "relatedattributename",
        "sdkmessageid",
        "entityattributes",
        "customentityname",
        "dependententityname",
        "dependentattributename");

   List<WorkflowDependency> configDependencies = new List<WorkflowDependency>();
   foreach (workflowdependency dependency in dependencies)
   {
         WorkflowDependency configDependency =
         WorkflowDependency.FromEntity(dependency);

      if (configDependency != null)
      {
         configDependencies.Add(configDependency);
      }
   }

   config.Dependencies = configDependencies.ToArray();
   config.Save(configFileName);
}

ExportWorkflow starts by retrieving the workflow with all of the required attributes. It then strips out all non-alphanumeric characters from the workflow name to use as a base name for the three files that will be created. Next, the method creates an instance of WorkflowConfiguration and populates its properties using values from the retrieved workflow. After this, the activities (.xoml) and rules files are written from the appropriate attributes on the workflow.

The next section of ExportWorkflow retrieves the Workflowdependency entities associated with the workflow and uses the static WorkflowDependency FromEntity method to convert them into their WorkflowDependency-derived counterparts. All of these dependencies are passed in to the original WorkflowConfiguration object and the Save method is called, which is responsible for generating the .config file.

The Save method is quite simple, thanks to the XmlSerializer class. Example 12-20 shows the Save method using the XmlSerializer to generate an XML file based on a WorkflowConfiguration instance.

Example 12-20. WorkflowConfiguration’s Save method

public void Save(string configFileName)
{
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(WorkflowConfiguration));
    using (Stream file = File.Create(configFileName))
    {
        xmlSerializer.Serialize(file, this);
    }
}

The WorkflowDependency FromEntity method is a straightforward class factory that creates the appropriate instance of a WorkflowDependency-derived class. Example 12-21 demonstrates how this is accomplished.

Example 12-21. WorkflowDependency’s FromEntity method

public static WorkflowDependency FromEntity(workflowdependency dependency) 
{

    switch (dependency.type.Value)
    {
        case WorkflowDependencyType.SdkAssociation:
            return new SdkAssociationDependency(dependency);

        case WorkflowDependencyType.PrimaryEntityImage:
            return new PrimaryEntityImageDependency(dependency);

        case WorkflowDependencyType.PrimaryEntityPreImage:
            return new PrimaryEntityPreImageDependency(dependency);

        case WorkflowDependencyType.PrimaryEntityPostImage:
            return new PrimaryEntityPostImageDependency(dependency);

        case WorkflowDependencyType.RelatedEntityImage:
            return new RelatedEntityImageDependency(dependency);

        case WorkflowDependencyType.LocalParameter:
            return new LocalParameterDependency(dependency);

        case WorkflowDependencyType.CustomEntityDefinition:
            return new CustomEntityDefinitionDependency(dependency);

        case WorkflowDependencyType.AttributeDefinition:
            return new AttributeDefinitionDependency(dependency);

        default:
            return null;
    }
}

FromEntity uses the type attribute off the passed-in workflowdependency to determine which derived class to create. It uses the overloaded version of the constructor that takes a workflowdependency as an argument so that the derived class can populate its properties appropriately.

If at any point you need inspiration regarding how to re-create something in a declarative workflow that you know how to do in CRM’s native workflow designer, you can use the export functionality in ProgrammingWithDynamicsCrm4.WorkflowManager to export the workflow and then inspect the dependencies and declarative workflow definition generated by CRM.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset