2. Creating a Pluggable Workflow Service
Having set up the communications mechanism between
the calculation engine and SharePoint, our next step is to create a
pluggable workflow service that can be hooked up to our SharePoint
service to broker requests between the WCF service and SharePoint’s
workflow engine. Before we get into the code, I’ll show you how
pluggable workflow services work.
As mentioned, pluggable workflow services can be created by inheriting from SPWorkflowExternalDataExchangeService.
External data exchange services, also known as local services, are a
key component of the WF framework. Without local services, a workflow
runtime has no means of communicating with the external environment,
and in fact SharePoint defines two local services that allow the
workflow runtime to communicate with the SharePoint platform itself: SPWinOEWSSService and SPWinOETaskService.
For the most part, the SharePoint workflow activities that are
available out of the box make use of these services for communication.
Generally speaking, WF can be configured using a
configuration file. The configuration can specify which services should
be available to the runtime and how certain functions are performed.
Since allowing changes to the configuration at this level could cause
major support issues, Microsoft chose to disallow workflow
configuration in SharePoint via the normal channels. In previous
versions of SharePoint, this meant that no additional local services
could be added. However, with SharePoint 2010, an additional
configuration handler has been implemented that allows objects of type SPWorkflowExternalDataExchangeService to be added to the workflow runtime.
External data exchange services are created in a
similar fashion to WCF services. An interface is defined that
determines the methods and events that should be available to the
workflow runtime. Once the interface is completed, a local service
class based on SPWorkflowExternalDataExchangeService
and implementing the interface is created. Finally, the local service
class is added to the configuration for the workflow runtime.
Using the ExternalDataExchange Attribute
Now that you understand how pluggable services work, let’s move on to our implementation.
We first add an interface
for our service. Add a new interface file to the WorkflowDemonstration
solution named IExternalCalculationService.cs. Add the following code:
using System;
using System.Workflow.Activities;
namespace WorkflowDemonstration
{
[ExternalDataExchange]
public interface IExternalCalculationService
{
event EventHandler<CalculationResultArgs> CalculationComplete;
void SubmitCalculation(string product);
}
[Serializable]
public class CalculationResultArgs : ExternalDataEventArgs
{
public CalculationResultArgs(Guid id) : base(id) { }
public string Result;
}
}
Note
Creating workflow services requires references to System.Workflow.Activities and System.Workflow.Runtime.
Notice a few things about this code sample. Firstly, the ExternalDataExchange
attribute is used to let the workflow runtime know that the interface
should be accessible to workflow activities. We’ll build up a workflow
later to see this in action. Secondly, any events that are raised must
be derived from the ExternalDataEventArgs class and must be serializable. The ExternalDataEventArgs
class defines the base parameters that are required to route the event
to the correct workflow instance. Because the workflow will most likely
be running in a different application domain, events must be
serializable in order to be passed to the runtime.
Deriving from SPWorkflowExternalDataExchangeService
With the interface in place, we can move on to creating an implementation of the service.
Add a new class named CalculationWorkflowService and then add the following code to the CalculationWorkflowService.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Workflow.Activities;
using System.ServiceModel.Activation;
using Microsoft.SharePoint.Workflow;
using System.Workflow.Runtime;
namespace WorkflowDemonstration
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirements-
Mode.Allowed)]
class CalculationWorkflowService: SPWorkflowExternalDataExchangeService, IExternal-
CalculationService
{
public event EventHandler<CalculationResultArgs> CalculationComplete;
public void SubmitCalculation(string product)
{
//Call WCF Service
}
public override void CallEventHandler(Type eventType,
string eventName,
object[] eventData,
SPWorkflow workflow,
string identity,
IPendingWork workHandler,
object workItem)
{
//raise event
}
public override void CreateSubscription(MessageEventSubscription subscription)
{
throw new NotImplementedException();
}
public override void DeleteSubscription(Guid subscriptionId)
{
throw new NotImplementedException();
}
}
}
The main thing worth mentioning with regard to this code sample is the override of CallEventHandler. CallEventHandler is defined on the SPWorkflowExternalDataExchangeService
base class and is used to relay events back to the workflow runtime
with sufficient information to recover the relevant SharePoint context.
CreateSubscription and DeleteSubscription are marked as MustInherit in the base class but are not required by our service and therefore have default implementations.