Windows Workflow Foundation: Calendar Scheduling Workflows

By Zoiner Tejada, Hershey Technologies

Articles in this series

Published: February, 2009

In this article we explore an alternative use of client-side workflows, whereby the workflows themselves are computational in nature, and it is the ability to model the computation logic that creates the preference over procedural programming. 

Computational workflows can be compared to functions in procedural code: they take as input some parameters, process them and return a set of values representing the result. They are different from their human workflow cousins, in that they usually execute in a single burst and return a result without persisting. In addition, they differ from page flow scenarios, because they are not strictly speaking driving the presentation layer, but are instead an implementation of the business logic. A concrete example of a computational workflow can be seen in the requirements of calendar scheduling, and this is the business scenario explored.

Scenario Introduction

Busy schedules are a fact of doing business, and the growing availability and sophistication of calendaring applications such as that offered via Outlook and Exchange ultimately serve the purpose of ensuring that the right people can get together, at the right place, at the right time. Sometimes, however, it can be quite difficult to coordinate that initial appointment that works for most everyone involved.  It turns out that the logic for assisting users to schedule an appointment is well modeled by a computational workflow.

In this particular case, we focus on the problem of selecting the initial group of attendees and time frame. Given that information, we can execute a workflow that queries a calendar server such as Exchange for each attendee’s Free/Busy information. If we are lucky, then all of the attendees are available and the appointment request gets sent to the attendees for their confirmation. Otherwise, there is value in the system helping us choose the next best time that works for all involved, or even recommending who, by making their attendance optional, might increase the chance of fully confirmed meeting on the first proposal. Figure 1 illustrates this flow from a high level.

Figure 1 - High level Scheduling Assistant Workflow

We will explore the implementation of this workflow using both Sequential and State Machine incarnations, and in the course explore some of the relevant exception handling approaches used to make the computational workflow more resilient. 

Inputs & Outputs

Because we are modeling this as a computational workflow, it helps to look at a sampling of the inputs and outputs of the workflow. The following table briefly summarizes the users used in this scenario, and how the calendaring service represents their Free/Busy information:

Table 1 - Users and their "availability" in the simulation

User Description

Chris Smith

This is a regular user who is always available.

George Brown

This is a regular user who is always available.

Never There

This is a user who is only ever available one hour after the requested meeting start time.

NotAllowed Exception

This is a user for whom the initiator of the appointment request does not have permissions to query Free/Busy information.

ProcessFreeBusy CommunicationException

This is a special user to simulate a failure communicating with the calendar server during the Process Free Busy step.

SuggestSchedules CommunicationException

This is a special user to simulate a failure communicating with the calendar server during the Suggest Schedules step.

Figure 2 shows all the inputs (Subject, Location, Time Frame and Attendees). The left list below Attendees enumerates the attendees who can be scheduled, and the list on the right is where selected attendees are moved from the left list and configured as required or optional.

Figure 2 - Scheduling an appointment

These are all packaged into a single type AppointmentRequest that is used to initialize the workflow:

[Serializable]
public class AppointmentRequest
{
    public string Subject { get; set; }
    public string Location {get;set;}
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public ObservableCollection<Attendee> Attendees { get; set; }
}
[Serializable]
public class Attendee
{
    public string Name { get; set; }
    public string Email { get; set; }
    public bool Required { get; set; }
}

Parameters can be passed into a new workflow instance via the WorkflowRuntime.CreateWorkflow overload which takes the type of the workflow to create, and a Dictionary<string, object> where each string key must match the name of a public property on the workflow, the value object can be any serializable type. This workflow exposes a public property of type AppointmentRequest, with the name AppointmentRequest.

There is no concrete difference between input and output workflows. Our Scheduling Assistant workflow has a public property of type FreeBusyResults that is interrogated when the workflow completes (by handling the WorkflowCompleted event and reading the value from the OutputParameters dictionary). This output is used to suggest schedule alternatives when appropriate. For example, running the workflow with the input from Figure 2, produces the suggestions shown in Figure 3. In this case the workflow returns six results. The first three suggest later start times can accommodate all users. The last three suggest times made available by making the user called “Never There” an optional attendee.

Figure 3 - Resulting alternatives to Figure 2

Note that this workflow is a simulation of the approach one would take to compute such suggestions and does not itself employ any sophisticated heuristics or neural networks to provide the result.  Instead we focus on how such logic might be embedded and executed by a computation workflow.

With the inputs and outputs in mind, let’s turn to the implementation of the workflow, starting with the sequential version.

Scheduling Assistant Workflow – Sequential Version

The workflow itself is relatively simple, on account of the use of two complex custom activities, and is almost completely described by Figure 4.

Figure 4 - The Scheduling Assistant Workflow in Sequential form.

When the computational workflow is launched the with appointment request, it first executes the Replicator called ProcessAllSchedules. This replicator runs one copy of the LookupFreeBusy custom activity for each attendee requested, which in turn simulates communication with the calendar server to get a simple Boolean answer for each user: free or not? If all attendees marked as required are in fact free, then the workflow goes ahead and sends out the appointment requests as any calendaring application might (simulated via the no-op SendAppointments activity). In this case, the workflow does not return any alternatives. However, if at least on required attendee is unavailable, then the workflow executes the SuggestAlternatives custom activity which executes logic to determine viable alternatives from the input provided. The activity itself has some pre-programmed decisions which guide it in building this list of alternatives. In the real-world, this is where one might stick the sophisticated logic using a form of solver, neural networks or even make a service call to perform the computation.

An interesting facet of this workflow is the error handling. In this case, there are two exceptions the workflow is concerned with and can react to: being unable to communicate with the server (throws a CalendarServerCommunicationException) and not be authorized to lookup a person’s Free/Busy information (which throws a CalendarAccessNotAllowedException). Recall by default a workflow with an unhandled exception will terminate, so where possible you want to use fault handlers, which behave similarly to try/catch blocks, to make your workflows resilient.

The CalendarAccessNotAllowedException is only thrown by the LookupFreeBusy activity. It is not thrown by the SuggestAlternatives custom activity as one might suspect, because that activity handles it internally and instead suggest that the attendee be marked as optional. Therefore, we handle this exception in the closest parent of an activity offering a FaultHandlersActivity, which means the ProcessAllSchedules sequence as shown:

Figure 5 - Handling CalendarAccessNotAllowedException.

To define a fault handler, we must first add a FaultHandlerActivity to the FaultHandlersActivity. As shown in figure 5, the FaultHandlersActivity is created as HandleLookupFailures and, to its strip, we have added the single FaultHandlerActivity HandleNotAllowed (highlighted in blue). The fault handler is configured to execute the sequence described by HandleNotAllowed, which runs a custom activity that simply checks if the attendee was required or not, and if so configures the workflow state to indicate that. Effectively, not all required attendees could be scheduled so the workflow recommends making the attendee optional in subsequent steps. This handler keeps the exception from bubbling up any higher and allows other instances of LookupFreeBusy and subsequent activities to execute normally.

A CalendarServerCommunicationException can be thrown by either of the two activities which query the calendar server. In this case, we simply decided to complete the workflow (e.g., not terminate) and show the communication error message to the user rather than encode retry logic in the error handler. In a sequential workflow, the best place to put a common, root level error handler such is this is at the root sequence and this is what is shown in Figure 5. The SetFaultResult activity within the FaultHandlerActivity HandleCommunicationFault simply sets the Fault property on the FreeBusyResult output property with the exception.

Figure 6 - Handling a CalendarServerCommunicationException.

Next, we explore how this workflow might be expressed as a state machine.

Scheduling Assistant Workflow – State Machine Version

For computational workflows, sequential workflows are usually more practical to model the single burst of execution. State Machines are geared more towards handling the specific events that cause the transition between states-- in a computational workflow where there are few to no intermediate events the state machine may be as simple as a single state or a series of connected states linked by State Initialization activities that effectively describe a sequence using state machine semantics. The latter approach was followed and is shown in Figure 6.

Figure 7 - The Scheduling Assistant Workflow in State Machine form.

This biggest difference in implementation between the sequential and state machine version has to do with the handling of workflow-level exceptions, such as the CalendarServerCommunicationException. Unlike sequential workflows, the root State Machine Workflow has no place to express an exception handler and you can only add exception handlers to sequences of StateInitialization, EventDriven and StateFinalization. To handle the exception required us to add fault handlers within both InitProcessAllSchedules and InitSuggestAlternatives, as shown in Figure 8 and Figure 9 respectively. Note that both perform exactly the same function, but must be duplicated because the state machine affords no place more global in which to define a common exception handler. 

Figure 8 - Handling a CalendarServerCommunicationException within InitProcessAllSchedules

Figure 9 - Handling a CalendarServerCommunicationException within InitSuggestAlternatives