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.
1. Creating a Sample WCF Calculation Service
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
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.
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.
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.
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 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; }
}
}
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:
namespace DemoCalculationEngine
{
public class CalculationRequestService : ICalculationRequestService
{
public bool SubmitCalculation(CalculationRequest request)
{
Program.theForm.SaveRequest(request);
return true;
}
}
}
Tip
To 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.
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:
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);
}
}
}
}
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.0\VC>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:
http://localhost:8732/Design_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: