Introduction

This is the first in a series of blog posts dedicated to Windows Workflow. For those who don’t know, Windows Workflow Foundation (WF) is the new engine for building custom workflows (you can find more information about WF on the official web site). It was released as part of the .NET Framework 3.0 along with Windows Presentation Foundation (WPF) and Windows Communication Foundation (WCF).

Local Service Communication

Workflows are not isolated entities and most of the times need to communicate with the outside world in order to perform some task. There are several ways of communicating with external entities/services, being one of them External Data Exchange and Local Service Communication. Local Service Communication basically allows workflows to communicate with an external service that resides within the workflow host. There are two main activities inside WF that allow communication between a workflow and an external service using Local Service Communication:

  • CallExternalMethodActivity – Allows a workflow to call a method from an external service. The workflow and the host establish a contract (using an interface) that defines the way they communicate with each other. Basically the workflow calls a method of a service residing within the host that implements the contract interface. For more details about this activity, click here.
  • HandleExternalEventActivity – Besides allowing the workflow to call an external method, it is also possible for the host to send an event to the workflow in order for it to perform some task. This is achieved using the HandleExternalEventActivity that will put the workflow on an idle state until an external event is raised. Just as in CallExternalMethodActivity, the communication between the workflow and the external is based on a contract.
Summary

To build a workflow that uses HandleExternalEventActivity to communicate with a service using Local Service Communication, you’ll need to:

  • Define a contract (interface);
  • Implement a service based on the defined contract;
  • Develop a workflow that uses HandleExternalEventActivity;
  • Develop a host for the workflow;
  • Add the service to the workflow runtime in the host;
  • Start the workflow and handle the workflow WorkflowIdled event in the host;
  • Raise the event.
Example

As already explained, the HandleExternalEventActivity allows communication between a workflow and an external service using Local Service Communication based on event notifications. Next, I will present a simple demo that will show a very basic usage of this activity. The demo is based on a simple Approval State Machine Workflow Console Application. The console application will ask the user if he/she wants to approve and based on the answer it will pass the corresponding answer to the workflow through the raise of an event. This is how the workflow looks like:

This workflow will basically handle the external event inside the StartState activity and set the approved or rejected state according to the information supplied by the host in the event. The interface of the service is defined like this:

[ExternalDataExchange]
public interface IApprovalEventService
{
    void RaiseSetStateEvent(Guid instanceId, bool approve);

    event EventHandler<ApprovalExternalDataEventArgs> ExternalDataEvent;
    
}

The interface uses the ExternalDataExchange attribute which allows the workflow to use it to communicate with the host using Local Service Communication. The interface defines the event, whose arguments are of type ApprovalExternalDataEventArgs, a type derived from ExternalEventArgs. It also defines a RaiseSetStateEvent method that will be used by the host to raise the event. This method defines an additional Boolean parameter named approve in order to pass the approval information to the workflow. This is how the ApprovalExternalDataEventArgs class looks like:

[Serializable]
public class ApprovalExternalDataEventArgs : ExternalDataEventArgs
{
    private bool _result;

    public bool Result {
        set {
            _result = value; 
        }
        get {
            return _result;
        }
    }

    public ApprovalExternalDataEventArgs(Guid instanceId, bool approve)
        : base(instanceId) {
        Result = approve;
    }
}

In the constructor, a Boolean approve parameter is used. This parameter is populated by the host when raising the event and is used to set the Result property that will be used later by the workflow. The other parameter used in the constructor is instanceId, which is used to correlate the event with the right workflow instance.

The HandleExternalEventActivity is defined inside the StartState activity and is named handleSetState. Here are the details of the StartState activity:

As shown in the image above, the external event is handled by the HandleExternalEventActivity handleSetState. Then, based on the value that was supplied by the host, approval or rejected activities will be executed (in this case a simple message will be written to the application console). The properties for the HandleExternalEventActivity handleSetState look like this:

There are three properties worth mentioning here:

  • e – It will hold the event arguments for the workflow instance. In this case it is set to ExternalDataEvent, a public property in the workflow of type ApprovalExternalDataEventArgs.
  • EventName – name of the event that will be handled. Also set to ExternalDataEvent.
  • InterfaceType – name of the interface. Set to IApprovalEventService.

The IfElseActivity defined after the HandleExternalEventActivity will evaluate a code condition to determine whether to execute the Approve or Reject activities.

The code condition is based on a CheckState method that will verify the Result property of the workflow ExternalDataEvent property.

// Code condition to evaluate whether to take the 1st branch, YesIfElseBranch
// Since it always returns true, the 1st branch is always taken.
private void CheckState(object sender, ConditionalEventArgs e) {
    e.Result = ExternalDataEvent.Result;
}

The two remaining pieces of this demo are the service and the host console application. Here is the definition of the service:

class ApprovalEventService : IApprovalEventService
{
    #region IApprovalEventService Members

    public void RaiseSetStateEvent(Guid instanceId, bool approve)
    {
        // create event
        EventHandler<ApprovalExternalDataEventArgs> setStateEvent 
        = this.ExternalDataEvent;

        // launch event
        if (setStateEvent != null)
            setStateEvent(null, new ApprovalExternalDataEventArgs
                (instanceId, approve));
    }

    public event EventHandler<ApprovalExternalDataEventArgs> 
        ExternalDataEvent;
   

    #endregion
}

The console application code looks like this:

private static ApprovalEventService service;
private static Guid instanceId; 

/// <summary>
/// We've decorated the IEventService interface with the 
/// ExternalDataExchangeAttribute.
/// The event args class (ApprovalExternalDataEventArgs) 
/// derives from ExternalDataEventArgs.
/// The event args class (ApprovalExternalDataEventArgs) is serializable.
/// ApprovalEventService implements IEventService.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
    using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
    {
        //add ApprovalEventService service to the workflow runtime
        AutoResetEvent waitHandle = new AutoResetEvent(false);
        ExternalDataExchangeService dataExchange = 
        new ExternalDataExchangeService();
        workflowRuntime.AddService(dataExchange);
        service = new ApprovalEventService();
        dataExchange.AddService(service);
        
        workflowRuntime.WorkflowCompleted += 
        delegate(object sender, WorkflowCompletedEventArgs e) 
        {
            //display workflow completion message
            Console.WriteLine("Workflow completed.");
            waitHandle.Set();
        };
        workflowRuntime.WorkflowTerminated += 
        delegate(object sender, WorkflowTerminatedEventArgs e)
        {
            //display error message if an exception is raised
            Console.WriteLine(e.Exception.Message);
            waitHandle.Set();
        };
        workflowRuntime.WorkflowIdled += 
        delegate(object sender, WorkflowEventArgs e)
        {
            //read the input from keyboard (yes/no) and raise 
            //the event passing the answer as a boolean parameter
            Console.WriteLine("Workflow idled.");
            Console.Write("Do you want to approve? (Y/N) ");
            string key = Console.ReadLine();
            bool approved = key.ToLower() == "y" ? true : false;
            Console.WriteLine("Sending event to workflow.");
            service.RaiseSetStateEvent(instanceId, approved);
        };

        //start workflow
        WorkflowInstance instance = 
        workflowRuntime.CreateWorkflow(
        typeof(StateMachineCommunicationDemo.SimpleStateMachine));
        instanceId = instance.InstanceId;
        Console.WriteLine("Starting workflow.");
        instance.Start();

        //put workflow in an idle state
        waitHandle.WaitOne();

        //stop runtime
        workflowRuntime.StopRuntime();

        Console.WriteLine("Press any key.");
        Console.Read();
    }
}

In the code above, the following actions are performed:

  • The service is added to the workflow runtime. In order for the service to be correctly added to runtime, two AddService methods must be executed and in the right order. First, an instance of ExternalDataExchangeService is added to the workflow runtime. Then, an instance of our service (ApprovalEventService) is added to the ExternalDataExchangeService instance.
  • The workflow is started
  • The workflow is placed in an idle state
  • Several delegate functions are defined to handle workflow events. The most important one is the event handler for the WorkflowIdled event. The event handler will be trigger once the workflow is placed in an idle state. Then, the user will be asked the approval question and the event is raised which will cause the HandleExternalEventActivity in the workflow to be executed.

Here is the application’s output in case of approval:

And in case of rejection:

Another related activity is the ListenActivity. For more information, please click here.

Related Articles

To learn why your business should migrate to SharePoint Online and Office 365, click here and here.

If you want to convert your tenant’s root classic site into a modern SharePoint site, click here.

If you are a SharePoint administrator or a SharePoint developer who wants to learn more about how to install a SharePoint farm in an automated way using PowerShell, I invite you to click here and here. The articles use AutoSPInstaller with a SharePoint 2016 farm but AutoSPInstaller support for SharePoint 2019 was already announced!

If you want to learn how to upgrade a SharePoint 2013 farm to SharePoint 2019, click here and here.

If you want to learn all the steps and precautions necessary to successfully keep your SharePoint farm updated and be ready to start your move to the cloud, click here.

If you learn how to greatly speed up your SharePoint farm update process to ensure your SharePoint farm keeps updated and you stay one step closer to start your move to the cloud, click here.

If you want to learn how to upgrade a SharePoint 2010 farm to SharePoint 2016, click here and here.

If you are new to SharePoint and Office 365 and want to learn all about it, take a look at these learning resources.

If you are work in a large organization who is using Office 365 or thinking to move to Office 365 and is considering between a single or multiple Office 365 tenants, I invite you to read this article.

If you or your customers are not ready to move entirely to the Cloud and Office 365, a hybrid scenario could be an interesting scenario and SharePoint 2019 RTM was recently announced with improved hybrid support! To learn all about SharePoint 2019 and all its features, click here.

If you want to know all about the latest SharePoint and Office 365 announcements from SharePoint Conference 2019, click here and here.

Happy SharePointing!

LEAVE A REPLY

Please enter your comment!
Please enter your name here