.NET

Windows Workflow 4: Human based workflows with a Meeting Scheduler Sample

 

1. Introduction

A few weeks ago I was playing around with WF 4.0 declarative workflows. One of the questions that a lot of people ask about workflow 4 is how to integrate manual tasks into the workflow process. The samples provided and the articles I had read online did not cover any real life application of workflow process (by real life, I mean having the workflow talk to “practical” WCF service, none of that simplified “Hello World” examples).

2. Scenario

The scenario that we will try to solve is that of scheduling company meetings. This seems like a simple problem, but can be proven a pain in the neck for a lot employees. Today’s workers store much of the essential data online and this can be used to provide automated support for scheduling meetings. In particular, users are using an online calendar which given the appropriate dates and time would keep a track of their schedule. The business in question also maintains a database with the contact information of its employees, their e-mail addresses or mobile phone numbers, as well as the name of a representative who should substitute them in case of absence. The business has respective communication services that allow for an e-mail message or an SMS to be sent using this information.
We shall concentrate on a slightly simplified scenario, where meetings have exactly three participants. If a participant is unavailable, the representative should be chosen and invited (independent of their availability) otherwise the participant is invited. Users use instances of the same calendar service.
To trigger the process, the secretary provides the names of three employees and a timeframe for the meeting (2 alternative time slots).

3. Analysis

The first step of the application workflow as shown in the figure below uses the input from the secretary to determine the participant of a particular meeting before sending out notifications to the respective participant depending on their preferred method of notification.

4. Design

The workflow service will receive requests from the user, to the initial screening of meeting participants and send notifications to the respective participants. The service will be long-running (it can take days or months to complete), durable (it can save state and resume later) and instrumented (both developers and users will be to know what’s going on without having to debug the service). This will be implemented using WCF and WF to achieve all this as shown in the figure below architectural diagram of the solution.

Scheduler Workflow
Scheduler Workflow
SchedulerArchitectureDiagram
Scheduler Architecture Diagram

5. Implementation

5.1 Meeting Availability Service

We start by implementing the MeetingAvailabilityService which basically will be responsible for checking the employee’s availability.

  1. Launch Visual Studio
  2. Select File New Project
  3. Select WorkFlow under Installed Templates Visual C#
  4. Select WCF Workflow Service Application
  5. Click OK
New WCF Workflow Service Application
New WCF Workflow Service Application

 

This will create a project using a Workflow Service Template, with three built-in activities: Sequence, ReceiveRequest, and SendResponse (see the designer window in the figure below).

Sequential Service
Sequential Service

Sequence Activity
The SequenceActivity is a CompositeActivity, meaning the SequenceActivity can contain other activities. The SequenceActivity class coordinates the running of a set of child activities in an ordered manner, one at a time. The SequenceActivity is completed when the final child activity is finished.

Receive Activity
Receive Activity will wait until a client connects to the service and then runs its contained child activities. MSDN.

Send Activity
Client activity that models the synchronous invocation of a service operation.

5.2 Defining the domain model

We shall define the Employee and Employee Calendar classes as shown below:

Employee.cs

public class Employee
    {
        public Int32 EmployeeID { get; set; }
        public string Surname { get; set; }
        public string OtherNames { get; set; }
        public override string ToString()
        {
            return Surname + " " + OtherNames;
        }
    }

EmployeeCalendar.cs

public class EmployeeCalendar
    {
        public Int32 EmployeeCalendarID { get; set; }
        public DateTime TimeOfDay { get; set; }
        public bool IsAvaiable { get; set; }
        public Employee Participant { get; set; }
        public Int32 EmployeeID { get; set; }
    }

We shall then add a service that checks for a particular participant’s availability for a meeting.

IParticipantAvailability.cs

 [ServiceContract]
    public interface IParticipantAvailability
    {
        [OperationContract]
        bool CheckParticipantAvailability(Int32 EmployeeID,DateTime MeetingTimeSlot1,DateTime MeetingTimeSlot2,DateTime MeetingTimeSlot3);
    }

ParticipantAvailability.cs

    [Serializable]
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class ParticipantAvailability:IParticipantAvailability
    {
        private List employees;
        private List employeeCalendars;
        public ParticipantAvailability()
        {
            employees = new List()
                {
                new Employee {EmployeeID=1,Surname="John",OtherNames="Doe"},
                new Employee {EmployeeID=2,Surname="Jane",OtherNames="Doe"},
                new Employee {EmployeeID=3,Surname="Michael",OtherNames="Jordan"},
                new Employee {EmployeeID=4,Surname="John",OtherNames="Representative2"},
                new Employee {EmployeeID=5,Surname="John",OtherNames="Representative3"},
                new Employee {EmployeeID=6,Surname="John",OtherNames="Representative4"},
                new Employee {EmployeeID=7,Surname="Jane",OtherNames="Representative1"},
                new Employee {EmployeeID=8,Surname="Jane",OtherNames="Representative2"},
                new Employee {EmployeeID=9,Surname="Jane",OtherNames="Representative3"},
                new Employee {EmployeeID=10,Surname="Michael",OtherNames="Jordan Representative1"},
                new Employee {EmployeeID=11,Surname="Michael",OtherNames="Jordan Representative2"},
                new Employee {EmployeeID=12,Surname="Peter",OtherNames="Kamau"},
                new Employee {EmployeeID=13,Surname="Raila",OtherNames="Odinga"},
                new Employee {EmployeeID=14,Surname="Uhuru",OtherNames="Kenyatta"}
                };
            employeeCalendars = new List()
                {
                    new EmployeeCalendar {EmployeeCalendarID=1,EmployeeID=1, IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=2,EmployeeID=2,IsAvailable=true,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=3,EmployeeID=3,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=4,EmployeeID=4,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=5,EmployeeID=5,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=6,EmployeeID=6,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=7,EmployeeID=7,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=8,EmployeeID=8,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=9,EmployeeID=9,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=10,EmployeeID=10,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=11,EmployeeID=11,IsAvailable=false,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=12,EmployeeID=12,IsAvailable=true,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=13,EmployeeID=13,IsAvailable=true,TimeOfDay=DateTime.Now},
                    new EmployeeCalendar {EmployeeCalendarID=14,EmployeeID=14,IsAvailable=true,TimeOfDay=DateTime.Now}
                };
        
        }
        public bool CheckParticipantAvailability(Int32 EmployeeID, DateTime MeetingTimeSlot1, DateTime MeetingTimeSlot2, DateTime MeetingTimeSlot3)
        {
            List availableTimeSlots = employeeCalendars.Where(e => e.EmployeeID == EmployeeID && 
                                                                                (e.TimeOfDay.Hour == MeetingTimeSlot1.Hour 
                                                                                || e.TimeOfDay.Hour==MeetingTimeSlot2.Hour 
                                                                                || e.TimeOfDay.Hour==MeetingTimeSlot3.Hour) ).ToList();
            if (availableTimeSlots.Count > 0)
            {
                return availableTimeSlots[0].IsAvailable;
            }   
            else
                return false;
        }
    }

The ParticipantAvailability class contains sample data that shall be used for the purpose of testing the service since we shall not be implementing persistence to a database in this post.

5.3 The Workflow

We shall change our workflow to check for participant’s availability by adding a parameter to our Receive activity to take the availability status of the participant based on their respective calendar. These parameters will be assigned to variables within our workflow as shown below.

Scheduling Workflow
Scheduling Workflow

 

Assigning Parameters To Variables
Assigning Parameters To Variables

The Receive Activity’s Message data will be used to receive the result of the participant’s availability as shown below:

Receive Activity Configuration
Receive Activity Configuration

5.4 Email Service

We shall add new WorkFlow Service Application which shall be responsible for sending emails to the respective participants. This seems like an overkill for such a simple sample, the reason for implementing it this way will become clear when it comes to the hosting options.
For the purpose of this example, we shall just return “Email Sent Successfully” to keep things simple.

IEmailService.cs

[ServiceContract]
    public interface IEmailService
    {
        [OperationContract]
        string SendEmail(string userAddress, string userPassword,string emailTo, string subject, string body);    
    }

EmailService.cs

public class EmailService:IEmailService
    {
        public string SendEmail(string userAddress, string userPassword, string emailTo, string subject, string body)
        {
            //TO DO:setup SMTP credentials
            return "Email sent successfully";
        }
    }

The Send Email Workflow shall be setup as shown below:

Send Email Workflow
Send Email Workflow

 

5.5 Testing The Email Service

To check that everything works, we shall test the service using WCF Test Client as shown below:

WCF Test Client
WCF Test Client

So essentially, what the test client allows us to do, is check that our Email Service actually works before we go out and build our own client that will consume this service. This is exactly what we shall do next:

5.6 The Client

Finally to piece everything up, we shall build our client.

  1. Right click on the solution explorer and select Add > New Project
  2. Select Workflow Console Application under Installed Templates Visual C#
  3. Click OK

 

New Workflow Console Application
New Workflow Console Application

Add Service Reference for the various services:

  1. Right click on the Project
  2. Select Add > Service Reference
  3. Click Discover
  4. Select the Service and type the name in the Namespace
  5. Click OK
  6. Build the project
Add Service Reference
Add Service Reference

Once the project has been built successfully, you should be able to see the service CheckParticipantAvailability in the toolbox as show below:

Toolbox
Toolbox

 

Drag and drop the various activities into the workflow, the end product should be as shown below:

Client Workflow
Client Workflow

Lets walk though it:

We begin by picking the first three employees from our sample data as had been defined in the ParticipantAvailability class using a while loop. Next, we pick the assign the employee loop counter to the EmployeeId variable and check the availability of the said employee by calling the method CheckParticipantAvailability and passing in the respective parameters. There is one problem however, the default Input parameter only allow for an empty constructor which essentially means that we cannot pass in parameters to the constructor. To get around this problem, we shall use a partial class to allow us to define our own constructor as show below:

CheckParticipantAvailability.cs

      public partial class CheckParticipantAvailability {
          public CheckParticipantAvailability(Int32 EmployeeID, DateTime MeetingTimeSlot1,
                                                       DateTime MeetingTimeSlot2,DateTime MeetingTimeSlot3)
          {
              this.employeeID = EmployeeID;
              this.meetingTimeSlot1 = MeetingTimeSlot1;
              this.meetingTimeSlot2 = MeetingTimeSlot2;
              this.meetingTimeSlot3 = MeetingTimeSlot3;
          }
           
          //Empty Constructor
        public CheckParticipantAvailability()
        {
            
        }
    }

The availability of the participant then determines whether the employee receives an email or an SMS depending on their preferred method of communication and the meeting is scheduled accordingly.

6. Summary

It is not hard to create workflows using WCF Workflow Service Application template in Visual Studio since a lot of things are handled and managed automatically so that developers can focus on functionality. This is just the beginning of how manual tasks can work in workflow and how to extend them to call custom WCF Services. You could expand this by adding persistence to the database.

Download the project
You can download the full source code of this example here : MeetingSchedulerWorkflow.zip

Stephen Ebichondo

Stephen is an application developer currently working as a technical lead at Equity Bank’s in-house mobile applications team. Passionate about all matters tech and building user communities, he is the current lead of Nairobi .NET User Group.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button