Building dynamic activities in Workflow foundation 4 – Part 3

Here it is, the last part of the series on dynamic activities in workflow foundation 4. In this part I will show you how you can build an even more advanced dynamic activity using the NativeActivity base class.

Scenario for this post

The scenario I will be demonstrating, produces an activity of which you (yes, you the developer) can design the body of that activity. The body that is designed will be encapsulated in an already existing activity structure that is required for all designed instances of that activity.

Building a dynamic activity using NativeActivity

The NativeActivity base class is the most advanced base class that can be found in workflow foundation 4 when it comes to building custom activities. With it you have access to bookmarks, private implementation activities and a whole lot more. It’s also the base class that requires you to write the most code to achieve the desired result.

The basic structure of a NativeActivity can be seen in the code snippet below:

public class MySequence: NativeActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        //TODO: Implementation
    }
}

This isn’t anything dynamic per sé, but since it’s native you can make it dynamic by allowing somebody to provide the body of the activity. For this you need to define a new property called Body which is of the Activity type.

public Activity Body { get; set; } 

To schedule the body you can use the ScheduleActivity method which has a couple of overloads. One overload accepts just the activity, a second overload accepts the activity to schedule and a callback and finally there’s an overload that also allows you to respond to exceptions raised in the activity that is scheduled. A sample of this can be seen below.

public class MySequence : NativeActivity
{
    public Activity Body { get; set; } 

    protected override void Execute(NativeActivityContext context)
    {
        context.ScheduleActivity(Body, OnBodyCompleted, OnBodyFaulted);
    }

    /// <summary>
    /// Called when the body raises an exception
    /// </summary>
    /// <param name="faultContext">The fault context.</param>
    /// <param name="propagatedException">The propagated exception.</param>
    /// <param name="propagatedFrom">The propagated from.</param>
    private void OnBodyFaulted(NativeActivityFaultContext faultContext, 
        Exception propagatedException, ActivityInstance propagatedFrom)
    {
            
    }

    /// <summary>
    /// Called when the body is completed
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="completedInstance">The completed instance.</param>
    private void OnBodyCompleted(NativeActivityContext context, 
        ActivityInstance completedInstance)
    {
        
    }
}

Providing a designer for the activity

While the sample in the previous section works, it’s not very designer friendly. When you add it to the designer it will not allow you to drag another activity onto it as the body of the MySequence.

To allow this behavior you need to create a designer for the activity. This designer is part of a separate Design project that you can add to the solution through the usual “Add Project” dialog.

The designer does a couple of things, first it graphically represents the activity. Second it holds a WorkflowItemPresenter control that is bound to the Body property of the activity, so that you can drag another activity onto the MySequence activity. You can see the XAML for the designer below.

<sap:ActivityDesigner x_Class="DynamicActivitySample3.Design.MySequenceDesigner"
                      
                      
                      
                      
                      >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition MinHeight="120"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition MinWidth="150"/>
        </Grid.ColumnDefinitions>
        <TextBlock Foreground="#CCCCCC" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Drop activity here."/>
        <Border BorderBrush="#CCCCCC" BorderThickness="1">
            <sap:WorkflowItemPresenter Item="{Binding Path=ModelItem.Body}" AllowedItemType="activities:Activity"/>
        </Border>
    </Grid>
</sap:ActivityDesigner>

Important to note here is that the designer doesn’t contain any code. Everything can be done through XAML because of the powerful databinding options offered by WPF.

Although the designer basically works now, it still doesn’t get used by the activity. To get this to work you previously added a DesignerAttribute to the activity and Visual Studio would use that to display the designer. With Visual Studio 2010 this no longer is a recommend practice. Instead you need to provide a DesignerMetadata class in the Designer assembly. This class is responsible for linking the designer to the activity. You can also provide a number of additional designer related attributes so that for example, you can group several properties into a separate group inside the property editor. The DesignerMetadata class is shown below.

/// <summary>
/// Contains designer metadata
/// </summary>
public class DesignerMetadata : IRegisterMetadata
{
    /// <summary>
    /// Registers this instance.
    /// </summary>
    public void Register()
    {
        AttributeTableBuilder tableBuilder = new AttributeTableBuilder();

        // Link the designer to the activity
        tableBuilder.AddCustomAttributes(typeof(MySequence), new DesignerAttribute(typeof(MySequenceDesigner)));

        // Mark the Body property as not browsable
        tableBuilder.AddCustomAttributes(typeof(MySequence), "Body", 
            new EditorBrowsableAttribute(EditorBrowsableState.Never),
            new BrowsableAttribute(false));

        // Register the attribute table in the metadata store
        MetadataStore.AddAttributeTable(tableBuilder.CreateTable());
    }
}

Now that you have the designer assembly and the activity assembly you need to do one more thing. Copy the design assembly into the same folder as the activity assembly. That way Visual Studio 2010 knows where to find the designer and it will automatically link the two together by loading up the DesignerMetadata class from the design assembly.

 

 

 

 

Advanced topics: Providing arguments to the body of the dynamic activity

The sample in the previous sections can be expanded in numerous ways to provide additional functionality. One of the ways to provide more functionality is to pass data to the body through the use of the ActivityAction<T> type.

To see how passing arguments to the body works, you need to modify the activity a bit and replace the body property with the following snippet:

private Variable<IEnumerator<String>> _namesEnumerator;

public ActivityAction<String> Body { get; set; }
public InArgument<IEnumerable<String>> Names { get; set; }

protected override void CacheMetadata(NativeActivityMetadata metadata)
{
    base.CacheMetadata(metadata);

    // register the names enumerator variable, otherwise the runtime will get lost.
    metadata.AddImplementationVariable(_namesEnumerator);
}

Every time the body gets executed it will get a single item from the list of names, which you can use in the activity that you use as the body of the MySequence activity.

The execute method needs to be modified to the following implementation to enable the new functionality:

protected override void Execute(NativeActivityContext context)
{
    IEnumerable<string> namesValue = Names.Get(context);

    if (namesValue == null)
        return;

    _namesEnumerator.Set(context, namesValue.GetEnumerator());
    ProcessNextName(context);
}

private void ProcessNextName(NativeActivityContext context)
{
    IEnumerator<String> namesEnumeratorValue = _namesEnumerator.Get(context);

    if (namesEnumeratorValue.MoveNext())
    {
        // Schedule the body
        context.ScheduleAction<String>(Body, 
            namesEnumeratorValue.Current, 
            OnBodyCompleted, OnBodyFaulted);
    }
}

/// <summary>
/// Called when the body is completed
/// </summary>
/// <param name="context">The context.</param>
/// <param name="completedInstance">The completed instance.</param>
private void OnBodyCompleted(NativeActivityContext context,
    ActivityInstance completedInstance)
{
    // Process the next name in the collection
    ProcessNextName(context);
}

What happens is that the activity will iterate over the items in the Names collection using an enumerator that is stored in a separate variable.

 

You may have noticed that I have overridden the CacheMetadata method. This method is responsible for providing the necessary metadata for the runtime. When you’re creating private variable instance or private activities you need to register them in this method. Otherwise the runtime will not be able to use them.

When you try to use the activity in this state, it won’t work correctly. This is because there’s ActivityAction<T> that you can drop from the toolbox on the designer. To solve this you need to introduce an implementation of the IActivityTemplateFactory class (You’ve seen that one before in the previous post).

public class MySequenceFactory: IActivityTemplateFactory

{

    public System.Activities.Activity Create(System.Windows.DependencyObject target)

    {

        ActivityAction<String> bodyAction = new ActivityAction<string>();

        bodyAction.Argument = new DelegateInArgument<string>(“CurrentName”);

        MySequence sequence = new MySequence

        {

            Body = bodyAction

        };

        return sequence;

    }

}

The factory is required to fill the ActivityAction ahead of time when you drop the MySequenceActivity on the designer. Because the Body is always available when you use the factory to create activity, you need to change the designer a bit so that it fills the Handler property of the body activity action instead of the body itself. This is done by changing the binding in the designer XAML to the following value:

<sap:WorkflowItemPresenter Item="{Binding Path=ModelItem.Body.Handler}" AllowedItemType="activities:Activity"/>

The end result is a MySequence activity with a designable body that automatically gets the current name in the list of names being processed.

image

What does the NativeActivity offer you?

The sample in this article demonstrates just a small part of what you can achieve with the NativeActivity base class. You can combine both normal C# code and activities to make almost any scenario thinkable.

A word of warning is in order though, do try to avoid this activity whenever you can and try to solve things first with the other activity types available. You should only consider using the NativeActivity class when you need to do complex things like creating activities who’s body needs to be designable.

Limitations

While the NativeActivity base class offers a nice set of features it doesn’t allow you to dynamically add public arguments to your activity. You can add private arguments, it’s functionality is limited in that the developer using your activity will not be able to fill those arguments.

Conclusion

As I worked on building custom dynamic activities I quickly learned that workflow foundation 4 is very powerful and allows a very broad range of activities to be developed. The downside of this is that it can be very complex and there are many ways in which things can get ugly very fast.

I still think however that it’s good to have this many options, because it means you can get almost anything to work in Workflow Foundation 4.