Creating a Pluggable Workflow Service Share Point 2010

In this section, we’ll build the environmental control procedure as defined in our demonstration scenario. Here’s a recap: To comply with international regulations relating to environmental protection, each product available for order must have achieved compliance with the appropriate standards for the country into which it will be sold. Determining compliance involves performing a series of calculations to determine the level of specific substances within the finished product. Since the calculation is relatively complex, it will be performed by a separate system. Once the calculation has been performed, the results should be sent to an environmental control officer for verification. We can see that our workflow should make use of calculation facilities provided by an external system. Since the calculation process is long running, an asynchronous pattern will be used—that is, a request will be sent to the external system, the system will acknowledge the request, and it will then begin performing the relevant work in a separate asynchronous process. Once the system has completed the prescribed work, it will communicate the results to the original caller.

Creating a Sample WCF Calculation Service
We’ll implement our demo calculation service using Windows Communication Foundation (WCF). WCF itself is a separate topic and in depth coverage is out of scope of this chapter, but I’ll use it for this example since it is one of the primary mechanisms used for communicating with external systems. Since we don’t actually need to perform any calculations, we’ll create a Windows Forms client application that receives incoming requests and writes them to a list. We’ll then be able to select requests from the list and manually submit a response to the workflow. To make this work, we need two WCF services—one in the Windows Forms client to receive the calculation request, and another within SharePoint to receive the response from the calculation service. We’ll create the Windows Forms client first.

Create a Windows Forms Client Hosting a WCF Service

  1. In Visual Studio 2010, choose File | New | Project. From the New Project dialog, select Visual C# | Windows | Windows Forms Application, as shown. Name the project DemoCalculationEngine.
  2. Create a Windows Forms Client Hosting a WCF Service
  3. Our user interface will be very simple. Add a DataGridView control and a Button. Anchor them appropriately so that they resize with the form. Set the Text property of the Button control to Send Result.
  4. With our user interface complete, the next step is to add a WCF service to receive the calculation requests. To add a new item to the project, press ctrl-shift-a (alternatively, choose Project | Add New Item). In the Add New Item dialog, select Visual C# Items | WCF Service. Name the new service class CalculationRequestService.cs.
  5. Since we’re creating a WCF service, Visual Studio will add two new files to the solution. The first file, CalculationRequestService.cs, contains the implementation of the service. The second file, ICalculationRequestService.cs, contains the service contract definition for the service. We’ll start by defining the contract since we can easily use Visual Studio to create a default implementation. In the ICalculationRequestService.cs
  6. file, add the following code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    namespace DemoCalculationEngine
    {
    [ServiceContract]
    public interface ICalculationRequestService
    {
    [OperationContract]
    bool SubmitCalculation(CalculationRequest request);
    }
    [DataContract]
    public class CalculationRequest
    {
    [DataMember(IsRequired = true)]
    public Guid SiteId { get; set; }
    [DataMember(IsRequired = true)]
    public Guid WebId { get; set; }
    [DataMember(IsRequired = true)]
    public Guid InstanceId { get; set; }
    [DataMember(IsRequired = true)]
    public string ProductName { get; set; }
    }
    }
  7. With our service contract and data contract defined, we can move on to focus on the implementation of the service. In the CalculationRequestService.cs file, add the following code:
  8. namespace DemoCalculationEngine
    {
    public class CalculationRequestService : ICalculationRequestService
    {
    public bool SubmitCalculation (CalculationRequest request)
    {
    Program.theForm.SaveRequest(request);
    return true;
    }
    }
    }

    TIPTo create a default implementation of an interface automatically using Visual Studio, right-click the name of the interface and then select Implement Interface | Implement Interface from the context menu. Our service implementation probably warrants some explanation. Since we’re going to write incoming requests to the data grid that we added to our user interface, we need to do this using the same thread that’s running the user interface to avoid cross-threading issues. In effect, our service does nothing more than write the requests to the user interface.

  9. You may notice that the code in our SubmitCalculation method contains a “Form has not yet been defined” error. To fix this, add the following code to Program.cs:
  10. using System;
    using System.ServiceModel;
    using System.Windows.Forms;
    namespace DemoCalculationEngine
    {
    static class Program
    {
    public static Form1 theForm;
    [STAThread]
    static void Main()
    {
    using (ServiceHost host = newServiceHost
    (typeof(CalculationRequestService)))
    {
    host.Open();
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    theForm = new Form1();
    Application.Run(theForm);
    }
    }
    }
    }
  11. As is too often the case, with that error fixed, you’ll notice that we now have a different problem. SaveRequest is not defined on Form1. So add the following code to form1.cs to complete our implementation:
