Windows Workflow 4: Human based workflows with a Meeting Scheduler Sample
Table Of Contents
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.
5. Implementation
5.1 Meeting Availability Service
We start by implementing the MeetingAvailabilityService which basically will be responsible for checking the employee’s availability.
- Launch Visual Studio
- Select File New Project
- Select WorkFlow under Installed Templates Visual C#
- Select WCF Workflow Service Application
- Click OK
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).
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.
The Receive Activity’s Message data will be used to receive the result of the participant’s availability as shown below:
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:
5.5 Testing The Email Service
To check that everything works, we shall test the service using WCF Test Client as shown below:
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.
- Right click on the solution explorer and select Add > New Project
- Select Workflow Console Application under Installed Templates Visual C#
- Click OK
Add Service Reference for the various services:
- Right click on the Project
- Select Add > Service Reference
- Click Discover
- Select the Service and type the name in the Namespace
- Click OK
- Build the project
Once the project has been built successfully, you should be able to see the service CheckParticipantAvailability in the toolbox as show below:
Drag and drop the various activities into the workflow, the end product should be as shown below:
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.
You can download the full source code of this example here : MeetingSchedulerWorkflow.zip