The World’s Leading Microsoft .NET Magazine
   
 
The .NET Addict's Blog

My Top Tags

                                                           

My RSS Feeds








I heart FeedBurner

Latest Diggs - Programming

Computers Blogs - Blog Top Sites

Site Hits

Total: 2,821,584
since: 19 Jan 2005

WWF - Using, Activating, and Persisting Workflows

posted Thu 08 Jun 06
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:            

links: digg this    del.icio.us    technorati    reddit




1. Karthick left...
Tue 18 Jul 06 5:25 am

Hi Kevin,

Thank you for your reply.I am using the code what you have posted in the forum.I have some problem in that.

UserTrackingRecord utr = sqlWi.UserEvents.Find(UserKeyIsPartyName);

In this line i don't have a property called find for userevents. I am using RC2 of WF.Plz help me with this.

  • private void button1_Click(object sender, EventArgs e)

    • {

      • using (NewPartyDialog np = new NewPartyDialog())

In this line i cant get the newpartydialog.Is there any other alternative for this in RC2 version.Plz help me with this.

Thank you.

Karthick.


Tag Related Posts

Will Silverlight be DOA?

Mon 16 Apr 07 8:02 P GMT-05

Exploring the MVC Pattern in WPF

Tue 10 Apr 07 12:51 P GMT-05
tags:                      

WPF Bindings == WTF Bindings?

Mon 12 Mar 07 6:31 P GMT-05

Cocoa Bindings vs. WPF Binding

Thu 15 Feb 07 5:41 P GMT-05
tags:                

Ulysses Agenda Makes Redmond Developer News

Wed 29 Nov 06 7:10 P GMT-05
tags:                

Is the continuous beta the new model for Vista?

Tue 21 Nov 06 8:51 P GMT-05
tags:              

Ulysses Agenda : First Cut Networking Design

Thu 14 Sep 06 12:46 A GMT-05
tags:                  

First Impressions of Windows Vista RC1

Thu 07 Sep 06 1:30 P GMT-05
tags:                      

Localizing a WPF Application

Tue 22 Aug 06 11:39 A GMT-05
tags:            

WPF Slide Show and Photo Album

Fri 18 Aug 06 6:48 P GMT-05

Is Windows Workflow Foundation Too Complex?

Fri 18 Aug 06 12:15 P GMT-05

ADO.NET Entity Framework Announced Today!

Wed 16 Aug 06 11:08 A GMT-05

.NET Framework 3.0 June CTP is out!

Fri 23 Jun 06 6:23 P GMT-05
tags:                

Tech-Ed 2006 - Session Reviews

Tue 13 Jun 06 6:22 P GMT-05
tags:                    

Tech-Ed 2006 Day 1 - Registration Day

Sun 11 Jun 06 7:17 P GMT-05

WPF/XAML and LINQ - A match made in heaven

Tue 06 Jun 06 11:32 A GMT-05
tags:                  

Windows Vista Beta 2 - Redeeming Qualities

Tue 30 May 06 2:29 P GMT-05
tags:        

Windows Vista Beta 2 - Day 2

Fri 26 May 06 11:50 A GMT-05
tags:            

Windows Vista Beta 2 - Day 1

Thu 25 May 06 1:01 P GMT-05
tags: