Click here to Skip to main content
Click here to Skip to main content

Using the Windows Workflow Foundation (WF) for Developing an Issue Management System

, 28 Feb 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
This article covers the basics of the Windows Workflow Foundation concepts and the development of an application by using some of the core activities.

Introduction

The article is aimed at learning how the Windows Workflow Foundation (WF) can be implemented in a real-time scenario. Before proceeding further, I would like to state that it is designed in such a way to give your Workflow skills a better insight before really jumping in to the real world and developing enterprise-wide applications. It is basically to attain an Intermediate level in understanding the Windows Workflow Foundation, targeting to develop an Issue Management application.

Before jumping in to such an exciting stuff, I would recommend you go through the basics of the Windows Workflow Foundation from the “n” number of sites that are available.

Once you get some basic understanding of the architecture of the Workflow Foundation and may be an understanding of the development of a “Hello World” sample, you are good to go for developing a scaled-down version of an application before gaining full-fledged expertise on developing an end to end enterprise wide application.

I would like to touch base on the fundamental understanding of the Windows Workflow Foundation before moving further.

Windows Workflow Foundation (WF)

The Windows Workflow Foundation (WF) is nothing but a set of activities. WF has to run within a process, which could be a SharePoint portal server, or a Windows application, or a Console application, or an ASP.NET application. Without a host process, a Workflow is nothing. There are two types of Windows Workflows available.

  1. Sequential Workflow: A flowchart kind of model with prescribed steps for processing a business entity which flows from an activity to activity.
  2. Statemachine Workflow: A business logic that transits from State to State. State is very important as it tells where a process is left and what should be the upcoming state of it before completion.

In this demo application, I am going to use a Statemachine Workflow. Just start visualizing on how an Issue management kind of application would fit in to a Statemachine Workflow before we hit to the real implementation of it.

Windows Workflow Foundation (WF) is basically a layered architecture, with the top layer being the base activity library providing out-of-box activities that enable developers to write the business logic/flow of a process by using conditional loops, web service calling etc. It also offers to construct activities.

The runtime engine is responsible for executing a workflow, responding to various events, State Management, Persistence of Workflow, tracking, and threading to decide whether a Workflow runs on separate threads etc. This not only supports the base activity library, but also provides other accessibility points which are the Runtime services.

Runtime Services are a set of services which are responsible for persisting the state of a Workflow (out-of-the-box, we have the SQL Server Persistence provider; custom persistence services such as DB2, XML structure etc.) communicating between the workflow application and the tracking service, and also for defining how to do the tracking of information as things are executed within a Workflow. It also provides Scheduling Services and Transaction Services.

Let's Dive into the Implementation

With the above mentioned fundamentals, let’s start developing an Issue Management application using the various activities that are shipped with the base activity library of the Windows Workflow Foundation (WF) by leveraging the Statemachine Workflow. The various activities that I am going to use are:

  • State Activity – It represents a state in the StateMachine Workflow.
  • EventDrivenActivity Activity– Wraps an Activity whose execution is triggered by an event.
  • HandleExternalEventActivity Activity – Defines a Workflow communication activity that is used to handle an event that is raised by a local service. It can be used for input and output communications.
  • CallExternalMethodActivity Activity – This can also be used for input and output communications. Will be useful for writing some business rules.
  • Code Activity – Runs a method associated with this Activity, and it will be executed in a synchronous manner.

I am going to exclude the Persistence Services and Tracking Services, but the application would behave as if the Persistence Services are enabled. Please note that for enabling Persistence Services (SQL Server persistence services), you got to do some ground work such as creating a database for storing the state of the Workflow and retrieving it at a later point of time.

Please note that the application that I am going to show is a scaled down version. All the components (such as Workflow, Host, and Services) are bundled in a single application, which you might want to structure into various layers as you plan to create a scaled up version of the same application, and this is what is recommended from a best practices stand-point. The way I am developing the application might not be the best and might not adhere to the standards.

Requirements of the System

Every organization would typically have an issue management system. An application where issues are logged at the helpdesk, and then assigned to concerned persons for resolving, and later they are marked as either Fixed, Cancelled (if it is invalid), or Deferred. Once it is fixed, it will be sent to a QA team who will test/retest the issue before closing it. If the test results are found unsatisfactory, they would have an option to Re-open it, and it will be sent to the Process activity for fixing. Here, we have added an Escalation flow as well, where if an issue is cancelled, it will be escalated to that department manager, where he/she would analyze and then it can be marked as closed or it can be reopened to send it across to the Process activity by the manager.

