A Shopping Cart Example


The remainder of this chapter describes an example workflow application that models the familiar concept of a shopping cart. Although this workflow is nowhere near what a real-world shopping cart application needs to be, it is a great example for illustrating the concepts conveyed in this chapter regarding state machines.

The workflow starts out in a state that is simply waiting for something to happen. After a new shopping session is created, the workflow moves to the Shopping state. It is in this state that the user can add new items to the workflow, as well as check out. When the user says that he or she wants to check out, the appropriate EventDriven activity makes sure the cart is valid. In this workflow, the number of items a user can purchase cannot exceed three. (This may sound like a strange rule, but it works for this example.)

If the user’s order is valid, the workflow proceeds to the WaitingToShip state. However, if more than three items were added to the order, the workflow enters the InvalidOrder state. When the workflow is in the InvalidOrder state, one of two things can happen. The first is a timeout. If nothing happens to correct the invalid state of the order within ten seconds, the order times out, and a transition is made to the workflow’s completed state. However, if an administrator decides to override the order and allow it to ship, a manual state transition can occur through the ordering application (discussed later).

In addition, if the workflow is in either the Shopping or InvalidOrder state, a cancel event can be passed to the workflow, which immediately moves the state machine to its completed state. Finally, if a command to ship the order is passed while the workflow is in the WaitingToShip state, the completed state is transitioned to.

To illustrate the use of this workflow inside an application, a Windows Forms application has been developed to control the process (see Figure 10-7). As you can see, there is a button to start a new shopping session. When this button is clicked, a new workflow instance is started, and a description of the order is added to the list below.

image from book
Figure 10-7

When at least one shopping session is started and selected in the list, the controls at the bottom of the screen that are appropriate for the current workflow state are enabled. For example, the Ship Order button is not enabled until a checkout command has been issued. Also, items can be added only while the workflow is in the Shopping state.

Now that the general application has been explained, it’s time to get down to business. The first thing to be discussed is the shopping cart workflow itself. Figure 10-8 shows the state machine as it is displayed in the Visual Studio designer.

image from book
Figure 10-8

All the workflow states can be found here. You can see that the WaitingForSession state is designated as the workflow’s initial state. This is the state that is immediately entered when the host application calls the WorkflowInstance.Start method. It does not exit that state and enter the Shopping state until the event configured in the OnSessionCreated EventDriven activity is raised.

The Shopping and InvalidOrder states are children, or leaf states, of the OrderActive state. As mentioned, this parent state handles the event configured in the OnOrderCanceled EventDriven activity. This employs the concept of recursive event composition introduced earlier in the chapter.

Also notice that the only connector line going from the InvalidOrder state is to the OrderCompleted state. This state transition occurs if there is a timeout while in the InvalidOrder state (as shown in Figure 10-9). As mentioned earlier, an administrator can override this invalid order and allow it to ship, if desired. Because there is no event wired to perform this transition, there is code in the client application that uses the SetState method to perform this transition. (The complete application code is shown later.)

image from book
Figure 10-9

The final piece of the workflow is the code that sits behind the visual representation:

  public sealed partial class ShoppingCartWorkflow: StateMachineWorkflowActivity {     public ItemAddedEventArgs itemAddedEventArgs = default(ItemAddedEventArgs);     private Order order = new Order();     private bool orderTimedOut = false;     private bool canceled = false;     public bool Canceled     {         get { return canceled; }     }     public Order Order     {         get { return this.order; }     }     public bool OrderTimedOut     {         get { return orderTimedOut; }     }     public ShoppingCartWorkflow()     {         InitializeComponent();     }     private void addItemToOrder_ExecuteCode(object sender, EventArgs e)     {         this.order.Items.Add(itemAddedEventArgs.Item);     }     private void CheckItemCount(object sender, ConditionalEventArgs e)     {         e.Result = this.order.Items.Count > 3;     }     private void setTimeOutFlag_ExecuteCode(object sender, EventArgs e)     {         this.orderTimedOut = true;     }     private void handleOrderCanceled_Invoked(object sender,         ExternalDataEventArgs e)     {         this.canceled = true;     } } 

The public properties in this code allow communication back to the host after the workflow is completed. In addition, there are event handler methods that set flags for processing. One of these methods is the code condition that is called when the user issues a checkout command, which verifies that this is a valid order.

The following code is the Windows Forms application that controls the workflow and provides a front end for its functionality. (This application was previously shown in Figure 10-7.) Because this is a book about Windows Workflow Foundation, the focus of this discussion is the part of the code that interacts with the Windows Workflow Foundation API classes. The EnableControls method uses the StateMachineWorkflowInstance class to determine the state that the workflow is currently in and appropriately enable the form’s controls.

  public partial class MainForm : Form {     private WorkflowRuntime runtime;     private ShoppingCartService shoppingCartService;     private List<StateMachineWorkflowInstance> orderWorkflows;     private delegate void DefaultDelegate();     private delegate void ShowOrderConfirmationDelegate(Order order,         bool orderTimedOut);     private StateMachineWorkflowInstance SelectedOrderWorkflow     {         get         {             if (lstOrders.SelectedItem != null)                 return orderWorkflows[lstOrders.SelectedIndex];             else                 return null;         }     }     public MainForm()     {         InitializeComponent();     }     private void MainForm_Load(object sender, EventArgs e)     {         this.orderWorkflows = new List<StateMachineWorkflowInstance>();         this.DisableControls();         this.ConfigureRuntime();     }     private void ConfigureRuntime()     {         runtime = new WorkflowRuntime();         runtime.WorkflowIdled +=             new EventHandler<WorkflowEventArgs>(runtime_WorkflowIdled);         runtime.WorkflowCompleted += new             EventHandler<WorkflowCompletedEventArgs>(runtime_WorkflowCompleted);         runtime.WorkflowTerminated += new             EventHandler<WorkflowTerminatedEventArgs>(runtime_WorkflowTerminated);         ExternalDataExchangeService dataService =             new ExternalDataExchangeService();         runtime.AddService(dataService);         shoppingCartService = new ShoppingCartService();         dataService.AddService(shoppingCartService);     }     private void runtime_WorkflowIdled(object sender, WorkflowEventArgs e)     {         this.UpdateOrders();     }     private void runtime_WorkflowTerminated(object sender,         WorkflowTerminatedEventArgs e)     {         this.UpdateOrders();     }     private void runtime_WorkflowCompleted(object sender,         WorkflowCompletedEventArgs e)     {         this.UpdateOrders();         if(!(bool)e.OutputParameters["Canceled"])             this.ShowOrderConfirmation((Order)e.OutputParameters["Order"],                 (bool)e.OutputParameters["OrderTimedOut"]);     }     private void ShowOrderConfirmation(Order order, bool orderTimedOut)     {         if (this.InvokeRequired)         {             this.Invoke(new ShowOrderConfirmationDelegate(ShowOrderConfirmation),                 new object[] { order, orderTimedOut });         }         else         {             StringBuilder sb = new StringBuilder();             if (!orderTimedOut)             {                 sb.Append("Your order was completed. " +                     "The following items were ordered:");                 sb.Append(Environment.NewLine);                 foreach (Item item in order.Items)                 {                     sb.Append(Environment.NewLine);                     sb.Append(item.ItemId);                 }             }             else             {                 sb.Append("Your order was invalid and timed-out " +                     "before someone approved it.");             }             MessageBox.Show(sb.ToString(), "Your Order",                 MessageBoxButtons.OK, MessageBoxIcon.Information);         }     }     private void btnNewSession_Click(object sender, EventArgs e)     {         WorkflowInstance instance = runtime.CreateWorkflow(             typeof(ShoppingCartWorkflow));         instance.Start();         StateMachineWorkflowInstance stateMachineInstance =             new StateMachineWorkflowInstance(this.runtime,                 instance.InstanceId);         this.orderWorkflows.Add(stateMachineInstance);         this.shoppingCartService.CreateSession(instance.InstanceId);     }     private void UpdateOrders()     {         if (this.InvokeRequired)         {             this.Invoke(new DefaultDelegate(UpdateOrders));         }         else         {             Guid tempGuid = default(Guid);             if (this.SelectedOrderWorkflow != null)                 tempGuid = this.SelectedOrderWorkflow.InstanceId;             this.DisableControls();             lstOrders.Items.Clear();             foreach (StateMachineWorkflowInstance i in this.orderWorkflows)             {                 string description = i.InstanceId.ToString() + " (" +                     (i.PossibleStateTransitions.Count > 0 ? i.CurrentStateName :                         "Completed") + ")";                 int index = lstOrders.Items.Add(description);                 if (tempGuid != default(Guid) && tempGuid == i.InstanceId)                     lstOrders.SelectedIndex = index;             }         }     }     private void DisableControls()     {         btnCheckOut.Enabled = false;         btnCancelOrder.Enabled = false;         btnShipOrder.Enabled = false;         btnAddItem.Enabled = false;         txtItem.Clear();         txtItem.Enabled = false;     }     private void EnableControls()     {         switch (this.SelectedOrderWorkflow.CurrentStateName)         {             case "Shopping":                 txtItem.Enabled = true;                 btnAddItem.Enabled = true;                 btnCheckOut.Enabled = true;                 btnCancelOrder.Enabled = true;                 break;             case "InvalidOrder":                 btnCancelOrder.Enabled = true;                 btnShipOrder.Enabled = true;                 break;             case "WaitingToShip":                 btnShipOrder.Enabled = true;                 break;         }     }     private void lstOrders_SelectedIndexChanged(object sender,         EventArgs e)     {         this.DisableControls();         if (lstOrders.SelectedItem != null)             this.EnableControls();     }     private void btnCheckOut_Click(object sender, EventArgs e)     {         if (this.SelectedOrderWorkflow != null)         {             this.shoppingCartService.CheckOut(                 this.SelectedOrderWorkflow.InstanceId);         }     }     private void btnCancelOrder_Click(object sender, EventArgs e)     {         if (this.SelectedOrderWorkflow != null)         {             this.shoppingCartService.CancelOrder(                 this.SelectedOrderWorkflow.InstanceId);             this.UpdateOrders();         }     }     private void btnShipOrder_Click(object sender, EventArgs e)     {         if (this.SelectedOrderWorkflow != null)         {             bool orderShipped = false;             if (this.SelectedOrderWorkflow.CurrentStateName                 == "InvalidOrder")             {                 DialogResult res = MessageBox.Show("Ship Order?",                     "The order is currently in an invalid state, " +                     "do you want to send it anyway?",                     MessageBoxButtons.YesNo, MessageBoxIcon.Question);                 if (res == DialogResult.Yes)                 {                     this.SelectedOrderWorkflow.SetState(                         "OrderCompleted");                     orderShipped = true;                 }             }             else             {                 this.shoppingCartService.ShipOrder(                     this.SelectedOrderWorkflow.InstanceId);                 orderShipped = true;             }             if (orderShipped)             {                 this.UpdateOrders();             }         }     }     private void btnAddItem_Click(object sender, EventArgs e)     {         if (this.SelectedOrderWorkflow != null)         {             this.shoppingCartService.AddItem(                 this.SelectedOrderWorkflow.InstanceId,                 new Item(txtItem.Text.Trim()));         }         this.txtItem.Clear();     } } 

The event handlers for the form’s buttons use an instance of the ShoppingCartService class that was developed for this example. Although this class is not shown in this chapter, you can find it in the book’s example code.

The event handler for the Ship Order button, btnShipOrder_Click, uses the StateMachineWorkflow Instance class as well as the call to the SetState method. A check is made in this method to see whether the workflow is currently in the InvalidOrder state. If it is, a confirmation message is displayed to make sure that the user really wants to ship an invalid order. If the order is not in this invalid state, it is shipped by calling the ShipOrder method of the ShoppingCartService class.



Professional Windows Workflow Foundation
Professional Windows Workflow Foundation
ISBN: 0470053860
EAN: 2147483647
Year: 2004
Pages: 118
Authors: Todd Kitta

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