Calling a WCF Service
The next step is to flesh out the implementation of the SubmitCalculation
method. Since this is where we’ll make a call out to our external
calculation service, we need to add a service reference to generate the
appropriate proxy.
So that our service endpoint is available, start up the DemoCalculationEngine application that we created earlier.
In
the WorkflowDemonstration solution, select Project | Add Service
Reference. Add the address of the client WCF service and then click Go
to retrieve the service metadata. Set the Namespace to CalculationEngine as illustrated.
When a service reference is added to a project,
Visual Studio automatically stores the binding and endpoint
configuration in either app.config or web.config, depending on the type
of project. In our case, the configuration has been added to app.config
even though this file is not utilized by the SharePoint deployment
mechanism.
Since SharePoint runs on IIS, any configuration
information has to be included in the appropriate web.config file.
However, when it comes to workflow, storing information in web.config
doesn’t work as expected. Depending on the state of the workflow, it
will be running either within IIS or within a separate server process.
The problem here is that configuration information that will be
available when running under IIS will not be available when running
under the server process.
To avoid problems locating configuration data, it’s
generally good practice to capture such information as part of the
workflow association process. For the purposes of our demonstration,
we’ll hard code the configuration information for now.
In the SubmitCalculation method, add the following code:
public void SubmitCalculation(string product)
{
//Call WCF Service
CalculationEngine.CalculationRequest request = new
CalculationEngine.CalculationRequest();
WSHttpBinding binding = new WSHttpBinding();
EndpointAddress address = new EndpointAddress("ClientServiceURI");
//So that we can pick up the correct workflow
//we need WorkflowInstanceId & a reference to web
CalculationEngine.CalculationRequestServiceClient client = new
CalculationEngine.CalculationRequestServiceClient(binding,address);
request.ProductName = product;
request.InstanceId = this.CurrentWorkflow.InstanceId;
request.SiteId = this.CurrentWorkflow.SiteId;
request.WebId = this.CurrentWorkflow.WebId;
client.SubmitCalculation(request);
}
One important thing to note about this code is the EndpointAddress. This should be the URI for the DemoCalculationEngine WCF service to which we added a reference.
Receiving WCF Messages
The next piece of functionality that we need to consider is raising the CalculationComplete
event. This event will let our workflow know that the external
calculation process has completed as well as provide the result of the
calculation.
You’ll remember that when we added the SharePoint WCF service, we added a basic stub implementation for the ProcessCalculationResult
method. We can now go back and revisit this since a call to this method
ultimately signals that the calculation process has completed.
Rather than have two separate objects, one for handling the ProcessCalculationResult
message and another for passing that message onto the workflow, we can
perform both tasks in a single method on the CalculationWorkflowService.
Add the following code to CalculationWorkflowService.cs:
public bool ProcessCalculationResult(CalculationResult result)
{
using (SPSite site = new SPSite(result.SiteId))
{
using (SPWeb web = site.OpenWeb(result.WebId))
{
RaiseEvent(web,
result.InstanceId,
typeof(IExternalCalculationService),
"CalculationComplete",
new object[] { result.Result });
return true;
}
}
}
So that we can direct WCF calls for ProcessCalculationResult
to this method, we need to make a few additional changes. First, delete
the CalculationResultService.cs file containing our stub method. Then
add ICalculationResult to the list of implemented interfaces on CalculationWebService, like so:
class CalculationWorkflowService:SPWorkflowExternalDataExchangeService,
IExternalCalculationService, ICalculationResultService
Since we’ll no longer be using the CalculationResultService class to handle method calls for our WCF service, we need to modify the service configuration in web. config. Within the system.serviceModel element, change the service element named WorkflowDemonstration.CalculationResultService to WorkflowDemonstration.CalculationWorkflowService as shown:
<services>
<service behaviorConfiguration="WfDemoBehavior"
name="WorkflowDemonstration.CalculationWorkflowService">
The final change we need to make is to the ServiceHost entry in the CalculationResultService.svc file. Change this to WorkflowDemonstration .CalculationWorkflowService as shown:
<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$"%>
<% @ServiceHost Service="WorkflowDemonstration.CalculationWorkflowService" %>
Raising Events in a Workflow Service
We can see that the ProcessCalculationResult makes
use of the parameters received to create a reference to an SPWeb
object. It then passes this reference together with a workflow instance
identifier to the RaiseEvent method. As its name suggests, RaiseEvent
is responsible for raising an event within the appropriate workflow
instance. Before the event is queued for the appropriate workflow
instance, the CallEventHandler method is called to populate an
appropriate ExternalDataEventArgs-derived object.
Add the following code to the CallEventHandler override to populate our CalculationResultArgs structure before the event is passed to the workflow:
public override void CallEventHandler(Type eventType,
string eventName,
object[] eventData,
SPWorkflow workflow,
string identity,
IPendingWork workHandler,
object workItem)
{
CalculationResultArgs args = new CalculationResultArgs(workflow
.InstanceId);
args.Result = eventData[0].ToString();
args.WorkHandler = workHandler;
args.WorkItem = workItem;
args.Identity = identity;
this.CalculationComplete(null, args);
}
Configuring Pluggable Workflow Services
You’ll
remember that SharePoint 2010 introduces a new configuration handler
for pluggable workflow services. The final step that we need to take to
enable our service is to add a configuration entry in web.config.
In web.config, navigate to the configuration | SharePoint | WorkflowServices section and then insert the following element:
<WorkflowService Assembly="WorkflowDemonstration, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=YourPublicKey"
Class="WorkflowDemonstration.CalculationWorkflowService">
</WorkflowService>
Unfortunately, on this occasion, we don’t have the
luxury of Visual Studio token replacement so we have to find the
PublicKeyToken by examining the properties of the WorkflowDemonstration
assembly within the Global Assembly Cache (GAC). With this done, we can
deploy the project to SharePoint.
We’ve now completed the implementation of
our pluggable workflow service as well as our SharePoint-hosted WCF
service. Although we could test the service using WCFTestClient, we’ll
receive errors since no genuine workflow instances are awaiting a
calculation response.