31 Mar 2009

WF 4.0 – Part 3. Custom Composite Activities

The following is based on the PDC CTP of WF 4.0 and as such might change or be missing in futures releases.

In Parts 1 and 2 we looked at the different types of Workflow and their respective designers available in the CTP of WF 4.0. In the next couple of parts we will look at how we go about creating our own custom Activities.

As previously mentioned, there has been a shift in the way we create and implement our Workflows. We no longer need to write C# to create a workflow, instead we use XAML. This is also true for Activities, as there are 2 kinds of Activities we can create in WF 4.0 which are Composite and Workflow Elements.

The Composite Activity is built up from other Activities and uses one of the Workflow types as it’s basis. This is a change in the way we think about Workflows, in 3.x we saw Workflows as containers that could only contain Activities, now Workflows can contain Workflows which contain Workflows etc. This new approach will allow us to capture and declare far more complex Processes and State Machines than in 3.x.

Composite Activities

So lets start by creating a Composite Activity. In this example we’re going to create a new Activity that is going to be used in the theoretical process of a User Group member arriving at a meeting and signing in. The Activity will basically be in charge of asking them there name, some sort of password and saying hello to them.

So first off we create a new Blank Activity and its designer is displayed. At this point we can either add in a single existing Activity or a Workflow.

Because my Activity is essentially making decisions, I am going to add a FlowChart Workflow to be the basis of my new Activity, this will allow me to add multiple Activities that will execute in a specific order.

Now the steps needed to perform our activity are as follows:

  1. Greet the User
  2. Ask for their confirmation code (given to them when they registered)
  3. Depending on whether the code is valid
    1. Welcome the user
    2. Give them another attempt to register

Now this Workflow is actually going to loop forever until a valid code is input, but in real life we’d give the user some sort of option to give up.

I’m going to be using our old friends the WriteConsole and ReadConsole Activities for this, as well as a new custom activity that checks the Users input against a confirmation code. The process is exactly the same as creating a Workflow, I drag the Activities on I want to use and connect them together. This leaves me with an Activity that looks like this:

Here are Variables and Arguments:

So what does the XAML look like for this?


   1: <p:Activity x:Class="DemoChart.GreetingActivity" xmlns:c="clr-namespace:CustomActivities;assembly=CustomActivities" xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/workflowmodel" xmlns:p1="http://schemas.microsoft.com/netfx/2008/xaml/schema" xmlns:p2="http://schemas.microsoft.com/xps/2005/06" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:swd="clr-namespace:System.WorkflowModel.Debugger;assembly=System.WorkflowModel" xmlns:swdv="clr-namespace:System.WorkflowModel.Design.View;assembly=System.WorkflowModel.Design" xmlns:swdx="clr-namespace:System.WorkflowModel.Design.Xaml;assembly=System.WorkflowModel.Design" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x2="http://schemas.microsoft.com/netfx/2008/xaml">
   2:   <p1:SchemaType.Members>
   3:     <p1:SchemaProperty Name="AttendeeName" Type="p:InArgument(p1:String)" />
   4:   </p1:SchemaType.Members>
   5:   <p:Flowchart DisplayName="Flowchart" swd:XamlDebuggerXmlReader.FileName="C:\Users\Administrator\Documents\Visual Studio 10\Projects\1_Designer_Demo\DemoChart\GreetingActivity.xaml">
   6:     <p:Flowchart.Variables>
   7:       <p:Variable x:TypeArguments="p1:Int32" Name="_attendeeCode" />
   8:       <p:Variable x:TypeArguments="p1:Boolean" Default="[False]" Name="_codeValid" />
   9:     </p:Flowchart.Variables>
  10:     <p:FlowStep>
  11:       <p:FlowStep.Action>
  12:         <c:WriteLine DisplayName="GreetAttendee" Text="[String.Concat(&quot;Welcome &quot;, AttendeeName)]">
  13:           <swdv:WorkflowViewStateService.ViewState>
  14:             <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  15:               <p2:Point x:Key="ShapeLocation">160,82.02</p2:Point>
  16:               <p2:Size x:Key="ShapeSize">160,35.96</p2:Size>
  17:             </scg:Dictionary>
  18:           </swdv:WorkflowViewStateService.ViewState>
  19:         </c:WriteLine>
  20:       </p:FlowStep.Action>
  21:       <swdv:WorkflowViewStateService.ViewState>
  22:         <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  23:           <p2:PointCollection x:Key="PolylineLocation">240,117.98 240,142.02</p2:PointCollection>
  24:         </scg:Dictionary>
  25:       </swdv:WorkflowViewStateService.ViewState>
  26:       <p:FlowStep x:Name="__ Reference ID 0">
  27:         <p:FlowStep.Action>
  28:           <c:WriteLine DisplayName="RequestMeetingCode" Text="[&quot;Please enter your metting confirmation code&quot;]">
  29:             <swdv:WorkflowViewStateService.ViewState>
  30:               <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  31:                 <p2:Point x:Key="ShapeLocation">160,142.02</p2:Point>
  32:                 <p2:Size x:Key="ShapeSize">160,35.96</p2:Size>
  33:               </scg:Dictionary>
  34:             </swdv:WorkflowViewStateService.ViewState>
  35:           </c:WriteLine>
  36:         </p:FlowStep.Action>
  37:         <swdv:WorkflowViewStateService.ViewState>
  38:           <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  39:             <p2:PointCollection x:Key="PolylineLocation">240,177.98 240,222.02</p2:PointCollection>
  40:           </scg:Dictionary>
  41:         </swdv:WorkflowViewStateService.ViewState>
  42:         <p:FlowStep x:Name="__ Reference ID 1">
  43:           <p:FlowStep.Action>
  44:             <c:ReadLine DisplayName="GetAttendeeCode" outArgument="[_attendeeCode]">
  45:               <swdv:WorkflowViewStateService.ViewState>
  46:                 <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  47:                   <p2:Point x:Key="ShapeLocation">160,222.02</p2:Point>
  48:                   <p2:Size x:Key="ShapeSize">160,35.96</p2:Size>
  49:                 </scg:Dictionary>
  50:               </swdv:WorkflowViewStateService.ViewState>
  51:             </c:ReadLine>
  52:           </p:FlowStep.Action>
  53:           <swdv:WorkflowViewStateService.ViewState>
  54:             <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  55:               <p2:PointCollection x:Key="PolylineLocation">240,257.98 240,282.02</p2:PointCollection>
  56:             </scg:Dictionary>
  57:           </swdv:WorkflowViewStateService.ViewState>
  58:           <p:FlowStep x:Name="__ Reference ID 2">
  59:             <p:FlowStep.Action>
  60:               <c:CodeCheck CodeToCheck="[_attendeeCode]" IsValid="[_codeValid]">
  61:                 <swdv:WorkflowViewStateService.ViewState>
  62:                   <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  63:                     <p2:Point x:Key="ShapeLocation">160,282.02</p2:Point>
  64:                     <p2:Size x:Key="ShapeSize">160,35.96</p2:Size>
  65:                   </scg:Dictionary>
  66:                 </swdv:WorkflowViewStateService.ViewState>
  67:               </c:CodeCheck>
  68:             </p:FlowStep.Action>
  69:             <swdv:WorkflowViewStateService.ViewState>
  70:               <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  71:                 <p2:PointCollection x:Key="PolylineLocation">240,317.98 240,350</p2:PointCollection>
  72:               </scg:Dictionary>
  73:             </swdv:WorkflowViewStateService.ViewState>
  74:             <p:FlowDecision False="{x2:Reference Name=&quot;__ Reference ID 0&quot;}" x:Name="__ Reference ID 3" Condition="[_codeValid]">
  75:               <p:FlowDecision.True>
  76:                 <p:FlowStep x:Name="__ Reference ID 4">
  77:                   <p:FlowStep.Action>
  78:                     <c:WriteLine DisplayName="Thanks" Text="[&quot;Thanks for coming!&quot;]">
  79:                       <swdv:WorkflowViewStateService.ViewState>
  80:                         <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  81:                           <p2:Point x:Key="ShapeLocation">20,462.02</p2:Point>
  82:                           <p2:Size x:Key="ShapeSize">160,35.96</p2:Size>
  83:                         </scg:Dictionary>
  84:                       </swdv:WorkflowViewStateService.ViewState>
  85:                     </c:WriteLine>
  86:                   </p:FlowStep.Action>
  87:                 </p:FlowStep>
  88:               </p:FlowDecision.True>
  89:               <swdv:WorkflowViewStateService.ViewState>
  90:                 <scg:Dictionary x:TypeArguments="p1:String, p1:Object">
  91:                   <p2:Point x:Key="ShapeLocation">210,350</p2:Point>
  92:                   <p2:Size x:Key="ShapeSize">60,60</p2:Size>
  93:                   <p2:PointCollection x:Key="TruePolylineLocation">210,380 100,380 100,462.02</p2:PointCollection>
  94:                   <p2:PointCollection x:Key="FalsePolylineLocation">270,380 398.5,380 398.5,160 320,160</p2:PointCollection>
  95:                 </scg:Dictionary>
  96:               </swdv:WorkflowViewStateService.ViewState>
  97:             </p:FlowDecision>
  98:           </p:FlowStep>
  99:         </p:FlowStep>
 100:       </p:FlowStep>
 101:     </p:FlowStep>
 102:     <x2:Reference Name="__ Reference ID 1" />
 103:     <x2:Reference Name="__ Reference ID 0" />
 104:     <x2:Reference Name="__ Reference ID 2" />
 105:     <x2:Reference Name="__ Reference ID 3" />
 106:     <x2:Reference Name="__ Reference ID 4" />
 107:   </p:Flowchart>
 108: </p:Activity>


As you can see that’s a fairly comprehensive bit of XAML, which is understandable as there is no code behind anywhere in this Workflow. We can now use this Activity on a Workflow, and this is what it will look like.And if we look at the Properties for the placed Activity, we can see our AttendeeName argument that we added to allow us to greet individuals by name.

As this is an InArgument, it allows for expression binding. Had we used just a plain String property on there, we would not be able to use the expression functionality.

In the next part, we will look at creating our own custom Activities in code (items called Workflow Elements) and how to skin them.

No comments: