Workflow custom Loop Activity

Thursday, November 02, 2006 3:40 PM

I was doing some workflow work and wanted to create a custom loop activity. The project needs it, and it's a great way to learn what (not) to do.

The activity is a container that would loop through a list of discrete items (think foreach(string currentValue in ValueList)) and exposes the current loop variable via a bindable DependencyProperty.

The basics of the activity are to keep the container in "Executing" mode until all child activities are done. The tricky part is that the whole ActivityExecutionContext and ExecutionContextManager need to create for a new context each loop iteration. The hookup of the synchronization is done by using Activity.RegisterForStatusChange(.., OnEvent) on each child executed, then in the OnEvent() unregister the activity from further notice. I don't love it, but it works.

 

Here goes:

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Design;

namespace NH.Workflow
{
    [Designer(typeof(SequenceDesigner),
      typeof(IDesigner)),
      ToolboxItem(typeof(ActivityToolboxItem)),

Description("Loop Activity - iterate over discrete list of items."),
      ActivityValidator(typeof(LoopActivityValidator))]
    public sealed class LoopActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
    {
        private int currentIndex = 0;
        private string[] valueList = { };

        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext)
        {

            if (base.EnabledActivities.Count == 0)
                return ActivityExecutionStatus.Closed;

            Activity firstChildActivity = base.EnabledActivities[0];
            ActivityExecutionContext firstChildContext = executionContext.ExecutionContextManager.GetExecutionContext(firstChildActivity);
            if (firstChildContext == null)
                return ActivityExecutionStatus.Closed;

            if (firstChildContext.Activity.ExecutionStatus == ActivityExecutionStatus.Executing)
                firstChildContext.CancelActivity(firstChildContext.Activity);

            return ActivityExecutionStatus.Canceling;
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            if (this.PerformNextIteration(executionContext))
                return ActivityExecutionStatus.Executing;
            else
                return ActivityExecutionStatus.Closed;
        }

        void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(object sender, ActivityExecutionStatusChangedEventArgs statusChangeEvent)
        {
            ActivityExecutionContext originContext = sender as ActivityExecutionContext;
            statusChangeEvent.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);
            ActivityExecutionContextManager ctxManager = originContext.ExecutionContextManager;
            ctxManager.CompleteExecutionContext(ctxManager.GetExecutionContext(statusChangeEvent.Activity));
            if (!this.PerformNextIteration(originContext))
                originContext.CloseActivity();
        }

        private bool PerformNextIteration(ActivityExecutionContext context)
        {
            if (((base.ExecutionStatus == ActivityExecutionStatus.Canceling)
                    || (base.ExecutionStatus == ActivityExecutionStatus.Faulting))
                    || currentIndex == valueList.Length)
            {
                return false;
            }

            this.CurrentValue = valueList[currentIndex++];

            if (base.EnabledActivities.Count > 0)
            {
                ActivityExecutionContext firstChildContext = context.ExecutionContextManager.CreateExecutionContext(base.EnabledActivities[0]);
                firstChildContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this);
                firstChildContext.ExecuteActivity(firstChildContext.Activity);
            }

            return true;
        }

        public static DependencyProperty ValueListProperty = System.Workflow.ComponentModel.DependencyProperty.Register("ValueList", typeof(string[]), typeof(LoopActivity));
        /// <summary>
        /// The list of values to iterate over. Child activities would be executed for each value in this list, and would be able to access the current value via the CurrentValue property.
        /// </summary>
        [Description("The values to iterate over")]
        [Category("Other")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string[] ValueList
        {
            internal get
            {
                return valueList;
            }
            set
            {
                valueList = value;
            }
        }

        public static DependencyProperty CurrentValueProperty = System.Workflow.ComponentModel.DependencyProperty.Register("CurrentValue", typeof(string), typeof(LoopActivity));
        /// <summary>
        /// The current value of the loop variable. This value changes each iteration and is used by child activities interested in the iteration value.
        /// </summary>
        [Description("The current loop value. Child activities should bind to this value if they are using the loop variable.")]
        [Category("Other")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string CurrentValue
        {
            get
            {
                return ((string)(base.GetValue(LoopActivity.CurrentValueProperty)));
            }
            private set
            {
                base.SetValue(LoopActivity.CurrentValueProperty, value);
            }
        }


    }

    /// <summary>
    /// Validator for the loop activity.
    /// Check that the list of discrete items to iterate over is valid.
    /// </summary>
    public class LoopActivityValidator : ActivityValidator
    {
        public override ValidationErrorCollection ValidateProperties(ValidationManager manager, object obj)
        {
            ValidationErrorCollection errors = new ValidationErrorCollection();
            LoopActivity activityToValidate = obj as LoopActivity;

            if (activityToValidate.Parent != null) // prevent compile time checking.
            {
                if (activityToValidate == null)
                    errors.Add(new ValidationError("object passed in is not a LoopActivity", 1));

                if (activityToValidate.ValueList == null)
                    errors.Add(new ValidationError("Value List not provided (it is null). Please provide a list of values to iterate over.", 2));
                if (activityToValidate.ValueList.Length == 0)
                    errors.Add(new ValidationError("Value List not provided (it is empty). Please provide a list of values to iterate over.", 3));
            }

            return errors;
        }
    }
}



  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

Feedback

# How to add activity to this

Hi,
How can we add custom code activity in here so that within the loop it gets executed everytime. 10/16/2008 5:27 PM | ___

# re: Workflow custom Loop Activity

this is a composite / container activity. You would drag this one to the surface and then you would add the (custom or other) activity into it.

please note that this code was posted in 2006 and new WF updates are not factored here. 10/17/2008 4:34 AM | nuri

# re: Workflow custom Loop Activity

Hi Nuri,
I am truying to implement the loop activity,
However when i drag it on i get a value list not provided error, but where do i set this property and and do i start the loop?
8/11/2009 3:01 AM | Gregory

# re: Workflow custom Loop Activity

Really nice little section of code.

Gregory FYI. To prevent the error that your seeing.

Just amend the following code and you can then bind to the value list in a hosted designer.

Thanks,

Olly

[Description("The values to iterate over")]
[Category("Other")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string[] ValueList
{
//Change the below code
//internal get
get
{
return valueList;
}
set
{
valueList = value;
}
}
12/1/2009 10:39 AM | Oliver Brown

# re: Workflow custom Loop Activity

How can I create an assynchronous parallel activity 11/3/2010 5:39 AM | soussou

Post a comment