Development of the Application

With that requirement set, let’s put that in the Windows Workflow Foundation - Statemachine terms. For building such an application on WF, all we need is a Workflow defining the various activities such as New Issue Logging, Processing, Retesting, Escalating, and Terminating once closed. Let’s first design the Workflow diagram by dragging the various activities. The figure below illustrates the end to end workflow of the Issue management application.

StateMachine_small.jpg

Let me give you a brief overview:

  1. Workflow is started by logging a new Issue, and a WorkflowIssueTrackInitialState Activity would be triggered.
  2. The next activity is to hit the ProcessState Activity in which the status of the Issue can be set as either Fixed or Closed.
  3. On the Fixed event, the workflow would move on to the RetestState Activity where it can be stated as Reopen or Close.
  4. On the Reopened event, the Workflow would be moved back to Step 2.
  5. On the Closed event, the Workflow would be terminated.
  6. On the Cancelled event in Step no 2, the Workflow would be moved to the EscalateManagerState Activity where the status of the Issue can be set to either Reopen (ManagerReopen) or Close (ManagerClose).
  7. On the ManagerReopen event, the Workflow would be moved to Step 2.
  8. On the ManagerClose event, the Workflow would be terminated.

Within the various states (WorkflowIssueTrackInitialState, ProcessState, RetestState, EscalateManagerState, Completed), you can see other activities called Fixed, Cancelled (within ProcessState), Reopen, Close, ManagerReopen, ManagerClose which are nothing but Event driven activities (EventDrivenActivity) that are fired whenever the Issue status is changed. In other words, if the Issue is set to Fixed in the Host application, the Fixed Event driven activity would be fired.

Once a particular event is fired through the Event driven activity, we would like to perform some business related operation, for which we use the CallExtenalMethodActivity Activity. In this sample, the CallExtenalMethodActivity Activity just displays the current status of an Issue. And, in one place, we wanted to display manager details, hence we use the CodeActivity activity which will be executed once the Workflow thread hits it.

Nevertheless, for every business operation, we need to go back and forth the parameters for the business logic to deal with. In this demo application, we need to pass various parameters such as Issue log data, Issue Title, Issue description etc for the business logic to make use of it and to run through certain business rules. This would be accomplished by defining a class which should be derived from the ExternalDataEventArgs class.

So far, I have explained the flow involved in this operation and how those can be accomplished by using various activities. However, the activities are not limited to what I have explained above; there are many other activities available in the Model layer of the Windows Workflow Foundation (WF) which can be used as per our requirements. If required, custom activities can also be developed.

Alright, let's switch gears to define the linking between these activities for making it an end to end Workflow.

As I have told earlier, we need to pass parameters for the business logic to deal with, so let’s go ahead and create a parameter which is of the class type. A class with the name “Issue” is created, and various properties are defined such as date created, title, description, and status, as shown below.

    [Serializable]
    public class Issue
    {
        private DateTime dateReported;
        private string title;
        private string description;
        private string status;

        public DateTime DateReported
        {
            get { return dateReported; }
            set { dateReported = value; }
        }

        public string Title
        {
            get { return title; }
            set { title = value; }
        }

        public string Description
        {
            get { return description; }
            set { description = value; }
        }

        public string Status
        {
            get { return status; }
            set { status = value; }
        }

    }

Make sure that the “Issue” class is attributed as Serializable.

Let me now show you how to configure EventDrivenActivity activities (Fixed, Cancelled etc.). We will take the “FixedEventDrivenActivity under the ProcessState activity. Double click on the Fixed EventDrivenActivity, it will take you to the place where you can configure other activities to execute within this. In this flow, the first activity should implement IEventActivity such as HandleExternalEventActivity or Delay Activity etc. Here, we are going to use the HandleExternalEventActivity (let’s name it as HandleExternalEventActivityIssueFixed) activity.

In order to use this activity, two properties should be set which are Interface type and Event name. The Interface that can be set for the Interface type property should be marked with ExternalDataExchangeAttribute. Also, the interfaces that are marked/attributed as ExternalDataExchangeAttribute should have event data that is derived from ExternalDataEventArgs.

Let’s take a slight diversion here, and create an Interface and events in it. Following is the Interface that I created. Name of the file is IIssueManagement.cs.

    [ExternalDataExchangeAttribute]
    public interface IIssueManagement
    {
        event EventHandler<issueeventargs> OnNewIssueCreate;
        event EventHandler<issueeventargs> OnNewIssueFixed;
        event EventHandler<issueeventargs> OnNewIssueCancelled;
        event EventHandler<issueeventargs> OnFixedReOpen;
        event EventHandler<issueeventargs> OnFixedClose;
        event EventHandler<issueeventargs> OnManagerReOpen;
        event EventHandler<issueeventargs> OnManagerClose;

        void NewIssueUpdate(Issue newIssue);
        void NewIssueFixed(Issue newIssue);
        void NewIssueCancelled(Issue newIssue);
        void FixedReopen(Issue newIssue);
        void FixedClose(Issue newIssue);
        void ManagerReopen(Issue newIssue);
        void ManagerClose(Issue newIssue);
    }

The Interface is attributed with ExternalDataExchangeAttribute, and it has an event with the IssueEventArgs class. The IssueEventArgs is a class that should be derived from ExternalDataEventArgs, as explained before. This class is mainly responsible for passing parameters back and forth. Let’s create the IssueEventArgs class.

    [Serializable]
    public class IssueEventArgs : ExternalDataEventArgs
    {
        public IssueEventArgs(Guid workflowId, Issue issue): base(workflowId)
        {
            this.issueToProcess = issue;
            this.workflowId = workflowId;
        }

        private Guid workflowId;
        private Issue issueToProcess;

        public Guid WorkflowId
        {
            get { return workflowId; }
            set { workflowId = value; }
        }

        public Issue IssueToProcess
        {
            get { return issueToProcess; }
            set { issueToProcess = value; }
        }
    }

Note that this class is derived from ExternalDataEventArgs and it has parameters of type Guid and Issue. The Guid type parameter is important here as it refers to the Workflow instance ID, which is going to be unique for every Workflow instance, and will be used to retrieve the Workflow instance at a later point of time as well. Make sure that this class is marked as “Serializable”. Also, note that the constructor of this class calls the base constructor.

Let’s go back to the HandleExternalEventActivityIssueFixed, and go to the properties by right clicking it, and then you can set the Interface property by clicking on the Browse button where the box would be loaded with the Interface “IIssueManagement” which we have created. Similarly, you can set the other mandatory property, that is “Event name”, by selecting the proper event from the dropdown box. After this, we can set the Parameters property.

As we know that IssueEventArgs is the argument that should be passed to the Event as defined in the Interface, we need to create a property in the Workflow class for the IssueEventArgs class to make that appear when the Parameters->Browse button is clicked. This can be accomplished by going to the class file of the Workflow and by adding a property of the IssueEventArgs class type as shown below:

public IssueEventArgs issueEventArgs = default(IssueEventArgs);

Now, go to the properties of HandleExternalEventActivityIssueFixed, and by clicking on the Browse button of Parameters, you would be able to see this property which you can set. Shown below is the final HandleExternalEventActivityIssueFixed Activity with properties.

handleExternalEventActivity.JPG

Now, I want to apply some business rules on the supplied Issue parameter. In this demo, I am going to display the status of the Issue and some message. For this, we will use the CallExternalMethodActivity (let’s name it as CallExternalMethodActivityNewIssueFixed). For this, we need to place the CallExternalMethodActivity activity in to the Fixed Event flow. For this, again, Interface and Method name properties are mandatory, and the Interface should be marked with ExternalDataExchangeAttribute and method (which we need to set) should be part of this interface. So, we will be using the IIssueManagement Interface for this, and we will create a method (with a parameter as the Issue class) inside the Interface as shown below, and we will set the Interface and Method name properties for it.

    void NewIssueFixed(Issue newIssue);

Since we have used an “Issue” type as parameter to this method, we will set the issueEventArgs in the parameter properties of the CallExternalMethodActivityNewIssueFixed Activity. Below is the final CallExternalMethodActivityNewIssueFixed with properties.

callExternalMethodActivity_small.jpg

You need to perform these steps for all the other external Event driven activities such as Cancelled, Reopen, Close, ManagerReopen, ManagerClose.

I have used CodeActivity as well in the Cancelled Event driven activity. You can refer to the source code for it.

As the Interface (IIssueManagement) is in place that works with the Workflow, we can create a class derived from that Interface which will provide the services between the Host application and the Workflow instance. Typically, it would have a method implementation for all the methods that are defined in the interface for implementing some business logic at each event. To do this, let me add a new class file and name the class as IssueManagementService. Below is the code for all the methods.

        public void NewIssueUpdate(Issue issue)
        {
            Console.WriteLine("New Issue has been received.");
            Console.WriteLine("Issue Current Status: " + issue.Status);
            Console.Write("Enter the Status <fixed>: ");
        }

        public void NewIssueFixed(Issue issue)
        {
            Console.WriteLine("New Issue has been Fixed.");
            Console.WriteLine("Issue Current Status: " + issue.Status);
            Console.Write("Enter the Status <reopen>: ");
        }

        public void NewIssueCancelled(Issue issue)
        {
            Console.WriteLine(
                "Issue has been Cancelled. It has been escalated to Manager.");
            Console.WriteLine("Issue Current Status: " + issue.Status);
            Console.WriteLine("Enter the Option <managerreopen>: ");
        }

        public void FixedReopen(Issue issue)
        {
            Console.WriteLine("Fixed Issue has been ReOpened.");
            Console.WriteLine("Issue Current Status: " + issue.Status);
            Console.Write("Enter the Status <fixed>: ");
        }

        public void FixedClose(Issue issue)
        {
            Console.WriteLine("Issue has been CLOSED.");
            Console.WriteLine("Issue Current Status: " + issue.Status);
        }

        public void ManagerReopen(Issue issue)
        {
            Console.WriteLine("Manager has ReOpened the Issue.");
            Console.WriteLine("Issue Current Status: " + issue.Status);
            Console.Write("Enter the Status <fixed>: ");
        }

        public void ManagerClose(Issue issue)
        {
            Console.WriteLine("Issue has been escalated to Manager and it is CLOSED.");
            Console.WriteLine("Issue Current Status: " + issue.Status);
        }

I will create various other methods that would be called from the host application for invoking an event (HandleExternalEventActivity) for workflow instance to execute further. First, let’s create the NewIssueCreate method for invoking the Workflow by creating an instance of it. Below is the piece of code which would typically have adding services, creating workflow instance, invoking the workflow instance, and raising the event (to fire NewIssue – an EventDrivenActivity) activity which in turn fires the HandleExternalEventActivityNewIssue under this. This method would return the Workflow instance ID which is of type Guid which will be used for further processing of the Workflow instance.

        public Guid NewIssueCreate(Issue newIssue)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Issue Received with Status: " + newIssue.Status);

            waitHandle = new AutoResetEvent(false);
            workflowRuntime = new WorkflowRuntime();

            ExternalDataExchangeService dataService = 
                      new ExternalDataExchangeService();
            workflowRuntime.AddService(dataService);

            IssueManagementService issueMgmtService = 
                      IssueManagementService.IMInstance;
            dataService.AddService(issueMgmtService);

            workflowRuntime.WorkflowCompleted +=
                new EventHandler<workflowcompletedeventargs>(
                workflowRuntime_WorkflowCompleted);

            workflowRuntime.StartRuntime();

            WorkflowInstance workflowInstance = 
                workflowRuntime.CreateWorkflow(typeof(WorkflowIssueTrack));
            workflowInstance.Start();
           
            bool eventResult = RaiseEvent(OnNewIssueCreate, 
                newIssue, workflowInstance.InstanceId);

            if (eventResult == false)
            {
                Console.WriteLine("Invalid Status is entered.");
            }
            
            return workflowInstance.InstanceId;
        }

Please note that, first we need to start the Workflow instance and we need to raise the event that is desired. For other methods shown below, we will not start the Workflow instance as it will be running behind the scenes in this particular sample. However, in a real time application, the Workflow instance should be started for every operation before raising the desired event.