using System.ComponentModel;
using System.Windows.Forms;
namespace DemoCalculationEngine
{
public partial class Form1 : Form
{
private delegate void SaveRequestMethod (CalculationRequest request);
private BindingList<CalculationRequest> _calculationList;
public Form1()
{
InitializeComponent();
_calculationList = new BindingList<CalculationRequest>();
dataGridView1.DataSource = _calculationList;
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridView1.MultiSelect = false;
dataGridView1.AllowUserToAddRows = false;
}
internal void SaveRequest(CalculationRequest request)
{
if (this.InvokeRequired)
{
SaveRequestMethod theDelegate = new SaveRequestMethod (this.SaveRequest);
this.Invoke(theDelegate, new object[] { request });
}
else
{
_calculationList.Add(request);
}
}
}
}

We can make use of the WCF test tools that are provided with Visual Studio to check that everything is working properly. Open up a Visual Studio command prompt and type the following:C:Program Files (x86)Microsoft Visual Studio 10.0VC>WCFTestClient This will start the WCFTestClient.exe application that we can use to submit requests to our calculation engine.
Before we can connect, we need to know the endpoint URI for our service. This can be found in the app.config file for our client application under system.serviceModel | Services | service | host | baseAddress. The URI will be similar to this: Time_Addresses/DemoCalculationEngine/CalculationRequestService/. Now, if we run the client application, we can choose File | Add Service in the WCFTestClient tool to generate a proxy that will allow us to send a test request. If all is well, we’ll see the requests being queued in our client app, as shown next:
Create a Windows Forms Client Hosting a WCF Service

Add a SharePoint-Hosted WCF Service
Now that our client WCF service is up and running, our next logical step is to implement a WCF service that can be hosted within SharePoint to receive calculation results from our client application.

  1. We’ll make use of the packaging and deployment capabilities of Visual Studio to set up our WCF service on SharePoint. In Visual Studio, choose File | New | Project and then from the New Project dialog, choose SharePoint | 2010 | Empty SharePoint Project. Name the new project WorkflowDemonstration as illustrated.
  2. Add a SharePoint-Hosted WCF Service
  3. Set the local site to use for debugging to the demonstration site that we created earlier: We need to deploy as a farm solution sincethe components that we’re about to add are not supported in a sandbox.
  4. At the time of writing, no SharePoint specific template item is available for deploying a WCF service, so we have to set up the solution file manually. The first thing we need to do is to add a new WCF Service item. Press ctrl-shift-a to show the Add New Item dialog. Select Visual C# | WCF Service. Name the new class CalculationResultService.cs.
  5. As before, Visual Studio will add two new files to our solution together with the appropriate references required to support WCF. Again, we’ll start off by defining our service contract. In the ICalculationResultService.cs file, enter the following:
  6. using System; using System.Runtime.Serialization;
    using System.ServiceModel;
    namespace WorkflowDemonstration
    {
    (ServiceContract]
    public interface ICalculationResultService
    {
    [OperationContract]
    bool ProcessCalculationResult(CalculationResult result);
    }
    [DataContract]
    public class CalculationResult
    {
    [DataMember(IsRequired = true)]
    public string Result { get; set; }
    [DataMember(IsRequired = true)]
    public Guid SiteId { get; set; }
    [DataMember(IsRequired = true)]
    public Guid WebId { get; set; }
    [DataMember(IsRequired = true)]
    public Guid InstanceId { get; set; }
    }
    }
  7. Since our service will ultimately send events to workflows hosted on the SharePoint workflow runtime, we have a few things to do before we can fully implement the required functionality. For now, we’ll create a stub method based on our interface. In the CalculationResultService.cs file, enter the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WorkflowDemonstration
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirements-
Mode.Allowed)]
public class CalculationResultService : ICalculationResultService
{
public bool ProcessCalculationResult(CalculationResult result)
{
return true;
}
}
}

Hosting an .svc File in SharePoint To make our service available for our client application, we need to host it somewhere. Since SharePoint runs on Internet Information Server (IIS), we need to create a .svc file with details of our service implementation. Of course, before we create the file, we need somewhere to put it; for the purposes of this demonstration, we’ll use a custom subfolder within the %SPROOT%TEMPLATELayouts folder. We can set up this folder automatically using our Visual Studio project.

  1. Choose Project | Add SharePoint “Layouts” Mapped Folder. You’ll notice that a new Layouts folder is added to the solution:
  2. Hosting an .svc File in SharePoint
  3. We can now go ahead and add our CalculationResultService.svc file. In the LayoutsWorkflowDemonstration folder, add a new XML file named CalculationResultService.svc. Replace the contents of the file with the following:
<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$"%>
<% @ServiceHost Service="WorkflowDemonstration. CalculationResultService"%>

Token Replacement in Visual Studio Visual Studio 2010 allows the use of replaceable tokens when creating SharePoint solution packages. Our code sample makes use of the token $SharePoint.Project.AssemblyFullName$ that will be replaced when the package is built, by the four-part assembly name for the associated assembly. However, at the time of writing, no WCF template is available for SharePoint. Therefore, tokens are not automatically replaced in files with an .svc extension. Thankfully, this is a simple problem to resolve. Navigate to C:Program Files (x86)MSBuildMicrosoft VisualStudio v10.0 SharePoint Tools and then open the Microsoft.VisualStudio.SharePoint.targets file. This is an XML format file that defines various configuration settings for building SharePoint projects. Find the TokenReplacementFileExtensions element and append svc to the list of file extensions as shown:

<TokenReplacementFileExtensions>$ (TokenReplacementFileExtensions);xml;aspx;ascx;
webpart;dwp;svc </TokenReplacementFileExtensions>

Adding WCF Service Configuration to SharePoint As well as an .svc file, IIS also needs to read the configuration of the WCF service from the web.config file. For the purposes of our demonstration, we’ll make the necessary changes manually.

  1. Open the web.config file for our application (at C:inetpubwwwrootwssVirtual Directories 80web.config if the application is the first application running on port 80). In the system.serviceModel element, add the following configuration details:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<bindings>
<basicHttpBinding>
<binding name="WfDemoBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="WfDemoBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="WfDemoBehavior"
name="WorkflowDemonstration.CalculationResultService">
<endpoint address="" binding="basicHttpBinding"
bindingConfiguration="WfDemoBinding"
contract="WorkflowDemonstration.ICalculationResultService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost/_layouts/ WorkflowDemonstration" />
</baseAddresses>
</host>
</service>
</services>

We’re now ready to deploy the service to SharePoint. From the Build menu select Deploy WorkflowDemonstration. Visual Studio will now build the solution, create a WSP package, and then deploy the package to our SharePoint server. As we did for our client application, we can now make use of WCFTestClient to send a test call to our WCF service. layouts/WorkflowDemonstration/CalculationResultService.svc. If all is well, our service will return True when invoked as per our stub implementation.

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 SP Workflow External DataExchange Service 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 SPWorkflow External Data Exchange Service 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 Work flow Demonstration solution named IExternal Calculation Service.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;
}
}

NOTECreating 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 External Data Event Args class and must be serializable. The External Data Event Args 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 SPWorkflow External Data Exchange Service 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 Calculation Workflow Service.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 isthe 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.
Calling a WCF Service The next step is to flesh out the implementation of the SubmitCalculation method. Sincethis is where we’ll make a call out to our external calculation service, we need to add aservice reference to generate the appropriate proxy.

  1. So that our service endpoint is available, start up the DemoCalculationEngine application that we created earlier.
  2. 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.

Creating a Pluggable Workflow Service
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 Demo Calculation Engine 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 Process Calculation Result message and another for passing that message onto the workflow, we can perform both tasks in a single method on the CalculationWorkflowService.

  1. Add the following code to CalculationWorkflowService.cs:
  2. 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;
    }
    }
    }
  3. 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 ICalculation Result to the list of implemented interfaces on CalculationWebService, like so: class Calculation Workflow Service:SP Workflow External Data Exchange Service,
  4. 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 Workflow Demonstration. Calculation Result Service toWork flow Demonstration.Calculation Workflow Service as shown:
  5. <services><service behaviorConfiguration="WfDemoBehavior"
    name="WorkflowDemonstration.CalculationWorkflowService">
  6. 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 ExternalDataEventArgsderived 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.

Calling a SharePoint-Hosted WCF Service

To complete our calculation engine implementation, we need to add some code to our DemoCalculationEngine project. We need a method that can make a call into our SharePoint WCF service to notify the workflow that calculation is complete. With the SharePoint project deployed, we first need to add a service reference to the SharePoint WCF service.

  1. In the DemoCalculationEngine project, choose Project | Add Service Reference.
  2. Since the service is hosted within SharePoint, requests must be authenticated; as a result, we need to enter credentials for a user account with permissions to connect to the SharePoint site.
  3. Once the service metadata has been retrieved, set the Namespace to CalculationResultService and the click OK to complete the process.

NOTEWhen adding a service reference for a SharePoint, you’ll sometimes see multiple prompts to enter credentials. Usually, after entering valid credentials once, clicking Cancel on subsequent prompts will allow the process to continue.

With our service reference in place, we can move on to add the following code to handle the button click event in Form1.cs:

private void button1_Click(object sender, System.EventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
CalculationResultService.CalculationResultServiceClient client = new CalculationResultService. CalculationResultServiceClient();
client.ClientCredentials.Windows.AllowedImpersonationLevel =
System.Security.Principal.TokenImpersonationLevel.Impersonation;
CalculationResultService.CalculationResult result = new
CalculationResultService.CalculationResult();
CalculationRequest selected = row.DataBoundItem as CalculationRequest;
result.Result = selected.ProductName + " Complete";
result.InstanceId = selected.InstanceId;
result.SiteId = selected.SiteId;
result.WebId = selected.WebId;
if (client.ProcessCalculationResult(result))
{
row.Selected = false;
_calculationList.Remove(selected);
}
}
}

We can now manually trigger calculation results by selecting an item from the data grid and then clicking the Send Result button.

TIPWhen hosting WCF services in SharePoint, it’s important that the client proxy allows impersonation; otherwise, some weird and wonderful COM errors may be thrown by SharePoint. To allow impersonation, set the Allowed Impersonation Level to Impersonation, as shown in the preceding code sample.


All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

Share Point 2010 Topics