Export (0) Print
Expand All

Adding Metadata to the Custom Workflow Activity

banner art

[Applies to: Microsoft Dynamics CRM 4.0]

You can create custom workflow activities in Microsoft Visual C# or Visual Basic .NET code by creating an assembly that contains a class derived from one of the Windows Workflow Foundation activities. This assembly is annotated using .NET attributes to provide the metadata that Microsoft Dynamics CRM uses at runtime to link your code to the workflow engine.

For more information about .NET attributes, see this topic in the MSDN Library: Extending Metadata Using Attributes.

Creating a Workflow Class

To create a workflow class, you must derive it from one of the workflow activity classes that are provided in Windows Workflow Foundation. The following example is derived from System.Workflow.ComponentModel.Activity.

Simple activities that represent one logic action should derive from System.Workflow.ComponentModel.Activity class. Complex activities that are combinations of existing activities derive from System.Workflow.ComponentModel.SequenceActivity.

For a complete listing of workflow activities, see this topic in the MSDN Library: Windows Workflow Foundation Activities.

Example

The following example shows a custom workflow activity derived from Activity.

[CrmWorkflowActivity("Calculate Distance","Mappoint Utilities")]
public class DistanceCalculator : Activity
{
  // Activity code goes here.
}

Notice that the activity is annotated with the .NET attribute CrmWorkflowActivity. This provides the information needed by the Microsoft Dynamics CRM Workflow form and workflow runtime engine. The CrmWorkflowActivityAttribute class has two possible constructors. The first constructor initializes only the name property. The second constructor, which is used in this example, initializes both the name and the group name property. The group name specifies the name of the submenu added to the main menu on the workflow form. The name property specifies the name of the workflow activity. If the group name is not specified, then the workflow activity is listed on the main menu on the workflow form. By adding these attributes to you code, you provide the link to the workflow form. For more information, see the CrmWorkflowActivity.GroupName property,

For more information about using the Microsoft Dynamics CRM workflow form, see "Creating and Using Workflows" in the Microsoft Dynamics CRM Help.

Adding Input Parameters

The next step is to add some input properties to your code as shown in the following example. Notice that the declaration is for a dependency property. This indicates that the workflow is dependent on this value. For more information about Windows Workflow Foundation dependency properties, see this topic in the MSDN Library: Using Dependency Properties.

Example

The following code example shows the definition of an input parameter.

public static DependencyProperty zipCodeStartProperty = DependencyProperty.Register("zipCodeStart", typeof(string), typeof(DistanceCalculator));

[CrmInput("Starting Zip Code")]
public string zipCodeStart
{
    get
    {
        return (string)base.GetValue(zipCodeStartProperty);
    }
    set
    {
        base.SetValue(zipCodeStartProperty, value);
    }
}

This input parameter is annotated with the .NET attribute CrmInput. The CrmInputAttribute class derives from CrmParameterAttribute class, which takes a parameter to specify the name of the input attribute. As with the CrmWorkflowActivityAttribute class, this name appears in the workflow form assistant. This lets you map an attribute as an input parameter to the workflow.

Adding Output Parameters

Output parameters are added in the same manner as input parameters.

Example

The following code example shows the definition of an output parameter.

public static DependencyProperty totalDistanceProperty = DependencyProperty.Register("totalDistance", typeof(CrmNumber), typeof(DistanceCalculator));

[CrmOutput("Total Distance")]
public CrmNumber totalDistance
{
    get
    {
        return (CrmNumber)base.GetValue(totalDistanceProperty);
    }
    set
    {
        base.SetValue(totalDistanceProperty, value);
    }

}

This output parameter is annotated with the .NET attribute CrmOutput. The CrmOutputAttribute class derives from the CrmParameterAttribute class, which takes a parameter to specify the name of the output attribute. As with the CrmWorkflowActivityAttribute class, this name appears in the workflow form assistant. This lets you map an attribute as an output.

More Attributes

You can use the input and output attributes for the same property as shown in the following example. Notice that the myLookup property is both an input and an output. Also, some types require additional attributes to be specified.

Example

public static DependencyProperty myNumberProperty = DependencyProperty.Register("myLookup", typeof(Lookup), typeof(CreateCustomEntity));

[CrmInput("My Lookup")]
[CrmReferenceTarget("account")]
[CrmOutput("My Lookup Output")]
public Lookup myLookup
{
   get
   {
      return (Lookup)base.GetValue(myLookupProperty);
   }
   set
   {
      base.SetValue(myLookupProperty, value);
   }
}

Occasionally a workflow might fail, and it is desirable for the system to try to run it again. For example, you create a workflow that calls your custom workflow activity to look up a value, which then returns to the workflow and is used to send an e-mail. If the e-mail fails to send, the system will retry the workflow. You can use the System.Workflow.ComponentModel.PersistOnClose attribute to indicate that the current workflow instance must be persisted when the custom workflow activity completes its execution. If the workflow is subsequently retried, the custom workflow activity will not be reevaluated.

For more information, see the PersistOnCloseAttribute class at msdn2.microsoft.com/en-us/library/system.workflow.componentmodel.persistoncloseattribute.aspx.

Example

The following example shows the addition of the PersistOnClose attribute.

[CrmWorkflowActivity("Update Next Birthday", "Release Scenarios")]
[PersistOnClose]
public partial class ReleaseScenario_UpdateNextBirthday : SequenceActivity
{
   protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
   {
      // Your activity code goes here.
   }

Adding the Execute Method

Finally, your custom workflow activity has to have an Execute method as shown in the example in this section.

Notice that the input parameters can be accessed as follows:

this.zipCodeEnd
this.zipCodeStart

The output parameter is set just before the end:

this.totalDistance = new CrmNumber((int)route.Itinerary.Distance);

And the Execute method returns the status:

return ActivityExecutionStatus.Closed;

For more information about the return value, see this topic in the MSDN Library: ActivityExecutionStatus Enumeration.

Example

The following example shows how to use the Execute method.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
   // You must fill in the MapPoint user name and password.
    ICredentials creds = new NetworkCredential("MappointUserName", "MappointPassword");
    string DataSourceName = "MapPoint.NA";
    FindServiceSoap findService = new FindServiceSoap();
    findService.Credentials = creds;
    findService.PreAuthenticate = true;

    RouteServiceSoap routeService = new RouteServiceSoap();
    routeService.Credentials = creds;
    routeService.PreAuthenticate = true;
    routeService.UserInfoRouteHeaderValue = new UserInfoRouteHeader();
    routeService.UserInfoRouteHeaderValue.DefaultDistanceUnit = DistanceUnit.Mile;

    FindAddressSpecification addressSpecStart = new FindAddressSpecification();
    addressSpecStart.DataSourceName = DataSourceName;
    addressSpecStart.InputAddress = new Address();
    addressSpecStart.InputAddress.PostalCode = this.zipCodeStart;

    FindAddressSpecification addressSpecEnd = new FindAddressSpecification();
    addressSpecEnd.DataSourceName = DataSourceName;
    addressSpecEnd.InputAddress = new Address();
    addressSpecEnd.InputAddress.PostalCode = this.zipCodeEnd;

    FindResults resultsStart = findService.FindAddress(addressSpecStart);
    FindResults resultsEnd = findService.FindAddress(addressSpecEnd);

    LatLong startLatLong = resultsStart.Results[0].FoundLocation.LatLong;
    LatLong endLatLong = resultsEnd.Results[0].FoundLocation.LatLong;

    RouteSpecification routeSpec = new RouteSpecification();
    routeSpec.DataSourceName = DataSourceName;
    routeSpec.Segments = new SegmentSpecification[2];

    routeSpec.Segments[0] = new SegmentSpecification();
    routeSpec.Segments[0].Waypoint = new Waypoint();
    routeSpec.Segments[0].Waypoint.Location = resultsStart.Results[0].FoundLocation;

    routeSpec.Segments[1] = new SegmentSpecification();
    routeSpec.Segments[1].Waypoint = new Waypoint();
    routeSpec.Segments[1].Waypoint.Location = resultsEnd.Results[0].FoundLocation;


    Route route = routeService.CalculateSimpleRoute(new LatLong[] { startLatLong, endLatLong }, DataSourceName, SegmentPreference.Quickest);
    this.totalDistance = new CrmNumber((int)route.Itinerary.Distance);

    return ActivityExecutionStatus.Closed;
}

See Also

Concepts

Other Resources


© 2010 Microsoft Corporation. All rights reserved.


Show:
© 2014 Microsoft