Below is the code for various methods that will be called based on the status of the Issue that is set by the user:

        public void NewIssueFix(Issue newIssue, Guid workflowId)
        {
            Console.ForegroundColor = ConsoleColor.Cyan;

            Console.WriteLine("Issue Received with Status: " + newIssue.Status);
            bool eventResult = RaiseEvent(OnNewIssueFixed, newIssue, workflowId);

            if (eventResult == false)
            {
                Console.WriteLine("Invalid Status is entered.");
            }

        }

        public void NewIssueCancel(Issue newIssue, Guid workflowId)
        {
            Console.ForegroundColor = ConsoleColor.Green;

            Console.WriteLine("Issue Received with Status: " + newIssue.Status);
            bool eventResult = RaiseEvent(OnNewIssueCancelled, newIssue, workflowId);

            if (eventResult == false)
            {
                Console.WriteLine("Invalid Status is entered.");
            }
        }

        public void IssueReopen(Issue newIssue, Guid workflowId)
        {

            Console.ForegroundColor = ConsoleColor.DarkYellow;

            Console.WriteLine("Issue Received with Status: " + newIssue.Status);
            bool eventResult = RaiseEvent(OnFixedReOpen, newIssue, workflowId);

            if (eventResult == false)
            {
                Console.WriteLine("Invalid Status is entered.");
            }
        }

        public void ManagerReopen(Issue newIssue, Guid workflowId)
        {

            Console.ForegroundColor = ConsoleColor.White;

            Console.WriteLine("Issue Received with Status: " + newIssue.Status);
            bool eventResult = RaiseEvent(OnManagerReOpen, newIssue, workflowId);

            if (eventResult == false)
            {
                Console.WriteLine("Invalid Status is entered.");
            }
        }

        public void ManagerClose(Issue newIssue, Guid workflowId)
        {

            Console.ForegroundColor = ConsoleColor.DarkGray;

            Console.WriteLine("Issue Received with Status: " + newIssue.Status);
            bool eventResult = RaiseEvent(OnManagerClose, newIssue, workflowId);

            if (eventResult == false)
            {
                Console.WriteLine("Invalid Status is entered.");
            }
        }

        public void NewIssueClose(Issue newIssue, Guid workflowId)
        {

            Console.ForegroundColor = ConsoleColor.Gray;

            Console.WriteLine("Issue Received with Status: " + newIssue.Status);
            bool eventResult = RaiseEvent(OnFixedClose, newIssue, workflowId);

            if (eventResult == false)
            {
                Console.WriteLine("Invalid Status is entered.");
            }
        }

We will be calling the methods of this class from an Instance property (which returns an object of the class).

First, I will call the CreateNewIssue method, and then I will let the user enter an option that is acceptable for the Workflow to move on to the next state.

            Console.WriteLine("[Workflow is Started with New Issue]");
            Console.WriteLine();
            Guid workflowInstanceId = 
                IssueManagementService.IMInstance.NewIssueCreate(issue);
            //Console.WriteLine("Issued to Workflow. Workflow ID: " + 
                workflowInstanceId.ToString());
            string readInput;
            do
            {
                readInput = Console.ReadLine();
                issue.Status = readInput;

                if (readInput.ToLower() == "fixed")
                {
                    IssueManagementService.IMInstance.NewIssueFix(issue, 
                        workflowInstanceId);
                }
                else if (readInput.ToLower() == "cancelled")
                {
                    IssueManagementService.IMInstance.NewIssueCancel(issue, 
                        workflowInstanceId);
                }
                else if (readInput.ToLower() == "reopen")
                {
                    IssueManagementService.IMInstance.IssueReopen(issue, 
                        workflowInstanceId);
                }
                else if (readInput.ToLower() == "close")
                {
                    IssueManagementService.IMInstance.NewIssueClose(issue, 
                        workflowInstanceId);
                }
                else if (readInput.ToLower() == "managerreopen")
                {
                    IssueManagementService.IMInstance.ManagerReopen(issue, 
                        workflowInstanceId);
                }
                else if (readInput.ToLower() == "managerclose")
                {
                    IssueManagementService.IMInstance.ManagerClose(issue, 
                        workflowInstanceId);
                }
            } while (IssueManagementService.watchProcess == "Running");

Output

Let’s see the final output which covers all the activities.

Output1_small.jpg

Let’s try to supply an option that is not expected after a state; the Workflow should throw an exception. Below is the output when trying to “Close” an Issue instead of marking it as Fixed or Cancelled.

Output2_small.jpg

You can see the text in red color (an exception thrown) which was the result of calling the wrong activity. Later “Cancelled” was called, and it went to the Manager. After closing by Manager (ManagerClose), the Workflow is terminated. Similarly, you can remove one flow from ManagerClose to the Completed activity. Now, even if you supply the status as ManagerClose, the Workflow instance would not be terminated.

Output3_small.jpg

Also, you can introduce a new flow from one of the EventDriven activities to some other state and, you can execute the application to see whether you are able to pass the status of the newly introduced activity.

Conclusion

Please note that, this application is developed assuming a single thread of Workflow running during the complete life cycle of the Issue. But in real-world, this kind of running of a Workflow thread is not possible. Once the Issue is submitted, the Workflow would be idle, and only on the next change of status, the Workflow would be made to run. For getting the idle Workflow later, Persistence service should be used, and also Tracking Services to determine the state of the Workflow instance. These things will be explained in my next posting.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Inala Uma Shankar
Software Developer Cognizant Technology Solutions
United States United States
My areas of interest are exploring new technologies from Microsoft, UML, Design Patterns, Designing.....

Comments and Discussions

 
GeneralThanks and Request Pinmembertoanlh19-Mar-09 15:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 28 Feb 2008
Article Copyright 2008 by Inala Uma Shankar
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid