Pick


Let's begin with a specific (and useful) pattern that is hard to capture using familiar control flow constructs available in general-purpose programming languages like C#. The Pick activity shown in Listing B.1 uses an attached metadata property to classify each of its child activities as a leader or a follower. When a Pick executes, its leaders are scheduled for execution first and only the followers of the first leader to complete are subsequently executed.

Listing B.1. Pick Activity

 using System; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Compiler; namespace EssentialWF.Activities {   [ActivityValidator(typeof(PickValidator))]   public class Pick : CompositeActivity   {     public static readonly DependencyProperty       FollowerOfProperty = DependencyProperty.RegisterAttached(         "FollowerOf",         typeof(string),         typeof(Pick),         new PropertyMetadata(DependencyPropertyOptions.Metadata),         typeof(FollowerOfAttachedPropertyValidator)       );     public static object GetFollowerOf(object dependencyObject)     {       DependencyObject o = dependencyObject as DependencyObject;       return o.GetValue(Pick.FollowerOfProperty);     }     public static void SetFollowerOf(object dependencyObject,       object value)     {       DependencyObject o = dependencyObject as DependencyObject;       o.SetValue(Pick.FollowerOfProperty, value);     }     internal static bool IsLeader(Activity a)     {       return (Pick.GetFollowerOf(a) == null);     }     private bool firstLeaderDone;     protected override void Initialize(       IServiceProvider provider)     {       firstLeaderDone = false;       base.Initialize(provider);     }     protected override ActivityExecutionStatus Execute(       ActivityExecutionContext context)     {       if (EnabledActivities.Count == 0)         return ActivityExecutionStatus.Closed;       // schedule execution of the leaders       foreach (Activity child in EnabledActivities)       {         if (Pick.IsLeader(child))         {           child.Closed += this.ContinueAt;           context.ExecuteActivity(child);         }       }       return ActivityExecutionStatus.Executing;     }     void ContinueAt(object sender,       ActivityExecutionStatusChangedEventArgs e)     {       e.Activity.Closed -= this.ContinueAt;       ActivityExecutionContext context =         sender as ActivityExecutionContext;       if (!firstLeaderDone)       {         // first leader is now completed         firstLeaderDone = true;         string leaderName = e.Activity.Name;         // cancel the other leaders, if any         int leadersCanceled = 0;         foreach (Activity child in EnabledActivities)         {           if (child.ExecutionStatus ==             ActivityExecutionStatus.Executing)         {           context.CancelActivity(child);           leadersCanceled++;         }       }         // schedule execution of the followers, if any         int followersExecuted = 0;         foreach (Activity child in EnabledActivities)         {           string s = Pick.GetFollowerOf(child) as string;           if (leaderName.Equals(s))           {             child.Closed += this.ContinueAt;             context.ExecuteActivity(child);             followersExecuted++;           }         }         if ((leadersCanceled + followersExecuted) == 0)         {           // no canceled leaders, and also no followers           context.CloseActivity();         }       }       else // a follower has completed       {         foreach (Activity child in EnabledActivities)         {           ActivityExecutionStatus status =             child.ExecutionStatus;           if ((status != ActivityExecutionStatus.Closed) &&             (status != ActivityExecutionStatus.Initialized))           {             // there is still at least 1 follower executing             return;           }         }         // all followers are done         context.CloseActivity();       }     }     // Cancellation logic     ...   } } 


The Pick activity uses the standard attached property pattern to define an attached property called FollowerOf, of type string. When the FollowerOf property is registered, a custom validator called FollowerOfAttachedPropertyValidator is specified. In this example, Pick is also associated with a validator component, Pick-Validator.

Every child activity of Pick is either a leader or a follower. A child activity is a leader by default; a child activity is a follower if it has the FollowerOf property attached to it. The value of the FollowerOf property must be the name of a peer activity (a different child activity of the same Pick) whose execution will precede that of the activity to which the FollowerOf property has been attached. In its Execute method, Pick schedules the execution of all leaders. When the first leader finishes, the Pick cancels the other leaders and executes any followers of the leader that completed first. There is no restriction on the type of child activity allowed as either leader or follower.

As an example, in the following WF program, there are two leaders (the Wait activities) and two followers (the WriteLine activities):

 <Pick x:Name="pick1" xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   <Wait Duration="00:00:06" x:Name="d1" />   <Wait Duration="00:00:02" x:Name="d2" />   <WriteLine Pick.FollowerOf="d1" x:Name="w1" Text="one" />   <WriteLine Pick.FollowerOf="d2" x:Name="w2" Text="two" /> </Pick> 


The output of the program is determined by which leader completes first. In this case, the Wait "d2" has a shorter duration, so the follower "w2" will execute and the output is

 two 


There is an interesting amount of validation logic to be written in order to ensure that the FollowerOf property is used correctly. For example, if all child activities are followers, none by definition can ever get executed. The PickValidator component, shown next, ensures that at least one child activity of a Pick is a leader:

[View full width]

using System; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Compiler; namespace EssentialWF.Activities { public class PickValidator : CompositeActivityValidator { public override ValidationErrorCollection Validate( ValidationManager manager, object obj) { ValidationErrorCollection errors = base.Validate(manager, obj); Pick pick = obj as Pick; foreach (Activity child in pick.EnabledActivities) { // if we find a leader, we can return if (Pick.IsLeader(child)) return errors; } errors.Add(new ValidationError("At least one child of Pick must not carry the FollowerOf attached property", 200)); return errors; } } }


Next we come to FollowerOfAttachedPropertyValidator, which is the validator component specified in the registration of the FollowerOf attached property. As shown next, this validation logic ensures that the FollowerOf property, as applied to a specific child activity of a Pick, has a legal value:

[View full width]

using System; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Compiler; namespace EssentialWF.Activities { public class FollowerOfAttachedPropertyValidator : Validator { public override ValidationErrorCollection Validate( ValidationManager manager, object obj) { ValidationErrorCollection errors = base.Validate(manager, obj); string activityName = obj as string; if (activityName == null || activityName.Equals(string.Empty)) { errors.Add(new ValidationError("FollowerOf has null or empty value specified", 201)); return errors; } Activity activity = manager.Context[typeof(Activity)] as Activity; Pick pick = activity.Parent as Pick; if (pick == null) { errors.Add(new ValidationError("FollowerOf must be applied to a child activity of Pick", 202)); return errors; } Activity target = pick.Activities[activityName]; if (target == null) { errors.Add(new ValidationError("FollowerOf must be the name of a child activity of Pick", 203)); return errors; } if (target.Name.Equals(activity.Name)) { errors.Add(new ValidationError("FollowerOf cannot refer to the same activity to which it is attached", 204)); return errors; } return errors; } } }


FollowerOfAttachedPropertyValidator performs several validation checks to make certain that an appropriate value has been provided for the FollowerOf property. The Pick activity (the parent of the activity carrying the FollowerOf property being validated) is obtained using the Context property of ValidationManager. To get a better feel for writing validation logic, we suggest that you enhance the preceding code to include a validation check that is missing: A follower activity must follow a leader (and not another follower). It also may prove instructive for you to write a set of sample WF programs that exercise all of the validation checks for Pick. This is a great habit to develop as an activity writer; there is little use writing validation logic unless you have test cases to show that invalid WF programs are being correctly identified as such.

The code for the Pick activity is not quite complete; it requires standard cancellation logic, as discussed in Chapter 4.




Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net