Windows Workflow Foundation


Windows Workflow Foundation (WWF)

WWF is a .Net technology added with .Net 3.0. It consists of an in-process workflow runtime engine, a class library and a set of visual designers and tools. WWF is a framework designed for creating and executing system or human workflows.

Here is the list of the workflow assemblies.

And here is the root workflow namespaces.

see also:

Workflows and Activities

A workflow is a group of one or more elemental work items which are required in order for the overall work to be completed. Those work items are combined in many ways in order to form a flow, the workflow, and in WWF they are known as Activities.

A workflow can be depicted as a diagram of steps (activities).

WWF uses two types of workflows:

The SequentialWorkflowActivity class represents a Sequential wokflow, where the StateMachineWorkflowActivity class represents a State Machine workflow.

Microsoft Visual Studio wizards

Microsoft Visual Studio provides project wizards to facilitate the creation of workflows. It is possible to create workflow classes, Workflow Library applications or Workflow Console applications for both types of workflows.

For demonstration purposes the easiest way is to create a workflow console application.

WorkflowRuntime class and WorkflowInstance class

Workflows are actually executed by the WWF runtime engine which is an in-process engine and requires a host application. The workflow runtime engine creates, maintains and executes workflows. It can host and execute multiple workflows concurrently.

The WorkflowRuntime class represents the execution environment of the WWF runtime engine.

    public class WorkflowRuntime : IServiceProvider, IDisposable
    {
        public WorkflowRuntime();
        public WorkflowRuntime(string configSectionName);
        public WorkflowRuntime(WorkflowRuntimeSection settings);

        public bool IsStarted { get; }
        public string Name { get; set; }

        public event EventHandler<ServicesExceptionNotHandledEventArgs> ServicesExceptionNotHandled;
        public event EventHandler<WorkflowRuntimeEventArgs> Started;
        public event EventHandler<WorkflowRuntimeEventArgs> Stopped;
        public event EventHandler<WorkflowEventArgs> WorkflowAborted;
        public event EventHandler<WorkflowCompletedEventArgs> WorkflowCompleted;
        public event EventHandler<WorkflowEventArgs> WorkflowCreated;
        public event EventHandler<WorkflowEventArgs> WorkflowIdled;
        public event EventHandler<WorkflowEventArgs> WorkflowLoaded;
        public event EventHandler<WorkflowEventArgs> WorkflowPersisted;
        public event EventHandler<WorkflowEventArgs> WorkflowResumed;
        public event EventHandler<WorkflowEventArgs> WorkflowStarted;
        public event EventHandler<WorkflowSuspendedEventArgs> WorkflowSuspended;
        public event EventHandler<WorkflowTerminatedEventArgs> WorkflowTerminated;
        public event EventHandler<WorkflowEventArgs> WorkflowUnloaded;

        public void AddService(object service);
        public WorkflowInstance CreateWorkflow(Type workflowType);
        public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader);
        public WorkflowInstance CreateWorkflow(Type workflowType, Dictionary<string, object> namedArgumentValues);
        public WorkflowInstance CreateWorkflow(Type workflowType, Dictionary<string, object> namedArgumentValues, Guid instanceId);
        public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader, XmlReader rulesReader, Dictionary<string, object> namedArgumentValues);
        public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader, XmlReader rulesReader, Dictionary<string, object> namedArgumentValues, Guid instanceId);
        public void Dispose();
        public ReadOnlyCollection<T> GetAllServices<T>();
        public ReadOnlyCollection<object> GetAllServices(Type serviceType);
        public ReadOnlyCollection<WorkflowInstance> GetLoadedWorkflows();
        public T GetService<T>();
        public object GetService(Type serviceType);
        public WorkflowInstance GetWorkflow(Guid instanceId);
        public void RemoveService(object service);
        public void StartRuntime();
        public void StopRuntime();
    }
    
    

An application hosts the WWF runtime engine in order to execute workflows by creating an object of the WorkflowRuntime class.

    using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
    {
        ...
    }
    


Workflows are not executed by the primary thread of the host application. Instead they are executed by threads created by the WWF runtime engine, and in some cases by threads a host application lends to the WWF runtime engine.

A host application uses the WorkflowInstance class in order to control a workflow object either of the SequentialWorkflowActivity or the StateMachineWorkflowActivity class.

    public sealed class WorkflowInstance
    {
        public Guid InstanceId { get; }
        public WorkflowRuntime WorkflowRuntime { get; }

        public void Abort();
        public void ApplyWorkflowChanges(WorkflowChanges workflowChanges);
        public void EnqueueItem(IComparable queueName, object item, IPendingWork pendingWork, object workItem);
        public void EnqueueItemOnIdle(IComparable queueName, object item, IPendingWork pendingWork, object workItem);
        public override bool Equals(object obj);
        public override int GetHashCode();
        public Activity GetWorkflowDefinition();
        public DateTime GetWorkflowNextTimerExpiration();
        public ReadOnlyCollection<WorkflowQueueInfo> GetWorkflowQueueData();
        public void Load();
        public void ReloadTrackingProfiles();
        public void Resume();
        public void Start();
        public void Suspend(string error);
        public void Terminate(string error);
        public bool TryUnload();
        public void Unload();
    }

The WorkflowInstance acts as a proxy to the actual workflow object.

    using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
    {
        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(MyWorkflowClass));
        instance.Start();
    }   


A Workflow class, such as the MyWorkflowClass above, acts a blueprint for a workflow. What actually is executed is the WorkflowInstance. A WorkflowInstance instance is uniquely identified by its InstanceId property of type Guid. That unique value is used to differentiate a running instance of a workflow from other running instances of the same workflow.

The WorkflowRuntime class provides a set of properties, methods and events for controlling the execution environment and getting notifications about running workflows.

    using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
    {
        AutoResetEvent waitHandle = new AutoResetEvent(false);

        /* using anonymous methods as event handlers for WorkflowRuntime events */
        workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) 
        { 
            /* unblock the current thread upon completion of the workflow */
            waitHandle.Set();                 
        };


        workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
        {
            Console.WriteLine(e.Exception.Message);
            waitHandle.Set();
        };

        
        /* create a workflow instance and start it */
        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Lessons.Workflows.Workflow1));
        instance.Start();


        /* block the current thread until workflow termination or completion */
        waitHandle.WaitOne();
    }    
    
    

Most of the events of the WorkflowRuntime class, such as the WorkflowCreated and the WorkflowTerminated, pass a parameter of type WorkflowEventArgs which provides a WorkflowInstance public property.

    public class WorkflowEventArgs : EventArgs
    {
        public WorkflowInstance WorkflowInstance { get; }
    }      

Activities and the Activity class

The elemental item of a workflow is the activity. All activities inherit directly or indirectly from the base Activity class.

The Activity class provides a number of properties such as the Name and QualifiedName string properties and a number of events, not all of them accessible by the Property Grid.

    public class Activity : DependencyObject
    {     
     ...
     
        public string Description { get; set; }
        public bool Enabled { get; set; }
        public ActivityExecutionResult ExecutionResult { get; }
        public ActivityExecutionStatus ExecutionStatus { get; }
        public bool IsDynamicActivity { get; }
        public string Name { get; set; }
        public CompositeActivity Parent { get; }
        public string QualifiedName { get; }

        public event EventHandler<ActivityExecutionStatusChangedEventArgs> Canceling;
        public event EventHandler<ActivityExecutionStatusChangedEventArgs> Closed;
        public event EventHandler<ActivityExecutionStatusChangedEventArgs> Compensating;
        public event EventHandler<ActivityExecutionStatusChangedEventArgs> Executing;
        public event EventHandler<ActivityExecutionStatusChangedEventArgs> Faulting;
        public event EventHandler<ActivityExecutionStatusChangedEventArgs> StatusChanged;     
     
     ...       
    }


An Activity could be a single-action activity or a composite activity. A composite activity contains a group of activities. All composite activities inherit from the CompositeActivity class.

    public class CompositeActivity : Activity
    {

        public CompositeActivity();
        public CompositeActivity(IEnumerable<Activity> children);
        public CompositeActivity(string name);


        public ActivityCollection Activities { get; }
        public ReadOnlyCollection<Activity> EnabledActivities { get; }

    ...
    
    }
    
    

The CompositeActivity class provides the Activities property, of type ActivityCollection class, which is the list of contained children activities. Also, the Activity.Parent property returns the parent CompositeActivity object of a contained child Activity object.

At design-time an activity resembles the behavior of any other visual element which can be dropped on the surface of a visual designer. At run-time the activity executes any specified action.

A number of predefined activity classes is contained in the WWF library. It is also possible to create custom activity classes.

CodeActivity class

The CodeActivity class is a simple activity which provides the ExecuteCode event.

    public event EventHandler ExecuteCode;

The ExecuteCode event executes a, so called, "code-beside" method associated with the activity, in a synchronous manner.

    public sealed class CodeActivity : Activity
    {
        public static readonly DependencyProperty ExecuteCodeEvent;

        public CodeActivity();
        public CodeActivity(string name);

        public event EventHandler ExecuteCode;
    }

SequenceActivity class

The SequenceActivity is a simple CompositeActivity derived class which may have any number of child activities.

The SequenceActivity executes its child activities in sequence, one after the other and is finished when the last child activity is finished.

    public class SequenceActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
    {
        public SequenceActivity();
        public SequenceActivity(string name);

        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext);
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext);
        protected override void OnActivityChangeRemove(ActivityExecutionContext executionContext, Activity removedActivity);
        protected virtual void OnSequenceComplete(ActivityExecutionContext executionContext);
        protected override void OnWorkflowChangesCompleted(ActivityExecutionContext executionContext);
    }

DelayActivity class

DelayActivity class provides a delay mechanism. It pauses workflow execution for a specified interval. It provides the property

    public TimeSpan TimeoutDuration { get; set; }
    

for specifying that interval. Also the event

    public event EventHandler InitializeTimeoutDuration;
    

may be used in order to initialize the delay interval.

    public sealed class DelayActivity : Activity, IEventActivity, IActivityEventListener<QueueEventArgs>
    {
        public static readonly DependencyProperty InitializeTimeoutDurationEvent;
        public static readonly DependencyProperty TimeoutDurationProperty;

        public DelayActivity();
        public DelayActivity(string name);

        public TimeSpan TimeoutDuration { get; set; }

        public event EventHandler InitializeTimeoutDuration;

        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext);
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext);
        protected override sealed ActivityExecutionStatus HandleFault(ActivityExecutionContext executionContext, Exception exception);
        protected override void Initialize(IServiceProvider provider);
        protected override void OnClosed(IServiceProvider provider);
    }

Conditional activities

A number of activity classes provide a property which controls the execution of the activity, based on a specified condition. Those activities actually play the role of an execution flow control mechanism based on conditions.

Here is the list of those classes

Condition evaluation: imperative (by code) and declarative (by XAML)

The above conditional activity classes provide a Condition or UntilCondition property. Additionally the ConditionedActivityGroup attaches a WhenCondition property to its children. That condition property is of type ActivityCondition and can be either a CodeCondition object or a RuleConditionReference object. They both inherit from the ActivityCondition class.

    public abstract class ActivityCondition : DependencyObject
    {
        protected ActivityCondition();

        public abstract bool Evaluate(Activity activity, IServiceProvider provider);
    }
    
 
    public class CodeCondition : ActivityCondition
    {
        public static readonly DependencyProperty ConditionEvent;

        public CodeCondition();

        public event EventHandler<ConditionalEventArgs> Condition;

        public override bool Evaluate(Activity ownerActivity, IServiceProvider provider);
    }
    

    public class RuleConditionReference : ActivityCondition
    {
        public RuleConditionReference();

        public string ConditionName { get; set; }

        public override bool Evaluate(Activity activity, IServiceProvider provider);
    }

The class type of the condition property is selectable at design-time in the Property Grid. Depending on the selected type the Property Grid displays a different set of choices.

The CodeCondition class represents a condition expressed by code (imperatively). It provides the event

    public event EventHandler<ConditionalEventArgs> Condition
    

The condition of the CodeCondition activity is considered true when the Result property of the ConditionalEventArgs event parameter is set to true.

    void actNumberLess_Condition(object sender, ConditionalEventArgs e)
    {
        e.Result = N < 5;
    }
    

The sender parameter is a reference to the activity object, not to the ActivityCondition object.

The RuleConditionReference class represents a condition expressed declaratively, using a design-time editor dialog box and storing rules to a XAML file, the .rules file. So selecting RuleConditionReference as the type of the condition property forces the Property Grid to display a button which leads to a rule editor dialog box.

Ultimately, that condition property, of type ActivityCondition, executes its Evaluate() method in order to evaluate the condition. For the CodeCondition class that Evaluate() method call ends up calling the Condition event. For the RuleConditionReference class that Evaluate() method call forces the evaluation of its declarative rule.

IfElseActivity class and IfElseBranchActivity class

The IfElseActivity class is a composite activity and inherits from the CompositeActivity class. As a CompositeActivity, it provides the Activities property which is an ActivityCollection class. Eventually the IfElseActivity is a list of activities of type IfElseBranchActivity. A list of branches. The IfElseActivity class is not a conditional activity. It's just a container for conditional activities.

An IfElseActivity activity may contain any number of IfElseBranchActivity branches. All those IfElseBranchActivity branches must have their Condition property specified properly. The last branch may or may not have that Condition property specified.

An IfElseActivity routes execution at a branch whose Condition property evaluates to true.

An IfElseActivity evaluates its first (left) IfElseBranchActivity branch first. If that evaluation returns true, then the execution takes that path. Else the IfElseActivity continues by evaluating the next IfElseBranchActivity branch and so on.

An IfElseBranchActivity is a also composite activity and may contain any number of other child activities.

    public sealed class IfElseActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
    {
        public IfElseActivity();
        public IfElseActivity(string name);

        public IfElseBranchActivity AddBranch(ICollection<Activity> activities);
        public IfElseBranchActivity AddBranch(ICollection<Activity> activities, ActivityCondition branchCondition);
        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext);
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext);
    }

    public sealed class IfElseBranchActivity : SequenceActivity
    {
        public static readonly DependencyProperty ConditionProperty;

        public IfElseBranchActivity();
        public IfElseBranchActivity(string name);

        public ActivityCondition Condition { get; set; }
    }

WhileActivity class

The WhileActivity class inherits from CompositeActivity class and it's a composite activity. It can contain just a single child activity, which may be a composite activity though.

The WhileActivity is a conditional activity. It provides a Condition property similar to the one of the IfElseBranchActivity class. The WhileActivity iteratively executes its child activity as long as its Condition property, which is of type ActivityCondition, evaluates to true.

    public sealed class WhileActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
    {
        public static readonly DependencyProperty ConditionProperty;

        public WhileActivity();
        public WhileActivity(string name);

        public ActivityCondition Condition { get; set; }
        public Activity DynamicActivity { get; }

        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext);
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext);
    }

ReplicatorActivity class

The ReplicatorActivity class inherits from CompositeActivity class and it's a composite activity. It can contain just a single child activity, which may be a composite activity though.

    public sealed class ReplicatorActivity : CompositeActivity
    {
        public static readonly DependencyProperty ChildCompletedEvent;
        public static readonly DependencyProperty ChildInitializedEvent;
        public static readonly DependencyProperty CompletedEvent;
        public static readonly DependencyProperty ExecutionTypeProperty;
        public static readonly DependencyProperty InitialChildDataProperty;
        public static readonly DependencyProperty InitializedEvent;
        public static readonly DependencyProperty UntilConditionProperty;

        public ReplicatorActivity();
        public ReplicatorActivity(string name);

        public bool AllChildrenComplete { get; }
        public IList CurrentChildData { get; }
        public int CurrentIndex { get; }
        public ICollection<Activity> DynamicActivities { get; }
        public ExecutionType ExecutionType { get; set; }
        public IList InitialChildData { get; set; }
        public ActivityCondition UntilCondition { get; set; }

        public event EventHandler<ReplicatorChildEventArgs> ChildCompleted;
        public event EventHandler<ReplicatorChildEventArgs> ChildInitialized;
        public event EventHandler Completed;
        public event EventHandler Initialized;

        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext);
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext);
        public bool IsExecuting(int index);
        protected override void OnClosed(IServiceProvider provider);
    }

The ReplicatorActivity is a conditional activity. It provides the UntiCondition property.

The ReplicatorActivity executes its child activity a specified number of times, each time creating an instance (replica) of the child activity.

The property ExecutionType controls the execution mode: Sequence or Parallel. In Sequence mode child activity instances are executed in sequence while in Paraller mode each child activity instance is executed in "parallel". In parallel mode all activities are executed by the same thread though.

The DynamicActivities property returns a collection of the running instances of the child activity at any given time.

The number of elements in the property InitialChildData, of type IList, controls the number of child instances and iterations. This is how the number of iterations is specified.

The Initilized event is triggered just before the ReplicatorActivity starts executing. It's a good place to set the InitialChildData or the ExecutionType and perform any other initialization task.

The Completed event is triggered when ReplicatorActivity finishes executing.

The ChildInitialized event is triggered when a child activity instance is initialized for execution and it is passed the corresponding element from InitialChildData IList property. The ReplicatorChildEventArgs.InstanceData property of that event parameter contains a reference to that InitialChildData element.

The ChildCompleted event is triggered when a child activity instance finishes executing.

The UntilCondition property is of type ActivityCondition and works just like the Condition property of the IfElseBranchActivity and WhileActivity class. It can be either a CodeCondition object or a RuleConditionReference object.

The ReplicatorActivity evaluates its UntilCondition property just before the execution of each child activity instance. It stops execution if the UntilCondition evaluates to true. If the UntilCondition evaluates to false but no more child activity instances are available for execution, ReplicatorActivity hangs.

In that case a workflow change is used in order to add an additional child activity to the ReplicatorActivity activity which will cause the UntilCondition to evaluate to true and to terminate the execution.

ConditionedActivityGroup class

The ConditionedActivityGroup class inherits from CompositeActivity class and it's a composite activity. It can can contain any number of child activities. Those child activities are executed in a loop.

The ConditionedActivityGroup class is a conditional activity. The UntilCondition property controls the continuation of the loop.

A WhenCondition dependency property is attached to each of the child activities of a ConditionedActivityGroup and acts as the familiar Condition property we've seen so far. The WhenCondition is optional and if it is not defined then the child activity will execute only once, the very first time of the loop execution.

The ConditionedActivityGroup re-evaluates its UntilCondition just befofe any child activity execution. The ConditionedActivityGroup loop continues as long as its UntilCondition property returns false and the WhenCondition property of at least one of the child activities returns true.

The ConditionedActivityGroup class has a somewhat bizarre design-time appearance. In the above half of its design-time box it provides a marquee-like rectangle where child activities are dropped. Only a single child activity can be selected at any time. The selected child is displayed in the bottom half of the design-time box. There is also a button in the middle which controls the preview or the edit of the selected child.

    public sealed class ConditionedActivityGroup : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
    {
        public static readonly DependencyProperty UntilConditionProperty;
        public static readonly DependencyProperty WhenConditionProperty;

        public ConditionedActivityGroup();
        public ConditionedActivityGroup(string name);

        public ActivityCondition UntilCondition { get; set; }

        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext);
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext);
        public int GetChildActivityExecutedCount(Activity child);
        public Activity GetDynamicActivity(string childActivityName);
        public static object GetWhenCondition(object dependencyObject);
        protected override void OnActivityChangeAdd(ActivityExecutionContext executionContext, Activity addedActivity);
        protected override void OnActivityChangeRemove(ActivityExecutionContext executionContext, Activity removedActivity);
        protected override void OnClosed(IServiceProvider provider);
        protected override void OnWorkflowChangesCompleted(ActivityExecutionContext executionContext);
        public static void SetWhenCondition(object dependencyObject, object value);
    }

Condition rules: declarative conditions

Conditions may defined declaratively by setting the condition property to be of type RuleConditionReference. In that case the Property Grid displays a button which leads to a "Rule Condition Editor" dialog box.

A conditions is defined by entering a line of code, to that dialog box, which will evaluate to true or false. This is called "rule". That dialog box is smart enough to provide even intellisense support. In writing a rule it is possible to use any code element visible from inside the current workflow object such as fields, properties or methods and language operators. The declaratively defined condition is considered valid as long as it can be represented by types contained in the System.CodeDom namespace and returns true or false.

Condition rules defined that declarative way are stored in a .rules XAML (Extensible Application Markup Language) file bound to the current workflow object which then becomes an embedded resource to the assembly of the project. The XAML representation of those rules in that .rules file is done by using xml element names taken from the System.CodeDom namespace, such as the CodePrimitiveExpression class.

The System.CodeDom namespace contains classes which are able to construct source code in a language neutral way. At run-time the workflow runtime engine reads the .rules file and based on the contained XAML code, generates actual source code elements using CodeDom classes, compiles the generated code and it then evaluates the rules by executing that compiled code.

PolicyActivity class, RuleSet class and Rule class

The PolicyActivity class represents a very special and interesting activity and can be thought of as a collection of "if-then-else" statements.

    public sealed class PolicyActivity : Activity
    {
        public static readonly DependencyProperty RuleSetReferenceProperty;

        public PolicyActivity();
        public PolicyActivity(string name);

        public RuleSetReference RuleSetReference { get; set; }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext);
        protected override void Initialize(IServiceProvider provider);
    }

The PolicyActivity provides the property

    public RuleSetReference RuleSetReference { get; set; }
    

That RuleSetReference object references a RuleSet object, where a RuleSet is a collection of Rule objects. That is actually a PolicyActivity represents a collection of Rule objects.

    public class RuleSet
    {
        public RuleSet();
        public RuleSet(string name);
        public RuleSet(string name, string description);

        public RuleChainingBehavior ChainingBehavior { get; set; }
        public string Description { get; set; }
        public string Name { get; set; }
        public ICollection<Rule> Rules { get; }

        public RuleSet Clone();
        public override bool Equals(object obj);
        public void Execute(RuleExecution ruleExecution);
        public override int GetHashCode();
        public bool Validate(RuleValidation validation);
    }
    

    public class Rule
    {
        public Rule();
        public Rule(string name);
        public Rule(string name, RuleCondition condition, IList<RuleAction> thenActions);
        public Rule(string name, RuleCondition condition, IList<RuleAction> thenActions, IList<RuleAction> elseActions);

        public bool Active { get; set; }
        public RuleCondition Condition { get; set; }
        public string Description { get; set; }
        public IList<RuleAction> ElseActions { get; }
        public string Name { get; set; }
        public int Priority { get; set; }
        public RuleReevaluationBehavior ReevaluationBehavior { get; set; }
        public IList<RuleAction> ThenActions { get; }

        public Rule Clone();
        public override bool Equals(object obj);
        public override int GetHashCode();
    }
 

The Rule class provides three key properties: the RuleCondition, the ThenActions collection and the ElseActions collection. So the "Rule" name is a misnomer. It's more than a rule.

If that RuleCondition property evaluates to true then the actions contained in the ThenActions collection property are executed. Else if that RuleCondition property evaluates to false then the actions contained in the ElseActions collection property are executed.

Eventually a Rule resembles an IF-THEN-ELSE construct. IF the condition is true THEN the "then actions" are executed, ELSE the "else actions" are executed.

At design-time RuleSets and Rules are defined by using the Property Grid. The property RuleSetReference of the PolicyActivity object displays a button which leads to a "Rule Set Editor" dialog box where those rules are defined under a RuleSet name.

The "Rule Set Editor" dialog box displays the Rules collection of a RuleSet in a list box. Rules can be added, edited or deleted. That same dialog box provides text boxes for editing the Condition, the ThenActions and the ElseActions. Regarding Rule syntax the same rules apply here as for the declarative conditions described earlier with the exception that ThenActions and ElseActions may contain more than one line of code. Also the Halt keyword can be used whithin an action code. The Halt command instructs the workflow runtime engine to stop further execution.

The Rule class also provides an Active boolean property and a Priority integer property. The Priority affects the order of evaluation and execution of a Rule. The Rules in a RuleSet object can be thought of as collection of items ordered by the value of their Priority property. The higher the value the higher the Priority of the Rule. Equal Priority Rules are executed in alphabetical order based on the value of their Name property.

Rules Evaluation and Re-evaluation

The RuleSet, not directly available at design-time, provides the property ChainingBehavior which controls the kind of re-evaluation and subsequently the re-execution of its Rules.

A Rule A, with a certain Priority, may be re-evaluated if some other Rule B, with an equal or lower Priority, modifies a field or property which is used by the condition of the Rule A.

At design-time that ChainingBehavior is specified at the "Chaining" combo box or the "Rule Set Editor" dialog box. There are three possibilities:

Here is an example

    //Rule A, Priority 0
    IF N == 5
    THEN DoSomething()
    
    // Rule B, Priority 0
    IF SomeCondition()
    THEN N = 5
    

Based on its Priority, Rule A executes first. If the initial value of the N != 5 then nothing happens, since Rule A has not an ELSE action defined. Rule B comes next and modifies the value of N which is used in the condition of Rule A. If this RuleSet is a Full Chaining one, then this modification forces Rule A to re-evaluate its condition and since now N == 5 its THEN action is executed.

The property RuleReevaluationBehavior of the Rule class it also controls the re-evaluation behavior of a Rule. It is an enumeration with to possible values: Always and Never. The "Rule Set Editor" dialog box contains a "Reevaluation" combo box with just those two possible values, available for each Rule.

Implicit, Explicit and Attribute based re-evaluation

Implicit re-evaluation happens when the RuleSet ChainingBehavior is set to Full Chaining, as in the last example above. The workflow runtime engine analyzes RuleConditions and discovers those dependencies.

Explicit re-evaluation happens when the RuleSet ChainingBehavior is set to Explicit Update Only and a ThenAction or ElseAction uses the Update keyword.

    IF SomeCondition()
    THEN Update(N)
    
    // or Update(this.N) or Update("N") or Update("this/N")
    

The Update keyword supports wildcard characters in the passed parameter.

    THEN Update(this.Customer.*)

Attribute based re-evaluation requires the use of special Attributes which mark methods of the current workflow class. Here is the list

And here is an exampble

    [RuleWrite("N")]
    void Increase()
    {
        N++;
    }
    

...

    IF N < 5
    THEN Increase()
    

The use of attributes help the workflow runtime engine to determine which calls issued by rules affect values used in conditions.

Chaining is dangerous because it is possible to put the WWF rules engine into an infinite loop.

State Machine workflows

A state machine consists of a finite number of states, transitions between states and actions. The state machine is always in one of its defined states. A transition is a state change. An action is an activity which takes place at a given moment.

In WWF the StateMachineWorkflowActivity class represents a state-machine workflow. In WWF a state-machine workflow is driven by events. The EventDrivenActivity class represents such an event, the StateActivity class represents a state where the SetStateActivity class represents a transition. Actions take the form of other activities contained in an EventDrivenActivity object. A StateActivity may optionally contain an initialization and a finalization activity, of type StateInitializationActivity and StateFinalizationActivity respectively.

Here is the list of the classes involved, along with their base classes

A sequential workflow is a series of forward only steps. It is not possible to go back in a passed activity. A state-machine workflow is a group of known states and a state may lead to any backword of forword state, perhaps based on some condition.

In WWF a state-machine workflow has always a start state and may have a final state. Each state can contain any number of events, in the form of EventDrivenActivity objects. Those events may execute some actions and finally they lead to a transition to another state or even the same state. When the final state is reached the worflow is completed.

see also:

State Machine workflows: classes and the workflow designer

Microsoft Visual Studio IDE facilitates the creation of workflows and workflow projects by providing a set of wizards. For demonstration purposes the easiest way is to create a workflow console application of the proper type.

A state-machine workflow may contain a number of StateActivity objects representing states. A StateActivity can contain only the following: any number of EventDrivenActivity objects, an optional StateInitializationActivity, an optional StateFinalizationActivity and any number of other StateActivity objects. If a StateActivity contains child StateActivity objects then it can not be the target of a transition. Only its children can be transition targets.

Double-clicking on a StateActivity presents a local menu which provides a set of menu items. There is a menu item for marking a StateActivity as the initial or final (completed) state of the workflow and others for creating StateInitializationActivity and StateFinalizationActivity activities for the selected state.

A StateActivity has to contain at least a single EventDrivenActivity object. That local menu provides the "Add EvenDriven" menu item for that sake. Clicking the "Add EvenDriven" menu item adds an EventDrivenActivity to the state and, at the same time, presents a "detail view" designer of that EventDrivenActivity activity. That detail view provides a clickable option which returns back to the workflow designer. Double-clicking on an EventDrivenActivity inside a StateActivity displays the detail-view again. A StateActivity may contain multiple EventDrivenActivity objects.

An EventDrivenActivity can contain any number of other activities dropped to it. This is done while in the EventDrivenActivity detail-view and by dragging and dropping activities from the Tool Box to the EventDrivenActivity box.

There is a restriction regarding EventDrivenActivity child activities though. The very first activity must implement the IEventActivity interface. From the pre-defined activities there are just three which meet this condition: the DelayActivity, the HandleExternalEventActivity, and the WebServiceInputActivity. The DelayActivity is the easier choise. It can be dropped on an EventDrivenActivity with its TimeoutDuration property set to zero.

A StateActivity may contain zero or more SetStateActivity objects. A SetStateActivity activity is a transition activity, which means it leads execution to another state. The SetStateActivity class provides the property TargetStateName where that "next" state is defined by its name.

Workflow parameters

Using an overloaded version of the WorkflowRuntime.CreateWorkflow() method

    public WorkflowInstance CreateWorkflow(Type workflowType, Dictionary<string, object> namedArgumentValues);
    

it is possible to pass a Dictionary<string, object> instance containing initial values for some or all of the public properties of the workflow being started. Each item in the passed dictionary is a KeyValuePair<string, object> object, where the Key is a string having the name of a public property of the workflow.

    Dictionary<string, object> Params = new Dictionary<string, object>();
    Params.Add("Param", "Hi there");    

    WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Lessons.Workflows.Workflow1), Params);
    instance.Start();
    

In the above example, the "Param" should be the name of a public string property in the Workflow1 or an exception is thrown.

When the workflow is completed, then it is possible to access any of its public properties from inside of an event handler for the WorkflowCompleted event of the WorkflowRuntime class.

    workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
    {
        /* accessing a public property of the workflow by name, after workflow completion */
        string S = (string)e.OutputParameters["Param"];
        Console.WriteLine(S);

        waitHandle.Set();             
    };    

Windows Workflow Foundation services

WWF services are classes the workflow runtime engine uses in order to provide additional functionality to the running workflows and the workflow hosts.

WWF provides a number of predefined services such as the communication service which makes it possible for a workflow to communicate with its host application. Other predefined services used by WWF are the persistence service, the scheduling service, the tracking service and the transaction service. All WWF services inherit from WorkflowRuntimeService class.

Here is a list of the predefined service classes.

Besides those predefined services it is possible to have custom services by inheriting from the base service classes.

Services are pluggable, that is they used in a plug-in like way by the workflow runtime.

Local Communication services

Local Communication services enable communication between a running workflow and a host application. The ExternalDataExchangeService class is the fundamental building block of this communication devise.

    /* 1. create an ExternalDataExchangeService object   */
    ExternalDataExchangeService ExchangeService = new ExternalDataExchangeService();

    /* 2. register the ExternalDataExchangeService object with the workflowRuntime   */
    workflowRuntime.AddService(ExchangeService);

Local Communication services: CallExternalMethodActivity

The CallExternalMethodActivity is used by a workflow in order to call an externally defined method. The external method is defined by an interface type marked by the ExternalDataExchangeAttribute attribute. That interface is considered the communication contract between the two ends, the host and the workflow, and is implemented by a custom class which is considered as the local communication service. There can be only a single impementation of that interface.

    /* the interface used as a contract between workflow host and workflow runtime engine */
    [ExternalDataExchange]
    public interface IWorkflowInfo
    {
        void InformHost(string Text);
    }

    /* a class implementing the contract interface. Only one implementation is allowed */
    public class WorkflowInfo : IWorkflowInfo
    {
        public void InformHost(string Text)
        {
            Console.WriteLine(Text);
        }
    }

Then an object of the implementor class is registered with the already registered ExternalDataExchangeService service.

    /* 3. create an instance of the a WorkflowInfo service.
       in a real-world situation we may use a parameterized constructor here 
       in order to pass some info to the WorkflowInfo instance regarding host */
    WorkflowInfo WI = new WorkflowInfo();

    /* 4. register a WorkflowInfo service with the ExternalDataExchangeService object   */
    ExchangeService.AddService(WI);
    

The CallExternalMethodActivity provides the property InterfaceType. The Property Grid displays a button which leads to a dialog box allowing the user to select the interface to be used as the communication contract. Another property, the MethodName is used to defined the name of the external method that belongs to that interface. After selecting the method name the Property Grid displays additional "properties" for binding the parameters that method requires to any available property of the workflow or any other available object.

The CallExternalMethodActivity provides also the event

    public event EventHandler MethodInvoking;

which is called just before the invocation of the external method.

Local Communication services: HandleExternalEventActivity

The HandleExternalEventActivity is used by a workflow in order to respond to an event triggered externally by the host application. The external event is defined by an interface type marked by the ExternalDataExchangeAttribute attribute. That interface is considered the communication contract between the two ends, the host and the workflow, and is implemented by a custom class which is considered as the local communication service. There can be only a single impementation of that interface.

    /* the interface used as a contract between workflow host and workflow runtime engine 
       It now contains an event too */
    [ExternalDataExchange]
    public interface IWorkflowInfo
    {
        void InformHost(string Text);

        event EventHandler<WorkflowInfoArgs> InfoAvailable;
    }
    
    public class WorkflowInfo : IWorkflowInfo
    {
        public void InformHost(string Text)
        {
            Console.WriteLine("WorkflowInfo: " + Text);


            WorkflowInfoArgs Args = new WorkflowInfoArgs(WorkflowEnvironment.WorkflowInstanceId, Text);

            if (InfoAvailable != null)
                InfoAvailable(null, Args);
        }

        public event EventHandler<WorkflowInfoArgs> InfoAvailable;
    }

The event used should be of type

    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
    

where as type argument of the TEventArgs type parameter can be used an ExternalDataEventArgs object or a derived type. Here is an implementation of an ExternalDataEventArgs class.

    [Serializable]
    public class WorkflowInfoArgs : ExternalDataEventArgs
    {
        private string text = "";

        public WorkflowInfoArgs(Guid instanceID, string Text)
            : base(instanceID)
        {
            text = Text;
        }

        public string Text { get { return text; } set { text = value; } }
    }

The instanceID of type Guid is a required parameter by the base constructor and is a unique ID of a workflow instance. The workflow runtime engine uses that ID in order to locate the right workflow instance when calling the event.

An object of that interface implementor class is registered with the already registered ExternalDataExchangeService service.

    WorkflowInfo WI = new WorkflowInfo();
    ExchangeService.AddService(WI);
    

The HandleExternalEventActivity provides the property InterfaceType. The Property Grid displays a button which leads to a dialog box allowing the user to select the interface to be used as the communication contract. Another property, the EventName is used to defined the name of the external event that belongs to that interface.

The HandleExternalEventActivity provides also the event

    public event EventHandler<ExternalDataEventArgs> Invoked;

which occurs when the external event is triggered.

    /* this is the event handler of the external event */
    private void handleExternalEventActivity1_Invoked(object sender, ExternalDataEventArgs e)
    {
        WorkflowInfoArgs args = e as WorkflowInfoArgs;
        Console.WriteLine("Workflow event handler: " + args.Text );
    }
    

The HandleExternalEventActivity is a blocking activity. It blocks workflow execution and waits until the specified event is triggered.

The ListenActivity class

The ListenActivity can be used to listen for multiple external events.

The ListenActivity is a CompositeActivity that can only contain EventDrivenActivity activities. It must have at least two EventDrivenActivity activities. Eventually the ListenActivity blocks the execution of the workflow and starts waiting until one of the events it contains to be triggered.

The EventDrivenActivity activities are SequenceActivity descendants and can contain a list of other activities. The only restriction is that its first child should implement the IEventActivity Interface. It happens than the HandleExternalEventActivity implements that IEventActivity Interface.

So a ListenActivity is actually a list of HandleExternalEventActivity branches. Each branch waits for a specific event to occur. When an event occurs the corresponding event handler is executed and all other branches are cancelled and stop listening for external events.

A ListenActivity can be used only with sequential workflows. Not with state machines.

Hosting workflows in Windows.Forms applications

Workflows going to be hosted in a Windows.Forms application, are placed in a separate workflow library. Then the host application uses a reference to that workflow library assembly.

Also the host application should have proper references to WWF assemblies such as the System.Workflow.Runtime.dll etc.

Passing information and data from a running workflow to Windows.Forms controls must be done in a synchronized manner since the workflow and the host application are executed in different threads.

A common trick is to use methods which check to see if InvokeRequired and if yes they call themselves a second time using Invoke().

    private delegate void WorkflowDoneDelegate();

    private void WorkflowDone()
    {
        if (this.InvokeRequired)
        {
            /* if synchronization is required, then re-call itself through an invoked delegate */
            WorkflowDoneDelegate d = delegate() { WorkflowDone(); };
            this.Invoke(d);
        }
        else
        {
            btnStartWorkflow.Enabled = true;
            lboInfo.Items.Add("-----------");
        }
    }

Persistence services

A workflow instance can be unloaded from the memory and saved to a persisted storage medium. Later on it can be loaded into memory again and continue execution. WWF Persistence services provide this kind of functionality.

Persistence service classes inherit from the abstract WorkflowPersistenceService class and implement its abstract methods.

    public abstract class WorkflowPersistenceService : WorkflowRuntimeService
    {
        protected WorkflowPersistenceService();

        protected static byte[] GetDefaultSerializedForm(Activity activity);
        protected internal static bool GetIsBlocked(Activity rootActivity);
        protected internal static string GetSuspendOrTerminateInfo(Activity rootActivity);
        protected internal static WorkflowStatus GetWorkflowStatus(Activity rootActivity);
        protected internal abstract Activity LoadCompletedContextActivity(Guid scopeId, Activity outerActivity);
        protected internal abstract Activity LoadWorkflowInstanceState(Guid instanceId);
        protected static Activity RestoreFromDefaultSerializedForm(byte[] activityBytes, Activity outerActivity);
        protected internal abstract void SaveCompletedContextActivity(Activity activity);
        protected internal abstract void SaveWorkflowInstanceState(Activity rootActivity, bool unlock);
        protected internal abstract bool UnloadOnIdle(Activity activity);
        protected internal abstract void UnlockWorkflowInstanceState(Activity rootActivity);
    }



A workflow can be persisted when

The "persist on delay" case happens when a DelayActivity is encountered and a persistence service, with its UnloadOnIdle flag set to true, is already added to the workflow runtime. The workflow instance is persisted when the DelayActivity starts waiting and is loaded back again when the TimeoutDuration of the DelayActivity elapses.

To explicitly persist a workflow instance, the Unload(), TryUnload() and Load() methods of the WorklflowInstance class are used. Those methods are called from the host application side.

The WorkflowInstance.Unload() unloads the workflow instance from the memory and persists it using the provided persistence service. This call blocks execution on the callind thread if the workflow executes an activity and waits until the activity completes, before proceed.

The WorkflowInstance.TryUnload() tries to unload and persist the workflow instance. This call does not block though. It returns true on success, false otherwise. False is returned if the workflow instance can not be persisted at the moment of call because it is not idle or is not suspended or it is already completed.

The WorkflowInstance.Load() loads a previously unloaded and persisted workflow instance based on the InstanceId of the workflow instance.

WWF provides a predefined persistence service, the SqlWorkflowPersistenceService class, which persists workflow instances to a MS SQL server database. Two sql scripts, found at <%WINDIR%>\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL help in creating the required database the SqlWorkflowPersistenceService uses to persist workflows.

A persistence service is added to the workflow runtime just like any other service.

    workflowRuntime.AddService(new SqlWorkflowPersistenceService(ConnectionString));

WWF supports binary formatting only in persisting workflow instances. The Activity class provides the Save() and Load() methods in order to serialize a tree of activities.

    using (FileStream FS = new FileStream(FileName, FileMode.CreateNew))
    {
        IFormatter formatter = new BinaryFormatter();
        formatter.SurrogateSelector = ActivitySurrogateSelector.Default;
        activity.Save(FS, formatter);
    }
    

...

    using (FileStream FS = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        FS.Position = 0;
        IFormatter formatter = new BinaryFormatter();
        formatter.SurrogateSelector = ActivitySurrogateSelector.Default;
        return Activity.Load(FS, activity, formatter);
    }             


Serialization surrogates are classes that implement the ISerializationSurrogate interface and they know how to serialize objects of a some particular type. SurrogateSelector classes are selector objects that assist formatters in selecting a proper serialization surrogate for a certain type. The ActivitySurrogateSelector class is a surrogate selector for Activity classes. The ActivitySurrogateSelector.Default property provides a default internal implementation of a serialization surrogate.

Here is a simple custom persistence service class.

    /* a custom persistence service class which saves workflow instances to disk files */
    public class CustomPersistenceService : WorkflowPersistenceService
    {
        private string folder;
        private bool unloadOnIdle = false;

        /* creates a full filename for the serialization data file */
        string CreateFileName(Guid ID)
        {
            return Path.Combine(folder, ID.ToString() + ".wwf");
        }


        /* serializes and saves the activity to a disk file.     
     
            There may be situations where multiple instances of the workflow runtime engine share the
            same datastore when persisting workflows. WWF provides a locking mechanism in order to prevent
            persistence services that run in two different processes from loading the same workflow instance 
            into memory at the same time. If a custom persistence service is to support that locking mechanism 
            then the LoadWorkflowInstanceState() method must throw a WorkflowOwnershipException if the workflow
            being loaded is locked by another process. It also must lock the datastore while loading the workflow.
            The UnlockWorkflowInstanceState() method unlocks a previously locked instance. Also the SaveWorkflowInstanceState() 
            method provides the unlock boolean parameter to indicate whether state information of a workflow instance 
            being serialized should remain in an un-locked state while in the datastore. 
         */
        void Save(Activity activity, bool unlock)
        {
            Guid ID = (Guid)activity.GetValue(Activity.ActivityContextGuidProperty);

            string FileName = this.CreateFileName(ID);

            if (File.Exists(FileName))
                File.Delete(FileName);

            using (FileStream FS = new FileStream(FileName, FileMode.CreateNew))
            {
                /*  
                    Serialization surrogates are classes that implement the ISerializationSurrogate interface 
                    and they know how to serialize objects of a some particular type. SurrogateSelector
                    classes are selector objects that assist formatters in selecting a proper serialization
                    surrogate for a certain type. The ActivitySurrogateSelector class is a surrogate selector
                    for Activity classes. The ActivitySurrogateSelector.Default property provides a default
                    internal implementation of a serialization surrogate
                 */
                IFormatter formatter = new BinaryFormatter();
                formatter.SurrogateSelector = ActivitySurrogateSelector.Default;
                activity.Save(FS, formatter);
            }


            if (!unlock)
                File.SetAttributes(FileName, FileAttributes.ReadOnly);
        }


        /* locates a disk file, based on the ID, and loads the data to the activity */
        Activity Load(Guid ID, Activity activity)
        {
            string FileName = this.CreateFileName(ID);

            using (FileStream FS = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                /* see the comment for surrogates in the Save() method */
                FS.Position = 0;
                IFormatter formatter = new BinaryFormatter();
                formatter.SurrogateSelector = ActivitySurrogateSelector.Default;
                return Activity.Load(FS, activity, formatter);
            }
        }



        public CustomPersistenceService(string Folder)
        {
            this.folder = Folder;
        }

        public CustomPersistenceService(string Folder, bool unloadOnIdle)
            : this(Folder)
        {
            this.unloadOnIdle = unloadOnIdle;
        }


        protected override bool UnloadOnIdle(Activity activity)
        {
            return this.unloadOnIdle;
        }

        protected override void SaveWorkflowInstanceState(Activity rootActivity, bool unlock)
        {
            this.Save(rootActivity, unlock);
        }

        protected override Activity LoadWorkflowInstanceState(Guid instanceId)
        {
            return Load(instanceId, null);
        }

        protected override void UnlockWorkflowInstanceState(Activity rootActivity)
        {
            Guid ID = (Guid)rootActivity.GetValue(Activity.ActivityContextGuidProperty);
            string FileName = this.CreateFileName(ID);
            using (FileStream FS = new FileStream(FileName, FileMode.Open))
            {
                File.SetAttributes(FileName, FileAttributes.Normal);
            }
        }

        protected override void SaveCompletedContextActivity(Activity activity)
        {
            this.Save(activity, true);
        }

        protected override Activity LoadCompletedContextActivity(Guid scopeId, Activity outerActivity)
        {
            return Load(scopeId, outerActivity);
        }

    }

    

Tracking services

In WWF Tracking services are used to capture and record information regarding workflow execution. The recorded information may include workflow events, activity state changes and other information. The tracking service could write that recorded information to a log file or to a database and it also has the possibility to filter the recorded information by using a tracking profile which is represented by the TrackingProfile class.

The tracking service base class is the TrackingService class.

    public abstract class TrackingService : WorkflowRuntimeService
    {
        protected TrackingService();
        protected internal abstract TrackingProfile GetProfile(Guid workflowInstanceId);
        protected internal abstract TrackingProfile GetProfile(Type workflowType, Version profileVersionId);
        protected internal abstract TrackingChannel GetTrackingChannel(TrackingParameters parameters);
        protected internal abstract bool TryGetProfile(Type workflowType, out TrackingProfile profile);
        protected internal abstract bool TryReloadProfile(Type workflowType, Guid workflowInstanceId, out TrackingProfile profile);
    }

WWF provides a predefined tracking service, the SqlTrackingService class, which records tracking information into a MS SQL server database. Two sql scripts, found at <%WINDIR%>\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL help in creating the database the SqlTrackingService uses.

A tracking service is added to the workflow runtime just like any other service.

    workflowRuntime.AddService(new SqlTrackingService(ConnectionString));    
    

The SqlTrackingQuery class is used to query the MS SQL database and retrieve tracking information. The information is returned as a SqlTrackingWorkflowInstance object. The returned SqlTrackingWorkflowInstance object contains all the available tracking information regarding a specified workflow instance ID.

    SqlTrackingQuery q = new SqlTrackingQuery(ConString); 

    SqlTrackingWorkflowInstance stwi;
    q.TryGetWorkflow(instanceId, out stwi);
    
    if (stwi != null)
    {
        lboInfo.Items.Add(string.Format("Tracking information for {0}", instanceId)); 

        lboInfo.Items.Add("Workflow events ===============================");
        foreach (WorkflowTrackingRecord wtr in stwi.WorkflowEvents)
            lboInfo.Items.Add(string.Format("({0})   Status: {1}", wtr.EventDateTime, wtr.TrackingWorkflowEvent));

        lboInfo.Items.Add("Activity events ===============================");
        foreach (ActivityTrackingRecord atr in stwi.ActivityEvents)
            lboInfo.Items.Add(string.Format("({0})   Activity: {1}   Status: {2}", atr.EventDateTime, atr.QualifiedName, atr.ExecutionStatus));
    }
    
    
     

The tracking information the workflow runtime sends to the tracking service for recording can be filtered by using tracking profiles which are lists of track points. A tracking profile is stored into the MS SQL database using the UpdateTrackingProfile stored procedure contained in the sql scripts used to create the database.

    TrackingProfile profile = new TrackingProfile();
    profile.Version = new Version("1.0.0");

    /* ActivityTrackingLocation defines the activities and their events we want to track */
    ActivityTrackingLocation atl = new ActivityTrackingLocation(typeof(Activity));
    atl.MatchDerivedTypes = true;
    atl.ExecutionStatusEvents.Add(ActivityExecutionStatus.Executing);

    /* ActivityTrackPoint defines a activity related point of interest in the potential execution path of a workflow */
    ActivityTrackPoint atp = new ActivityTrackPoint();
    atp.MatchingLocations.Add(atl);
    profile.ActivityTrackPoints.Add(atp);

    /* WorkflowTrackPoint defines a workflow related point of interest in the potential execution path of a workflow */
    WorkflowTrackPoint wtp = new WorkflowTrackPoint();

    Array events = Enum.GetValues(typeof(TrackingWorkflowEvent));
    foreach (TrackingWorkflowEvent ev in events)
        wtp.MatchingLocation.Events.Add(ev);

    profile.WorkflowTrackPoints.Add(wtp);

    StringWriter SW = new StringWriter(new StringBuilder());
    TrackingProfileSerializer serializer = new TrackingProfileSerializer();
    serializer.Serialize(SW, profile);


    using (SqlConnection Con = new SqlConnection(ConString))
    {

        SqlCommand Cmd = new SqlCommand();
        Cmd.Connection = Con;
        Cmd.CommandType = CommandType.StoredProcedure;
        Cmd.CommandText = "dbo.UpdateTrackingProfile";

        Cmd.Parameters.Add(new SqlParameter("@TypeFullName", typeof(Lessons.Workflows.Workflow1).ToString()));
        Cmd.Parameters.Add(new SqlParameter("@AssemblyFullName", typeof(Lessons.Workflows.Workflow1).Assembly.FullName));
        Cmd.Parameters.Add(new SqlParameter("@Version", "1.0.0"));
        Cmd.Parameters.Add(new SqlParameter("@TrackingProfileXml", SW.ToString()));

        Con.Open();
        Cmd.ExecuteNonQuery();
    }
    

Scheduling services

In WWF Scheduling services schedule the execution of workflows in regard to threads. There are two predefined scheduling services, the DefaultWorkflowSchedulerService and the ManualWorkflowSchedulerService. All scheduling services derive from the base abstract class WorkflowSchedulerService.

    public abstract class WorkflowSchedulerService : WorkflowRuntimeService
    {
        protected WorkflowSchedulerService();

        protected internal abstract void Cancel(Guid timerId);
        protected internal abstract void Schedule(WaitCallback callback, Guid workflowInstanceId);
        protected internal abstract void Schedule(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId);
    }
    

By default the workflow runtime uses the DefaultWorkflowSchedulerService which schedule workflows to run on threads coming from the CLR's thread pool. There is a thread pool per process. Thus, by default, workflows execute asynchronously on a background thread.

    public class DefaultWorkflowSchedulerService : WorkflowSchedulerService
    {
        public DefaultWorkflowSchedulerService();
        public DefaultWorkflowSchedulerService(int maxSimultaneousWorkflows);
        public DefaultWorkflowSchedulerService(NameValueCollection parameters);

        public int MaxSimultaneousWorkflows { get; }

        protected internal override void Cancel(Guid timerId);
        protected override void OnStarted();
        protected internal override void Schedule(WaitCallback callback, Guid workflowInstanceId);
        protected internal override void Schedule(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId);
        protected internal override void Stop();
    }

The ManualWorkflowSchedulerService schedule workflows to run on a thread the host application lends to the workflow runtime. That thread is the primary thread of the host application, so the workflow is executed in a synchronous mode. This means that execution of the host application is blocked until the workflow instance becomes idle or completed.

    public class ManualWorkflowSchedulerService : WorkflowSchedulerService
    {
        public ManualWorkflowSchedulerService();
        public ManualWorkflowSchedulerService(bool useActiveTimers);
        public ManualWorkflowSchedulerService(NameValueCollection parameters);

        protected internal override void Cancel(Guid timerId);
        protected override void OnStarted();
        public bool RunWorkflow(Guid workflowInstanceId);
        protected internal override void Schedule(WaitCallback callback, Guid workflowInstanceId);
        protected internal override void Schedule(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId);
        protected internal override void Stop();
    }

Here is how to use the ManualWorkflowSchedulerService service.

    workflowRuntime2 = new WorkflowRuntime();

    scheduler = new ManualWorkflowSchedulerService();
    workflowRuntime2.AddService(scheduler);

    instance = workflowRuntime2.CreateWorkflow(typeof(Lessons.Workflows.Workflow1));
    instance.Start();
    scheduler.RunWorkflow(instance.InstanceId);  
    

It is also possible to create custome scheduling services by inheriting from the base WorkflowSchedulerService class.

NOTE: The WWF runtime engine uses one thread per workflow instance. This is true even when a workflow contains a ParallelActivity.

Faults: throwing and handling

The ThrowActivity class is used to throw an exception. The ThrowActivity provides the FaultType property which gets or sets the exception type. The Property Grid displays a button which leads to the "Browse and Select a .Net type" dialog box in order to help the user to locate a proper Exception class.

Any unhandled exception triggers the WofkflowRuntime.WorkflowTerminated event and terminates abnormally the execution of the running workflow.

Exception handling inside workflows is performed by the special FaultHandlerActivity class. The FaultHandlerActivity class can be a child to a composite activity only.

A FaultHandlerActivity is similar to a catch clause in a try-catch block. Like the ThrowActivity it provides a FaultType property which gets or sets the exception type to catch.

There is also the FaultHandlersActivity class which is a composite activity which may contain any number of FaultHandlerActivity activities and other activities.

Fault handler activities used in two places: at workflow level and at a local level, where local level is the scope of a composite activity.

While in the workflow designer, the workflow and the composite activities provide a local menu, accessible by left clicking the icon of the item, which contains the "View Fault Handlers" menu item. That menu item provides a "fault handling view" of the selected item. That view is actualy a FaultHandlersActivity composite activity where the user can place FaultHandlerActivity items for handling specific fault types, and other activity types.

Cancellations: CancellationHandlerActivity

Cancellation works in the same manner as the fault handling. It is defined in global or local level using the workflow designer.

Cancellation happens when a composite activity is canceled before all its child activities complete execution.

CancellationHandlerActivity is the parent for child activities which contain cancellation behavior, that is cleanup code for a cancelled composite activity or a cancelled workflow.

Transactions: TransactionScopeActivity

The TransactionScopeActivity defines a transaction scope inside a workflow. Activities enclosed by a TransactionScopeActivity are executed inside a transaction.

The TransactionScopeActivity activity uses internally types from the System.Transactions namespace and it will automatically roll back any action in the case of an error.

The TransactionScopeActivity can not contain other TransactionScopeActivity items and it requires the presence of a persistence service in order to work.

WWF dependency property system

All Activity classes inherit from the base System.Workflow.ComponentModel.DependencyObject class. The DependencyObject class is the base class for all classes that have dependency properties.

A dependency property is a special property defined as a static public field using special notation. Here is an example of a proper definition of a dependency property.

    public class DependencyActivity: Activity
    {
        public static DependencyProperty TextProperty = DependencyProperty.Register("Text", 
                                                                                    typeof(string), 
                                                                                    typeof(DependencyActivity), 
                                                                                    new PropertyMetadata("<default text value>")
                                                                                    );
            
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        public string Text
        {
            get
            {
                return ((string)(base.GetValue(TextProperty)));
            }
            set
            {
                base.SetValue(TextProperty, value);
            }
        }
    }

The PropertyMetadata is an object which is used by CLR in order to get reflection-like information regarding the property.

A DependencyObject instance keeps a hash table that stores the current values of any dependency property applied to it. A dependency property "backs up" a CLR property. In the above example the TextProperty dependency property backs up the Text CLR property.

Dependency properties can be used in XAML code, that is in setting property values declaratively. Also dependency properties can be used as attached properties, if they defined by using the DependencyProperty.RegisterAttached() instead of the DependencyProperty.Register() method.s

Attached properties are properties of an outer object that appear to be as properties of an inner object. In some way they extend any contained object. For example the ConditionedActivityGroup attaches a WhenCondition property to each contained child. Attached properties can be used in XAML. The also used by the Property Grid. They can not be used at run-time though.

A dependency property supports a special type of binding. It can be bound to a field, property or method of another object visible to the dependence property. The actual value of a property which is bound to a target, is not determined until run-time. The Property Grid displays a button which leads to a "Bind ...." dialog box for the user to select the binding target.

There are two types of activity properties: metadata properties and instance properties.

A metadata property is immutable at run-time, thus it must be set at design-time. A metadata property can not be bound to a target. Metadata properties exist to guarantee the integrity of an activity. The Name property of an Activity or the InterfaceType of a HandleExternalEventActivity are examples of metadata properties.

An instance property can be set at run-time or it can be bound to a target. An instance property can be either a plain CLR property or a dependency property.

Any type used in activity properties must be marked as serializable.

Custom Activities

It is possible to create custom activity classes by inheriting from the predefined activity classes and by adding custom execution logic, properties and events in order to encapsulate common functionality.

A custom activity overrides the base Execute() method which returns an ActivityExecutionStatus enumeration value to indicate the status of the activity after the method completes.

    [ActivityValidator(typeof(InputActivityValidator))]

public partial class InputActivity: Activity {

        string title = "<dialog title>";
        string text = "<dialog text>";
        string input = "";
        bool result = false;        

public InputActivity() { InitializeComponent(); }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext context)
        {
            if (BeforeDialog != null)
                BeforeDialog(this, EventArgs.Empty);

            result = InputDialog.Execute(Title, Text, ref input);


            if (AfterDialog != null)
                AfterDialog(this, EventArgs.Empty); 

            /* return a status to indicate that the activity is complete */
            return ActivityExecutionStatus.Closed;
        }


        /* properties */
        [Browsable(true)]
        public string Title { get { return title; } set { title = value; } }

        [Browsable(true)]
        public string Text { get { return text; } set { text = value; } }

        [Browsable(true)]
        public string Input { get { return input; } set { input = value; } }

        [Browsable(true)]
        public bool Result { get { return result; } set { result = value; } }


        /* events */
        [Browsable(true)]
        public event EventHandler BeforeDialog;

        [Browsable(true)]
        public event EventHandler AfterDialog;

}

An ActivityValidator derived class is used in order to validate property values of an activity at design-time and at run-time

    /* a simple validator for InputActivity properties */
    public class InputActivityValidator : ActivityValidator
    {
        public override ValidationErrorCollection ValidateProperties(ValidationManager manager, object obj)
        {
            /* errorList is the return data and stores any errors found  */
            ValidationErrorCollection errorList = base.ValidateProperties(manager, obj);

            InputActivity a = obj as InputActivity;
            if (a != null)
            {
                if (string.IsNullOrEmpty(a.Title))
                    errorList.Add(ValidationError.GetNotSetValidationError("Title"));

                if (string.IsNullOrEmpty(a.Text))
                    errorList.Add(ValidationError.GetNotSetValidationError("Text"));

            }

            return errorList;
        }
    }


A custom activity class may or may not contain dependency properties.

public partial class InputDependencyActivity: Activity {

        public static DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(InputDependencyActivity), new PropertyMetadata("Dialog box title"));
        public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(InputDependencyActivity), new PropertyMetadata("Dialog box text"));
        public static DependencyProperty InputProperty = DependencyProperty.Register("Input", typeof(string), typeof(InputDependencyActivity), new PropertyMetadata("Dialog box input"));
        public static DependencyProperty ResultProperty = DependencyProperty.Register("Result", typeof(bool), typeof(InputDependencyActivity));

        public static DependencyProperty BeforeDialogEvent = DependencyProperty.Register("BeforeDialog", typeof(EventHandler<EventArgs>), typeof(InputDependencyActivity), new PropertyMetadata(null));
        public static DependencyProperty AfterDialogEvent = DependencyProperty.Register("AfterDialog", typeof(EventHandler<EventArgs>), typeof(InputDependencyActivity), new PropertyMetadata(null));
        

        public InputDependencyActivity()

{ InitializeComponent(); }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext context)
        {
 
            base.RaiseGenericEvent<EventArgs>(BeforeDialogEvent, this, EventArgs.Empty);

            string S = Input;
            Result = InputDialog.Execute(Title, Text, ref S);

            if (Result)
                Input = S;
 
            base.RaiseGenericEvent<EventArgs>(AfterDialogEvent, this, EventArgs.Empty);

            /* return a status to indicate that the activity is complete */
            return ActivityExecutionStatus.Closed;
        }


        /* properties */
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        public string Title
        {
            get
            {
                return ((string)(base.GetValue(TitleProperty)));
            }
            set
            {
                base.SetValue(TitleProperty, value);
            }
        }
                
        ...



        /* events */
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        public event EventHandler<EventArgs> BeforeDialog
        {
            add
            {
                base.AddHandler(BeforeDialogEvent, value);
            }
            remove
            {
                base.RemoveHandler(BeforeDialogEvent, value);
            }
        }

        ...

}

Compensation (undoing after an exception)

Compensation is the rollback operation of any successfully completed activity actions, because of an exception which is thrown somewhere in the workflow.

Activities inside a CompensatableSequenceActivity or a CompensatableTransactionScopeActivity activity are compensatable. Those two activities implement the ICompensatableActivity interface which is required for a compensatable activity.

An activity which implements the ICompensatableActivity interface can contain a single CompensationHandlerActivity as a child. A CompensationHandlerActivity is a CompositeActivity and can contain any number of child activities which perform the actual compensation actions. That CompensationHandlerActivity and its children will be executed in case of a compensation because of an exception.

The CompensatableSequenceActivity and the CompensatableTransactionScopeActivity provide a "View Compensation Handler" menu item, just like the "View Fault Handlers" menu item, which displays a view of their CompensationHandlerActivity and where other activities can be added.

The CompensatableSequenceActivity is used when no transactions are involved to the execution. The CompensatableTransactionScopeActivity is used with transactions and it requires the presence of a persistence service registered with the runtime.

Workflow configuration files

A workflow host application can use a configuration file, app.config or web.config, in order to configure the behavior of certain workflow runtime objects and other aspects related to workflows.

Here is an example app.config

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>

        <configSections>
            <section name="MyWorkflowRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken 31bf3856ad364e35" />
        </configSections>
        
        <MyWorkflowRuntime>
            <CommonParameters>          
                <!-- parameters used by all workflow services such as connection strings -->
                <add name="ConnectionString" value="Initial Catalog=WorkflowStore; Data Source=localhost; Integrated Security=SSPI;" />
            </CommonParameters>
            
            <Services>         

                <!-- workflow services -->

            </Services>
        </MyWorkflowRuntime>

        <system.diagnostics>
            <switches>
                  <add name="System.Workflow.Runtime" value="All" />
                  <add name="System.Workflow.Runtime.Hosting" value="All" />
                  <add name="System.Workflow.Runtime.Tracking" value="Critical" />
                  <add name="System.Workflow.Activities" value="Warning" />
                  <add name="System.Workflow.Activities.Rules" value="Off" />

                  <add name="System.Workflow LogToFile" value="1" />
            </switches>
        </system.diagnostics>
        
    </configuration>


The <configSections> specifies configuration section and namespace declarations. The <section> element associates a section element to another configuration element by using the name attribute. In the above the

    name="MyWorkflowRuntime" 

indicates that there is a separate <MyWorkflowRuntime> element elsewhere containing settings. The MyWorkflowRuntime literal should be used by the host application when creates workflow runtime instances.

    using(WorkflowRuntime workflowRuntime = new WorkflowRuntime("MyWorkflowRuntime"))
    {
        ...
    }
 

It is possible to have multiple workflow configuration sections inside of a configuration file.

The <CommonParameters> element contains <add> elements with name and value pairs for various settings, such as connection strings, available to the services in the <Services> element.

The <Services> element contains services to be used with the workflow.

    <Services>
        <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" UnloadOnIdle="false"/>
    </Services>
    

It is possible to add a persistence service and configure its connection string at the same element.

    <Services>
        <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService ..." connectionString="" />
    </Services>    
    
    

Logging can be valuable when debugging. WWF follows the configuration file format introduced in .Net 2.0 to configure logging.

    <system.diagnostics>
        <switches>
              <add name="System.Workflow.Runtime" value="All" />
              <add name="System.Workflow.Runtime.Hosting" value="All" />
              <add name="System.Workflow.Runtime.Tracking" value="Critical" />
              <add name="System.Workflow.Activities" value="Warning" />
              <add name="System.Workflow.Activities.Rules" value="Off" />

              <add name="System.Workflow LogToFile" value="1" />
        </switches>
    </system.diagnostics>

The <switches> element contains a number of <add> elements where each one is a name-value pair. An <add> element defines a specific trace source and each trace source conveys trace information from a different area of the workflow runtime. The possible values of the value attribute are as following

The line

    <add name="System.Workflow LogToFile" value="1" />

directs logging information to a file using the name WorkflowTrace.log in the same directory as the application.

Workflow authoring modes

WWF supports three authoring modes for implementing workflows

Code-only. This is the default authoring mode. Workflows are defined using source code files and designers. No XAML definition files are used. A code-only workflow must be compiled. This is the only mode used so far by all examples. A code-only workflow may constructed totally in code and without using designers at all.

Code-separation. Workflows are defined both in source code files and XAML markup files with a .xoml extension. MS Visual Studio IDE provides special project and item wizards for creating workflows of this mode. This mode mixes source code and markup. A code-separation workflow must be compiled.

No-code. Workflows are defined by just using XAML markup in .xoml files. No source code is involved. Workflows are totally declarative. A no-code workflow can be compiled to an in-memory or to an in-disk assembly if the markup code contains the x:Class attribute. Also a no-code workflow can be loaded, without any compilation, by just loading the .xoml definition file into the runtime engine.

see also:

Using workflow authoring modes

A Code-only mode and a Code-separation mode workflow are executed the same way by just using the type of the workflow class when calling the WorkflowRuntime.CreateWorkflow() method.

    static void ExecuteWorkflow(Type WorkflowType)
    {
        using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
        { 
            AutoResetEvent waitHandle = new AutoResetEvent(false);
            workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
            workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
            {
                Console.WriteLine(e.Exception.Message);
                waitHandle.Set();
            };

            WorkflowInstance instance = workflowRuntime.CreateWorkflow(WorkflowType);
            instance.Start();

            waitHandle.WaitOne();
            Console.WriteLine("----------------------------------");
        }
    }
    

...

    ExecuteWorkflow(typeof(CodeOnly));                  // CodeOnly mode, the normal default mode
    ExecuteWorkflow(typeof(CodeSeparationWorkflow));    // Code-separation mode     
    

The same way is used with a compiled No-code mode workflow which may or may not contain source code through the <x:Code> element. Compiling workflows is described next.

    static Type NoCode_Compiled()
    {
        string DataXML =
                @"<?xml version=""1.0"" encoding=""utf-8""?>                       " +
                "<SequentialWorkflowActivity                                        " +
                "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/workflow\"    " +
                "xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"           " +
                "x:Class=\"Lessons.Workflows.NoCode_Compiled\" >                    " +
                "<CodeActivity ExecuteCode=\"act_ExecuteCode\" />                   " +
                "<x:Code>                                                           " +
                "   <![CDATA[                                                       " +
                "   private void act_ExecuteCode(object sender, EventArgs e)        " +
                "   {                                                               " +
                "      Console.WriteLine(\"Hi there, from: NoCode Compiled\");      " +
                "  }                                                                " +
                "   ]]>                                                             " +
                "</x:Code>                                                          " +
                "</SequentialWorkflowActivity>                                      "
                ;

        /* create and save a .xoml file */
        string[] Files = { @"..\..\NoCode_Compiled.xoml" };
        File.WriteAllText(Files[0], DataXML);


        /* create and setup a WorkflowCompiler */
        WorkflowCompiler compiler = new WorkflowCompiler();
        WorkflowCompilerParameters Params = new WorkflowCompilerParameters();
        Params.GenerateInMemory = true;


        /* compile the file(s) and check for any errors */
        WorkflowCompilerResults Results = compiler.Compile(Params, Files);

        if (Results.Errors.Count > 0)
            foreach (CompilerError ce in Results.Errors)
                Console.WriteLine(ce.ErrorText);

        /* load the NoCodeCompiledWorkflow class which is contained in the dynamically compiled assembly */
        return Results.CompiledAssembly.GetType("Lessons.Workflows.NoCode_Compiled");
    }        

...

    ExecuteWorkflow(NoCode_Compiled()); 

A No-code mode workflow may be executed without compilation though. This is the so-called XAML activation. Since no compilation takes place with XAML activation, such a workflow .xoml file can not contain inline source code or the x:Class="..." attribute. It can contain custom activities though.

The using of a custom activity in a .xoml file requires a proper namespace definition of the namespace where the activity resides.

    xmlns:ns0="clr-namespace:Lessons.Workflows; Assembly=AuthoringModes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 

The XAML activation requires to load the .xoml file into an XmlReader object and pass that object to the WorkflowRuntime.CreateWorkflow() method.

    static XmlReader NoCode_XAMLActivation()
    {
        string DataXML =
            @"<?xml version=""1.0"" encoding=""utf-8""?>                       " +
            "<SequentialWorkflowActivity                                        " + 
            "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/workflow\"    " +
            "xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"           " +
            "xmlns:ns0=\"clr-namespace:Lessons.Workflows; Assembly=AuthoringModes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" 	 " +
            //"x:Class=\"Lessons.Workflows.DynamicWorkflow\"     " +
             ">                                                                  " +
            "<ns0:TextActivity Text=\"NoCode XAML Activation\" />               " +
            "<DelayActivity TimeoutDuration=\"00:00:02\"  />                    " +
            "</SequentialWorkflowActivity>                                      "
            ;

        /* create and save a .xoml file */
        File.WriteAllText(NoCodeXAMLActivationFileName, DataXML);

        FileStream FS = File.OpenRead(NoCodeXAMLActivationFileName);
        return XmlReader.Create(FS);
    }
    
    
     /* executes a workflow passed as the content of an XmlReader object */
    static void ExecuteWorkflow(XmlReader Reader)
    {

        using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
        {       

            AutoResetEvent waitHandle = new AutoResetEvent(false);
            workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
            workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
            {
                Console.WriteLine(e.Exception.Message);
                waitHandle.Set();
            };

            WorkflowInstance instance = workflowRuntime.CreateWorkflow(Reader); 
            instance.Start();

            waitHandle.WaitOne();
            Console.WriteLine("----------------------------------");

        }
    }

...

    ExecuteWorkflow(NoCode_XAMLActivation());
    

Compiling workflows

The WWF comes with a command-line workflow compiler, the wfc.exe. The workflow compiler can be found at C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin. It is easily used through the MS Visual Studio Command Prompt.

The WorkflowCompiler class can be used to compile workflows at run-time.

    /* file names of .xoml files */
    string[] Files = { .... }; 

    /* create and setup a WorkflowCompiler */
    WorkflowCompiler compiler = new WorkflowCompiler();
    WorkflowCompilerParameters Params = new WorkflowCompilerParameters();
    Params.GenerateInMemory = true;

    /* compile the file(s) and check for any errors */
    WorkflowCompilerResults Results = compiler.Compile(Params, Files);

    if (Results.Errors.Count > 0)
        foreach (CompilerError ce in Results.Errors)
            Console.WriteLine(ce.ErrorText);

    /* load the workflow class which is contained in the dynamically compiled assembly */
    Type WorkflowType = Results.CompiledAssembly.GetType("NameOfTheWorkflowClass");
    

...

    WorkflowInstance instance = workflowRuntime.CreateWorkflow(WorkflowType);
    instance.Start();

For the compilation to succeed the .xoml has to contain the x:Class attribute.

When the WorkflowCompilerParameters.GenerateInMemory is true the generated assembly is loaded automatically in the current application domain.

Here is a .xoml file which contains executable code of a No-code mode workflow. The compilation of the following XAML code creates an assembly which contains the NoCode_Compiled class, thanks to the x:Class="Lessons.Workflows.NoCode_Compiled" attribute contained in the XAML.

    <?xml version="1.0" encoding="utf-8"?>                       
     <SequentialWorkflowActivity                                     
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
     x:Class="Lessons.Workflows.NoCode_Compiled" >                                   
     <CodeActivity ExecuteCode="act_ExecuteCode" />                
     <x:Code>                                                        
        <![CDATA[                                                    
        private void act_ExecuteCode(object sender, EventArgs e)     
        {                                                            
           Console.WriteLine("Hi there, from: NoCode Compiled");   
       }                                                             
        ]]>                                                          
     </x:Code>                                                       
     </SequentialWorkflowActivity>                                   
           

And here is another .xoml file which defines a workflow containing a custom TextActivity activity and a DelayActivity.

    <?xml version="1.0" encoding="utf-8"?>                         
     <SequentialWorkflowActivity                                     
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"            
     xmlns:ns0="clr-namespace:Lessons.Workflows; Assembly=AuthoringModes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 	 
     x:Class="Lessons.Workflows.DynamicWorkflow"     
     >                                                                 
        <ns0:TextActivity Text="NoCode XAML Activation" />              
        <DelayActivity TimeoutDuration="00:00:02"  />                   
    </SequentialWorkflowActivity>   
    

Here is that custom TextActivity.

    public class TextActivity : Activity
    {
        public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TextActivity));
        
        public TextActivity()
        {
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext context)
        {
            Console.WriteLine("Hi there, from: {0}", Text);
            return ActivityExecutionStatus.Closed;
        }

        public string Text
        {
            get {  return ((string)(base.GetValue(TextProperty)));  }
            set {  base.SetValue(TextProperty, value);  }
        }
    }            
    

The using of a custom activity in a .xoml file requires a proper namespace definition of the namespace where the activity resides.

    xmlns:ns0="clr-namespace:Lessons.Workflows; Assembly=AuthoringModes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 
                                            

Serializing workflows

Workflow serialization is done by using the WorkflowMarkupSerializer class. The XAML produced by the WorkflowMarkupSerializer may be saved to a .xoml file or to any other medium for later use.

    static void SerializeWorkflow()
    {
        WorkflowMarkupSerializer WS = new WorkflowMarkupSerializer();

        using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
        {
            using (StringWriter SW = new StringWriter())
            {
                using (XmlWriter XW = XmlWriter.Create(SW))
                {
                    Console.WriteLine("Serializing a Workflow");
                    Console.WriteLine("==================================");
                    WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(CodeSeparationWorkflow));
                    WS.Serialize(XW, instance.GetWorkflowDefinition());
                    Console.WriteLine(SW.ToString());
                    Console.WriteLine("----------------------------------");
                }
            }
        }
    }


Hosting the Workflow Designer

The MSDN provides the excellent article "Windows Workflow Foundation: Everything About Re-Hosting the Workflow Designer" at http://msdn.microsoft.com/en-us/library/aa480213.aspx by Vihang Dalal, which covers the hosting of the workflow designer in custom applications. The sample project of the article can be used as a designer for sequential workflows and provides full access to the internal Condition and Rules dialog boxes.

The sample can be downloaded from http://www.microsoft.com/downloads/details.aspx?FamilyID=3A331D20-44D4-4FB4-A833-F6EC9AEE1B82&displaylang=en

Editing RuleSet definitions with the RuleSetDialog class

The RuleConditionDialog class and the RuleSetDialog class are the dialog boxes used by the MS Visual Studio IDE for editing Conditions and Rules. Those dialog boxes can be used by a host application in order to provide condition and rule editing functionality.

The RuleSetDialog provides a constructor

    public RuleSetDialog(Type activityType, ITypeProvider typeProvider, RuleSet ruleSet);

for editing a RuleSet against a CLR class type.

    using (RuleSetDialog Dlg = new RuleSetDialog(typeof(Customer), null, ruleSet))
    {
        if (Dlg.ShowDialog() == DialogResult.OK)
            ruleSet = Dlg.RuleSet;
    }
    

Here are two methods for saving/loading rule sets to disk files.

    /* Saves RuleSet to FileName */
    static public void SaveToFile(RuleSet RuleSet, string FileName)
    {
        using (XmlWriter rulesWriter = XmlWriter.Create(FileName))
        {
            WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
            serializer.Serialize(rulesWriter, RuleSet);
        }
    }

    /* Loads and returns a RuleSet from FileName */
    static public RuleSet LoadFromFile(string FileName)
    {
        using (XmlTextReader rulesReader = new XmlTextReader(FileName))
        {
            WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
            return serializer.Deserialize(rulesReader) as RuleSet;
        }
    }
    

It is also possible to serialize a RuleSet to plain text for storing it anywhere a string data can be saved.

    /* Returns the RuleSet converted to XML text */
    static public string ToText(RuleSet RuleSet)
    {
        StringBuilder SB = new StringBuilder();
        using (XmlWriter writer = XmlWriter.Create(SB))
        {
            WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
            serializer.Serialize(writer, RuleSet);
        }
        return SB.ToString();
    }


    /* Returns a RuleSet reading the rules in Text */
    static public RuleSet FromText(string Text)
    {
        RuleSet Result = null;
        if (!string.IsNullOrEmpty(Text))
        {
            using (XmlReader reader = XmlReader.Create(new StringReader(Text)))
            {
                WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
                Result = serializer.Deserialize(reader) as RuleSet;
            }
        }
        return Result;
    }
    

Using the WWF Rules Engine functionality

The RuleSet class provides rules engine functionality. It provides two methods

    public bool Validate(RuleValidation validation);
    public void Execute(RuleExecution ruleExecution);

for validating and executing the RuleSet against any CLR object.

The classes involved in the process are the RuleValidation and RuleExecution class.

    /* executes RuleSet rules against Instance */
    static public void Execute(RuleSet RuleSet, object Instance)
    {
        if (RuleSet == null)
            throw new ArgumentNullException("RuleSet");

        if (Instance == null)
            throw new ArgumentNullException("Instance");

        RuleValidation ruleValidation = new RuleValidation(Instance.GetType(), null);
        RuleExecution ruleExecution = new RuleExecution(ruleValidation, Instance);
        RuleSet.Execute(ruleExecution);
    }
    

...

    Execute(LoadFromFile(FileName), customer);    
    
    

see also:


Copyright © 2009 Theodoros Bebekis, Thessaloniki, Greece (teo point bebekis at gmail point com)