First, let me preface this blog post by indicating that the code here runs against the February CTP of WWF. I haven't yet determined if it works on May. I am in the middle of working on a bigger, more all-encompassing project that involves WWF that will provide far more illustration on WWF than what is here .. but since I promised way back in March that I would include the code for persistence and tracking...here it is.
The application (remember, the
restaurant workflow) consists of a Windows application, a class library containing shared services that can be used by the workflow and by the Windows application, and a class library containing the workflow itself. The schemas for tracking and persistence were already created in my custom "Workflow" database (SQL 2005) using tools provided with the WinFX SDK.
Here is the code for the main form:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Workflow.Runtime.Hosting;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Tracking;
using RestaurantSharedServices;
using System.Threading;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
private WorkflowRuntime wfRuntime;
private Dictionary<Guid, StateMachineWorkflowInstance> stateMachines;
private RestaurantService restService;
private SqlWorkflowPersistenceService sqlPersistence;
private SqlTrackingService sqlTracking;
private delegate void AddListItemDelegate(Guid wfGuid, string partyName);
private delegate void UpdateListItemDelegate(Guid wfGuid, string newStatus);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
stateMachines = new Dictionary<Guid, StateMachineWorkflowInstance>();
StartWorkflowRuntime();
}
private void StartWorkflowRuntime()
{
wfRuntime = new WorkflowRuntime();
restService = new RestaurantService();
wfRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(wfRuntime_WorkflowCompleted);
wfRuntime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowIdled);
wfRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(wfRuntime_WorkflowTerminated);
ExternalDataExchangeService eds = new ExternalDataExchangeService();
wfRuntime.AddService(eds);
eds.AddService(restService);
sqlPersistence = new SqlWorkflowPersistenceService(
"server=localhost; initial catalog=Workflows; user id=wf;password=workflow;",
true, new TimeSpan(0, 0, 10, 0), new TimeSpan(0, 0, 10, 0));
sqlTracking = new SqlTrackingService(
"server=localhost; initial catalog=Workflows; user id=wf;password=workflow;");
wfRuntime.AddService(sqlPersistence);
wfRuntime.AddService(sqlTracking);
wfRuntime.StartRuntime();
LoadPersistedFlows();
}
private void LoadPersistedFlows()
{
foreach (SqlPersistenceWorkflowInstanceDescription sqlwid in sqlPersistence.GetAllWorkflows())
{
Guid wfGuid = sqlwid.WorkflowInstanceId;
ListViewItem lvi = new ListViewItem();
WorkflowInstance wi = wfRuntime.GetWorkflow(wfGuid);
wi.Load();
StateMachineWorkflowInstance smwi = new StateMachineWorkflowInstance(wfRuntime, wfGuid);
SqlTrackingQuery sqlQ = new SqlTrackingQuery(
"server=localhost; initial catalog=Workflows; user id=wf; password=workflow;");
SqlTrackingWorkflowInstance sqlWi;
sqlQ.TryGetWorkflow(wfGuid, out sqlWi);
UserTrackingRecord utr = sqlWi.UserEvents.Find(UserKeyIsPartyName);
stateMachines.Add(wfGuid, smwi);
lvi.Text = wfGuid.ToString();
lvi.Tag = wfGuid;
if (utr!= null)
lvi.SubItems.Add((string)utr.UserData);
lvi.SubItems.Add(smwi.CurrentStateName);
listView1.Items.Add(lvi);
}
}
private static bool UserKeyIsPartyName(UserTrackingRecord utr)
{
return utr.UserDataKey == "PartyName";
}The code above initializes the workflow engine and, more importantly,
loads persisted workflows from the database into memory. This is absolutely crucial in
any workflow application because the whole idea behind the workflow runtime is that it can manage short
or long-running workflows. To manage a long-running workflow, you need to be able to retrieve the state of previously persisted workflows. The code for my workflow persists itself whenever the workflow idles. Below is more of the code that supports the main form, as well as the workflow event handlers:
private Guid StartPartyWorkflow(Dictionary<string, object> parms)
{
WorkflowInstance wi = wfRuntime.CreateWorkflow(typeof(RestaurantWorkflows.RestaurantWorkflow), parms);
StateMachineWorkflowInstance smwi = new StateMachineWorkflowInstance(wfRuntime, wi.InstanceId);
wi.Start();
stateMachines.Add(wi.InstanceId, smwi);
return wi.InstanceId;
}
void wfRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
{
throw new Exception("The method or operation is not implemented.");
}
void wfRuntime_WorkflowIdled(object sender, WorkflowEventArgs e)
{
this.Invoke(new UpdateListItemDelegate(UpdateListItem),
new object[] { e.WorkflowInstance.InstanceId,
"Running" });
}
void wfRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
throw new Exception("The method or operation is not implemented.");
}
private void button1_Click(object sender, EventArgs e)
{
using (NewPartyDialog np = new NewPartyDialog())
{
if (np.ShowDialog() == DialogResult.OK)
{
bool reservation = np.HasReservation;
Dictionary<string, object> parms = new Dictionary<string, object>();
parms.Add("Seated", false);
parms.Add("PartyName", np.PartyName);
parms.Add("Reservation", reservation);
if (reservation)
parms.Add("ReservationTime", np.ReservationTime);
parms.Add("ArrivalTime", DateTime.Now);
Guid wfGuid = StartPartyWorkflow(parms);
this.Invoke(new AddListItemDelegate(AddListItem), new object[] { wfGuid, np.PartyName });
restService.RaisePartyArrivedEvent(wfGuid);
}
}
}
private void AddListItem(Guid wfGuid, string partyName)
{
ListViewItem lvi = new ListViewItem(wfGuid.ToString());
lvi.SubItems.Add(partyName);
lvi.SubItems.Add("--");
lvi.Tag = wfGuid;
listView1.Items.Add(lvi);
}
private void UpdateListItem(Guid wfGuid, string newStatus)
{
ListViewItem lvi = null;
foreach (ListViewItem itm in listView1.Items)
{
if (itm.Tag.ToString() == wfGuid.ToString())
lvi = itm;
}
lvi.SubItems[2].Text = newStatus;
}
private void button2_Click(object sender, EventArgs e)
{
ListViewItem lvi = listView1.SelectedItems[0];
Guid wfGuid = (Guid)lvi.Tag;
StateMachineWorkflowInstance smwi = stateMachines[wfGuid];
SqlTrackingQuery sqlQ = new SqlTrackingQuery(
"server=localhost; initial catalog=Workflows; user id=wf; password=workflow;");
SqlTrackingWorkflowInstance sqlWi;
sqlQ.TryGetWorkflow(wfGuid, out sqlWi);
sqlWi.AutoRefresh = true;
StringBuilder sb = new StringBuilder();
/*foreach ( ActivityTrackingRecord atr in sqlWi.ActivityEvents)
{
sb.AppendFormat("{0} at {1}\n", atr.ActivityType.ToString(), atr.EventDateTime.ToString());
}*/
foreach (UserTrackingRecord utr in sqlWi.UserEvents)
{
sb.AppendFormat("{0} ({1}) at {2}\n", utr.ActivityType.ToString(),
utr.UserData,
utr.EventDateTime.ToString());
}
MessageBox.Show(sb.ToString());
}
The code is not in the best shape that it could be, but I went back into the archives and found that I had promised to post this code back in March...woops! Again, I will be creating a new all-encompassing project based on the May CTP that will include WWF, WCF, WPF, and DLinq all in the same solution...so stay tuned for that!
The main point of this post is to show you that after you create a workflow (see the previous blog entry on WWF), there is still some code you need to write in order to make that workflow function properly. For a state machine workflow, you need to be able to determine the current state of the workflow. If, for example, you were using WWF to maintain a defect tracking system - you would need the
SQL Tracking Service so that you could run queries to obtain all of the state changes for a given workflow (remember that a workflow instance is just that - one item that follows a workflow..in this case its one customer... in the case of a defect tracking system it would be one defect). The
SQL Persistence Service allows the state of all open workflows managed by a given Workflow runtime to be stored in and retrieved from SQL server.
While most WWF "Hello World" type applications skip over persistence and tracking: they are absolutely crucial to any commercial-grade application utilizing WWF.
tags: vista winfx tracking workflow wwf persistence
links: digg this del.icio.us technorati reddit