Task 3: Implement the IOrderingService Interface
In this task, you use the IOrderingService
interface that you created in Task 2 by implementing the interface in the Simple Order Form host application to create a local service for communicating with the running workflow.
When a user submits a new order, the host application raises the NewOrder
event, which is processed by the OrderProcessingWorkflow
. The OrderProcessingWorkflow
calls the ItemStatusUpdate
method of the IOrderingService
. Then it calls that method in the host application to update the user interface.
Note
Although you are encouraged to follow the exercises in a linear manner, it is not required. You can start this exercise by opening the sample project and proceeding to the steps in the following section.
Implementing the Interface
Follow these steps to implement the interface that you created in the previous task.
To implement the IOrderingService interface
Add the following component references to the StateMachineHost project:
System.Workflow.Runtime
System.Workflow.Activities
System.Workflow.ComponentModel
Add the following references to the Mainform.cs file in the StateMachineHost project:
System.Workflow.Activities
System.Workflow.ComponentModel
System.Workflow.Runtime
StateMachineWorkflow
Imports System.Workflow.Activities Imports System.Workflow.ComponentModel Imports System.Workflow.Runtime Imports StateMachineWorkflow
using System.Workflow.Activities; using System.Workflow.ComponentModel; using System.Workflow.Runtime; using StateMachineWorkflow;
In the Mainform source file in the StateMachineHost project, derive the
MainForm
class from theIOrderingService
interface so that the main Form itself can serve as the service class for communicating with the workflow.Public Class MainForm : Inherits Form : Implements IOrderingService End Class
public class MainForm : Form, IOrderingService
In the
MainForm
class, create an array of type string that contains some inventory items used for creating orders.Private inventoryItems() As String = { "Apple", "Orange", "Banana", "Pear", "Watermelon", "Grapes" }
private string[] inventoryItems = { "Apple", "Orange", "Banana", "Pear", "Watermelon", "Grapes" };
In the
MainForm
class, create a delegate to handle item status updates.Private Delegate Sub ItemStatusUpdateDelegate(orderId As Guid, newStatus As String)
private delegate void ItemStatusUpdateDelegate(Guid orderId, string newStatus);
In the
MainForm
class, create a new public method namedItemStatusUpdate
that accepts a Guid namedorderId
and a String namednewStatus
as parameters. This method is used to change the order status, which has the effect of changing the currently active state in the workflow.Public Sub ItemStatusUpdate(ByVal orderId As Guid, ByVal newStatus As String) Implements IOrderingService.ItemStatusUpdate End Sub
public void ItemStatusUpdate(Guid orderId, string newStatus) { }
In the
MainForm
class, create a private WorkflowRuntime field namedworkflowRuntime
and initialize it tonull
(Nothing
in Visual Basic).In the
MainForm
class, create an ExternalDataExchangeService field namedexchangeService
and initialize it tonull
(Nothing
in Visual Basic). This service serves as a container for the custom local data exchange service that is used to communicate with the workflow.private WorkflowRuntime workflowRuntime = null; private ExternalDataExchangeService exchangeService = null;
In the
MainForm
class, create a private genericEventHandler
of theNewOrderEventArgs
type namednewOrderEvent
. This event is raised to create a new order, which has the effect of starting a new workflow instance to track the order.In the
MainForm
class, create a public genericEventHandler
event property of theNewOrderEventArgs
type namedNewOrder
.In the
NewOrder
event property, create an Add and Remove accessor to add and remove event handlers from thenewOrderEvent
event.private event EventHandler<NewOrderEventArgs> newOrderEvent; public event EventHandler<NewOrderEventArgs> NewOrder { add { newOrderEvent += value; } remove { newOrderEvent -= value; } }
Create a private Dictionary field of type
string, List<string>
calledorderHistory
. This field is used to track the running workflow instances.Private orderHistory as Dictionary(Of String, List(Of String))
private Dictionary<string, List<string>> orderHistory;
Initialize the
orderHistory
object in the constructor of theMainform
class to a new instance of aDictionary
object.orderHistory = new Dictionary(Of String, List(Of String))
orderHistory = new Dictionary<string, List<string>>();
In the
MainForm
constructor, create an instance of the WorkflowRuntime object after the code that creates an instance of theorderHistory
object.Create an instance of the ExternalDataExchangeService object after the code created in the previous step.
Add the ExternalDataExchangeService object to the WorkflowRuntime services by calling the AddService method and passing the
exchangeService
object as a parameter.Add the
MainForm
object to the ExternalDataExchangeService services by calling the AddService method and passing thethis
(Me
in Visual Basic) object as a parameter. This allows theMainform
object itself to act as a messaging service for the workflow run-time object.Start the WorkflowRuntime by calling the StartRuntime method.
this.workflowRuntime = new WorkflowRuntime(); this.exchangeService = new ExternalDataExchangeService(); this.workflowRuntime.AddService(this.exchangeService); this.exchangeService.AddService(this); this.workflowRuntime.StartRuntime();
Creating the Windows Form Controls
Add the following line to the Mainform.Designer file.
Imports System.Windows.Forms using System.Windows.Forms;
Add the following code to the Mainform class in the Mainform.Designer file. This code serves as the logic for the user interface for controlling the application. Because the focus of this tutorial is on creating and using workflows rather than Windows Forms, it is not necessary to go into details about the workings of the form. Please see MSDN for information on the operation of Windows Forms.
Private label1 As System.Windows.Forms.Label Private itemsList As System.Windows.Forms.ComboBox Private label2 As System.Windows.Forms.Label Private itemQuantity As System.Windows.Forms.NumericUpDown Private submitButton As System.Windows.Forms.Button Private groupBox1 As System.Windows.Forms.GroupBox Private ordersIdList As System.Windows.Forms.ComboBox Private label3 As System.Windows.Forms.Label Private orderStatus As System.Windows.Forms.TextBox Private label4 As System.Windows.Forms.Label
private System.Windows.Forms.Label label1; private System.Windows.Forms.ComboBox itemsList; private System.Windows.Forms.Label label2; private System.Windows.Forms.NumericUpDown itemQuantity; private System.Windows.Forms.Button submitButton; private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.ComboBox ordersIdList; private System.Windows.Forms.Label label3; private System.Windows.Forms.TextBox orderStatus; private System.Windows.Forms.Label label4;
Replace the code in the
InitializeComponent
method in the Mainform.Designer.cs or Mainform.Designer.vb file (hidden by default in the “Windows Form Designer Code” section) with the following code.Me.label1 = New System.Windows.Forms.Label() Me.itemsList = New System.Windows.Forms.ComboBox() Me.label2 = New System.Windows.Forms.Label() Me.itemQuantity = New System.Windows.Forms.NumericUpDown() Me.submitButton = New System.Windows.Forms.Button() Me.groupBox1 = New System.Windows.Forms.GroupBox() Me.orderStatus = New System.Windows.Forms.TextBox() Me.label4 = New System.Windows.Forms.Label() Me.ordersIdList = New System.Windows.Forms.ComboBox() Me.label3 = New System.Windows.Forms.Label() (Me.itemQuantity As _ System.ComponentModel.ISupportInitialize).BeginInit() Me.groupBox1.SuspendLayout() Me.SuspendLayout() ‘ ‘ label1 ‘ Me.label1.AutoSize = true Me.label1.Location = New System.Drawing.Point(12, 9) Me.label1.Name = "label1" Me.label1.Size = New System.Drawing.Size(23, 13) Me.label1.TabIndex = 0 Me.label1.Text = "Item" ‘ ‘ Me.itemsList ‘ Me.itemsList.DropDownStyle = ComboBoxStyle.DropDownList Me.itemsList.FormattingEnabled = true Me.itemsList.Location = New System.Drawing.Point(13, 26) Me.itemsList.Name = "itemsList" Me.itemsList.Size = New System.Drawing.Size(161, 21) Me.itemsList.TabIndex = 0 ‘ ‘ label2 ‘ Me.label2.AutoSize = true Me.label2.Location = New System.Drawing.Point(12, 50) Me.label2.Name = "label2" Me.label2.Size = New System.Drawing.Size(42, 13) Me.label2.TabIndex = 2 Me.label2.Text = "Quantity" ‘ ‘ Me.itemQuantity ‘ Me.itemQuantity.Location = New System.Drawing.Point(13, 67) Me.itemQuantity.Minimum = New decimal(New int[] { _ 1, _ 0, _ 0, _ 0}) Me.itemQuantity.Name = "itemQuantity" Me.itemQuantity.Size = New System.Drawing.Size(80, 20) Me.itemQuantity.TabIndex = 1 Me.itemQuantity.Value = New decimal(New int[] { _ 1, _ 0, _ 0, _ 0}) ‘ ‘ Me.submitButton ‘ Me.submitButton.Location = New System.Drawing.Point(99, 64) Me.submitButton.Name = "submitButton" Me.submitButton.Size = New System.Drawing.Size(100, 23) Me.submitButton.TabIndex = 2 Me.submitButton.Text = "Submit Order" AddHandler Me.submitButton.Click, AddressOf Me.submitButton_Click ‘ ‘ groupBox1 ‘ Me.groupBox1.Controls.Add(Me.orderStatus) Me.groupBox1.Controls.Add(Me.label4) Me.groupBox1.Controls.Add(Me.ordersIdList) Me.groupBox1.Controls.Add(Me.label3) Me.groupBox1.Location = New System.Drawing.Point(13, 93) Me.groupBox1.Name = "groupBox1" Me.groupBox1.Size = New System.Drawing.Size(247, 193) Me.groupBox1.TabIndex = 3 Me.groupBox1.TabStop = false Me.groupBox1.Text = "Order Status" ‘ ‘ orderStatus ‘ Me.orderStatus.Location = New System.Drawing.Point(7, 82) Me.orderStatus.Multiline = true Me.orderStatus.Name = "orderStatus" Me.orderStatus.ReadOnly = true Me.orderStatus.Size = New System.Drawing.Size(230, 105) Me.orderStatus.TabIndex = 3 ‘ ‘ label4 ‘ Me.label4.AutoSize = true Me.label4.Location = New System.Drawing.Point(8, 65) Me.label4.Name = "label4" Me.label4.Size = New System.Drawing.Size(35, 13) Me.label4.TabIndex = 2 Me.label4.Text = "History" ‘ ‘ ordersIdList ‘ Me.ordersIdList.DropDownStyle = ComboBoxStyle.DropDownList Me.ordersIdList.FormattingEnabled = true Me.ordersIdList.Location = New System.Drawing.Point(7, 37) Me.ordersIdList.Name = "ordersIdList" Me.ordersIdList.Size = New System.Drawing.Size(230, 21) Me.ordersIdList.TabIndex = 0 AddHandler Me.ordersIdList.SelectedIndexChanged, AddressOf Me.ordersIdList_SelectedIndexChanged ‘ ‘ label3 ‘ Me.label3.AutoSize = true Me.label3.Location = New System.Drawing.Point(7, 20) Me.label3.Name = "label3" Me.label3.Size = New System.Drawing.Size(41, 13) Me.label3.TabIndex = 0 Me.label3.Text = "Order Id" ‘ ‘ MainForm ‘ Me.AcceptButton = Me.submitButton Me.AutoScaleDimensions = New System.Drawing.SizeF(6F, 13F) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font Me.ClientSize = New System.Drawing.Size(272, 298) Me.Controls.Add(Me.groupBox1) Me.Controls.Add(Me.submitButton) Me.Controls.Add(Me.itemQuantity) Me.Controls.Add(Me.label2) Me.Controls.Add(Me.itemsList) Me.Controls.Add(Me.label1) Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D Me.MaximizeBox = false Me.MinimizeBox = false Me.Name = "MainForm" Me.Text = "Simple Order Form" (Me.itemQuantity As System.ComponentModel.ISupportInitialize).EndInit() Me.groupBox1.ResumeLayout(false) Me.groupBox1.PerformLayout() Me.ResumeLayout(false) Me.PerformLayout()
this.label1 = new System.Windows.Forms.Label(); this.itemsList = new System.Windows.Forms.ComboBox(); this.label2 = new System.Windows.Forms.Label(); this.itemQuantity = new System.Windows.Forms.NumericUpDown(); this.submitButton = new System.Windows.Forms.Button(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.orderStatus = new System.Windows.Forms.TextBox(); this.label4 = new System.Windows.Forms.Label(); this.ordersIdList = new System.Windows.Forms.ComboBox(); this.label3 = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.itemQuantity)).BeginInit(); this.groupBox1.SuspendLayout(); this.SuspendLayout(); // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(12, 9); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(23, 13); this.label1.TabIndex = 0; this.label1.Text = "Item"; // // this.itemsList // this.itemsList.DropDownStyle = ComboBoxStyle.DropDownList; this.itemsList.FormattingEnabled = true; this.itemsList.Location = new System.Drawing.Point(13, 26); this.itemsList.Name = "itemsList"; this.itemsList.Size = new System.Drawing.Size(161, 21); this.itemsList.TabIndex = 0; // // label2 // this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(12, 50); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(42, 13); this.label2.TabIndex = 2; this.label2.Text = "Quantity"; // // this.itemQuantity // this.itemQuantity.Location = new System.Drawing.Point(13, 67); this.itemQuantity.Minimum = new decimal(new int[] { 1, 0, 0, 0}); this.itemQuantity.Name = "itemQuantity"; this.itemQuantity.Size = new System.Drawing.Size(80, 20); this.itemQuantity.TabIndex = 1; this.itemQuantity.Value = new decimal(new int[] { 1, 0, 0, 0}); // // this.submitButton // this.submitButton.Location = new System.Drawing.Point(99, 64); this.submitButton.Name = "submitButton"; this.submitButton.Size = new System.Drawing.Size(100, 23); this.submitButton.TabIndex = 2; this.submitButton.Text = "Submit Order"; this.submitButton.Click += new System.EventHandler(this.submitButton_Click); // // groupBox1 // this.groupBox1.Controls.Add(this.orderStatus); this.groupBox1.Controls.Add(this.label4); this.groupBox1.Controls.Add(this.ordersIdList); this.groupBox1.Controls.Add(this.label3); this.groupBox1.Location = new System.Drawing.Point(13, 93); this.groupBox1.Name = "groupBox1"; this.groupBox1.Size = new System.Drawing.Size(247, 193); this.groupBox1.TabIndex = 3; this.groupBox1.TabStop = false; this.groupBox1.Text = "Order Status"; // // orderStatus // this.orderStatus.Location = new System.Drawing.Point(7, 82); this.orderStatus.Multiline = true; this.orderStatus.Name = "orderStatus"; this.orderStatus.ReadOnly = true; this.orderStatus.Size = new System.Drawing.Size(230, 105); this.orderStatus.TabIndex = 3; // // label4 // this.label4.AutoSize = true; this.label4.Location = new System.Drawing.Point(8, 65); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(35, 13); this.label4.TabIndex = 2; this.label4.Text = "History"; // // ordersIdList // this.ordersIdList.DropDownStyle = ComboBoxStyle.DropDownList; this.ordersIdList.FormattingEnabled = true; this.ordersIdList.Location = new System.Drawing.Point(7, 37); this.ordersIdList.Name = "ordersIdList"; this.ordersIdList.Size = new System.Drawing.Size(230, 21); this.ordersIdList.TabIndex = 0; this.ordersIdList.SelectedIndexChanged += new System.EventHandler(this.ordersIdList_SelectedIndexChanged); // // label3 // this.label3.AutoSize = true; this.label3.Location = new System.Drawing.Point(7, 20); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(41, 13); this.label3.TabIndex = 0; this.label3.Text = "Order Id"; // // MainForm // this.AcceptButton = this.submitButton; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(272, 298); this.Controls.Add(this.groupBox1); this.Controls.Add(this.submitButton); this.Controls.Add(this.itemQuantity); this.Controls.Add(this.label2); this.Controls.Add(this.itemsList); this.Controls.Add(this.label1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "MainForm"; this.Text = "Simple Order Form"; ((System.ComponentModel.ISupportInitialize)(this.itemQuantity)).EndInit(); this.groupBox1.ResumeLayout(false); this.groupBox1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout();
Initialize the
itemsList
combo box in the constructor for the Mainform class.Foreach item As String In Me.inventoryItems) Me.itemsList.Items.Add(item) Next Me.itemsList.SelectedIndex = 0
foreach (string item in this.inventoryItems) { this.itemsList.Items.Add(item); } this.itemsList.SelectedIndex = 0;
Add the following methods to the MainForm class in the Mainform file.
Private Function GetOrderHistory(orderId As String) As String ‘ Retrieve the order status Dim itemHistory As New StringBuilder() Foreach status As String In Me.orderHistory(orderId) itemHistory.Append(status) itemHistory.Append(Environment.NewLine) End Foreach Return itemHistory.ToString() End Function Private Sub submitButton_Click(sender As object, e As EventArgs) End Sub Private Sub ordersIdList_SelectedIndexChanged(sender as object, e As EventArgs) this.orderStatus.Text = GetOrderHistory(this.ordersIdList.SelectedItem.ToString()) End Sub
private string GetOrderHistory(string orderId) { // Retrieve the order status StringBuilder itemHistory = new StringBuilder(); foreach (string status in this.orderHistory[orderId]) { itemHistory.Append(status); itemHistory.Append(Environment.NewLine); } return itemHistory.ToString(); } private void submitButton_Click(object sender, EventArgs e) { } private void ordersIdList_SelectedIndexChanged(object sender, EventArgs e) { this.orderStatus.Text = GetOrderHistory(this.ordersIdList.SelectedItem.ToString()); }
Enabling New Order Processing
Next, modify the Windows Form application to enable a user to submit a new order when the Submit button is clicked. To do this, create an instance of the OrderProcessingWorkflow
and run it passing in the parameters (read from the form) required to process the new order.
To submit a new order
In the
submitButton_Click
method, create a String variable nameditem
and set its value equal to theSelectedItem
property of theitemsList
control.Create a Int32 variable named
quantity
and set its value equal to theValue
property of theitemQuantity
control.string item = this.itemsList.SelectedItem.ToString(); int quantity = (int)this.itemQuantity.Value;
Create a Type variable named
orderWorkflow
and set its value equal to theOrderProcessingWorkflow
type.Create a WorkflowInstance variable named
workflowInstance
and set its value equal to the return value of the CreateWorkflow method defined in theworkflowRuntime
object. Pass theorderWorkflow
object as a parameter to the CreateWorkflow method call to create the workflow.Type orderWorkflow = typeof(OrderProcessingWorkflow); WorkflowInstance workflowInstance = this.workflowRuntime.CreateWorkflow(orderWorkflow);
Create a new generic
List
object of the String type and assign it to theorderHistory
Dictionary
collection, using the InstanceId of theworkflowInstance
object as the key. This allows the host to track the workflow instances that are created.For the value at the
orderHistory
key location created in the previous step, add a String whose value is the item name and quantity that the user submitted for the order by calling theAdd
method defined in theDictionary
class.this.orderHistory[workflowInstance.InstanceId.ToString()] = new List<string>(); this.orderHistory[workflowInstance.InstanceId.ToString()].Add ("Item: " + item + "; Quantity:" + quantity);
Call the Start method that is defined in the
workflowInstance
object to start the workflow.Raise the
NewOrder
event, passingnull
(Nothing
in Visual Basic) as the first parameter, and a newNewOrderEventArgs
object that is initialized with the InstanceId of theworkflowInstance
object, theitem
variable and thequantity
variable. This uses the local service to send a message to the workflow that a new order has been created.workflowInstance.Start(); newOrderEvent(null, new NewOrderEventArgs(workflowInstance.InstanceId, item, quantity));
Updating the User Interface
Finally, enable the workflow to update the user interface using status messages.
To update the user interface with status messages
In the
ItemStatusUpdate
method, verify whether theInvokeRequired
property of theordersIdList
object istrue
. You must perform this action because the workflow instance runs on a separate thread and updating the UI requires you to invoke operations from the UI thread.If this property is
true
, create a newItemStatusUpdateDelegate
object namedstatusUpdate
and a Object array namedargs
that contains theorderId
andnewStatus
parameters.Call the
Invoke
method of theMainForm
class, passing thestatusUpdate
andargs
objects as parameters.If Me.ordersIdList.InvokeRequired = True Then Dim statusUpdate As ItemStatusUpdateDelegate = _ New ItemStatusUpdateDelegate(AddressOf ItemStatusUpdate) Dim args As Object() = New Object(1) {orderId, newStatus} Me.Invoke(statusUpdate, args) Else End If
if (this.ordersIdList.InvokeRequired == true) { ItemStatusUpdateDelegate statusUpdate = new ItemStatusUpdateDelegate(ItemStatusUpdate); object[] args = new object[2] { orderId, newStatus }; this.Invoke(statusUpdate, args); } else { }
Note
The following steps should be included in the
Else
body of theInvokeRequired
If
statement that you created in the previous step.Create an
If
statement to make sure that theorderHistory
collection contains a value that uses theorderId
parameter as a key; this ensures that the status update is being sent to an extant list item.In the
If
statement created in the previous step, set the value at theorderId
key of theorderHistory
collection equal to the current DateTime, with the value of thenewStatus
parameter appended to it.Create a nested
If
statement in theIf
statement created in step 3 to verify whether theordersIdList
contains theorderId
in itsItems
collection.If it does not, add the value of the
orderId
to theItems
collection.Set the
SelectedItem
property of theorderIdList
equal to the String value of theorderId
parameter.Set the
Text
property of theorderStatus
object equal to the return value of theGetOrderHistory
method. Pass the String value of theorderId
object as a parameter to theGetOrderHistory
method.if (this.orderHistory.ContainsKey(orderId.ToString())) { this.orderHistory[orderId.ToString()].Add (DateTime.Now + " - " + newStatus); // Update order status data UI info if (this.ordersIdList.Items.Contains(orderId.ToString()) == false) { this.ordersIdList.Items.Add(orderId.ToString()); } this.ordersIdList.SelectedItem = orderId.ToString(); this.orderStatus.Text = GetOrderHistory(orderId.ToString()); }
Compiling the Code
For more information about compiling your code, see Compiling the Code.
In Task 4: Define the WaitingForOrder State, you define the first state in the OrderProcessingWorkflow
by adding child activities.
See Also
Reference
WorkflowRuntime
StartRuntime
ExternalDataExchangeService
AddService
WorkflowInstance
InstanceId
Start
Concepts
Windows Workflow Foundation and Application Communication
State Machine Workflows
Other Resources
Task 4: Define the WaitingForOrder State
Tutorial: Create a State Machine Workflow
Communications
HostCommunication Sample
Ordering State Machine
Simple State Machine
Copyright © 2007 by Microsoft Corporation. All rights reserved.
Last Published: 2010-03-04