Calling a .NET4 WCF/Workflow Service hosted in AppFabric from a .NET3.5 Workflow hosted in SharePoint
The following blog put together by our team goes into some details of demonstrating interoperability between SharePoint Server 2010 (Beta1) and Windows Server AppFabric (Beta1). As you may be aware SharePoint 2010 uses .NET 3.5 based Workflow technologies while the Windows Server AppFabric hosts, manage and administers .NET4 Workflows. This perceived incompatibility has caused considerable concern to customer and partner community and this blog demonstrates that all is not lost. The design pattern applied is quite straightforward and is implemented as communication between .NET4 Services and a SharePoint Service using a code activity akin to calling a.NET4 Workflow Service from a WF .NET3.5 Workflow Service.
The scenario demonstrates of interoperability of a 'human' business process running on Microsoft's SharePoint Server 2010 with a dependent 'system' application running on Windows Server AppFabric. While there are many business processes that would leverage this architecture, we modeled this blog post around a purchasing scenario. For this scenario, a purchase request is created by the buyer on the SharePoint Sever which is passed on to the 'composite' Purchasing Application running on Windows Server App Fabric. The Purchasing Application communicates with the vendors for price/availability; and communicates the 'selection' to the buyer via SharePoint user interfaces. In the implementation, the buyer creates the requisition and uploads this document on the SharePoint 2010. A SharePoint Workflow (based off .NET 3.5) makes a service call to an Application (.NET 4 WCF Workflow Service) hosted in Windows Server AppFabric which processes the request and returns an appropriate response.
We hope to follow-up this blog with a couple of other blog posts that elaborate on a few other important interoperability scenarios between these two very important technologies from Microsoft.
The SharePoint Workflow is launched whenever a user adds a new document to the Shared Documents library through the SharePoint Web UI (see Figure 1 below). An activity within the SharePoint Workflow calls out to the SimpleWorkflowService (a .NET 4 Workflow Service hosted in Windows Server AppFabric) and invokes the GetData operation. The operation takes an integer as input and simply echoes back the integer in string form. Upon returning from the call, the SharePoint Workflow completes.
The following server techologies are used in this demonstration.
This build out comprises the following steps:
Details are presented below.
In this step, we will add a WCF Workflow Service project to the solution and configure it to be hosted using Windows Server AppFabric.
In this task we will use the default workflow service that Visual Studio generates for new WCF Workflow Service applications. This workflow service simply takes in an integer and returns a string (which is the string value of the integer). The service exposes one operation, GetData that wraps the integer and string values in Request / Response payloads respectively. It has a signature as follows:
public GetDataResponse GetData(GetDataRequest request)
By default, the newly created project will be hosted in the Visual Studio Development Server. In order to host the service in AppFabric, we simply need create a virtual directory in IIS that map to the project directory. This is most easily done within Visual Studio.
In this step, we will modify the SharePoint workflow and project so that it calls the SimpleWorkflowService.
In this task we will configure the workflow to setup the parameter to pass during the call to GetData, log what will be sent, perform the call and then log the return value.
public Guid workflowId = default(System.Guid); public SPWorkflowActivationProperties workflowProperties = new SPWorkflowActivationProperties(); public ExternalServices.GetDataRequest sendActivity1_request1 = new CallExternalService.ExternalServices.GetDataRequest(); public String logToHistoryListActivity1_HistoryDescription = default(System.String); public String logToHistoryListActivity2_HistoryDescription = default(System.String); public Workflow1() { InitializeComponent(); } private void onWorkflowActivated1_Invoked(object sender, ExternalDataEventArgs e) { //Simulate taking data (e.g., from ListItem) in to pass as parameter to the external WCF Workflow Service sendActivity1_request1.@int = 42; } private void PrepLogData(object sender, EventArgs e) { //Build the string to log logToHistoryListActivity1_HistoryDescription = String.Format("Workflow was started. Preparing to send '{0}'.", sendActivity1_request1.@int); } private void DataReceived(object sender, SendActivityEventArgs e) { //Extract the return value from the response payload and log it CallExternalService.ExternalServices.GetDataResponse response = e.SendActivity.ParameterBindings[SendActivity.ReturnValuePropertyName].Value as CallExternalService.ExternalServices.GetDataResponse; logToHistoryListActivity2_HistoryDescription = "Returned: " + response.@string; }
public Guid workflowId = default(System.Guid);
public SPWorkflowActivationProperties workflowProperties = new SPWorkflowActivationProperties();
public ExternalServices.GetDataRequest sendActivity1_request1 = new CallExternalService.ExternalServices.GetDataRequest();
public String logToHistoryListActivity1_HistoryDescription = default(System.String);
public String logToHistoryListActivity2_HistoryDescription = default(System.String);
public Workflow1()
{
InitializeComponent();
}
private void onWorkflowActivated1_Invoked(object sender, ExternalDataEventArgs e)
//Simulate taking data (e.g., from ListItem) in to pass as parameter to the external WCF Workflow Service
sendActivity1_request1.@int = 42;
private void PrepLogData(object sender, EventArgs e)
//Build the string to log
logToHistoryListActivity1_HistoryDescription = String.Format("Workflow was started. Preparing to send '{0}'.", sendActivity1_request1.@int);
private void DataReceived(object sender, SendActivityEventArgs e)
//Extract the return value from the response payload and log it
CallExternalService.ExternalServices.GetDataResponse response = e.SendActivity.ParameterBindings[SendActivity.ReturnValuePropertyName].Value as CallExternalService.ExternalServices.GetDataResponse;
logToHistoryListActivity2_HistoryDescription = "Returned: " + response.@string;
All of the class level fields are for the activities to exchange data. The first two (workflowId and workflowProperties) are added by the item template by default. The sendActivity1_request1 represents the message that will be sent to GetData with the input value. Within the onWorkflowActivated1_Invoked method, we set this value to the constant 42. The two HistoryDescription fields will store the string of what we want to write out to the SharePoint Workflow History list in the first and second LogToHistoryList activities. The onWorlflowActivated1_Invoked method will be called when the first activity in the sequence runs and it will serve to initialize the input parameter to GetData. PrepLogData is called by the first LogToHistoryList activity. The DataReceived method is called by the SendActivity after it has gotten the response from the external SimpleWorkflowService. Before these can be used, we have to wire them up to the activities in our workflow.
SharePoint 2010 workflow packages cannot read the values from the app.config that were added by the Add Service Reference. In order for our client endpoint configuration to be available to the SendActivity, we need to put this endpoint in the web.config of the SharePoint site.
<configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <security mode="None"> <transport clientCredentialType="None" proxyCredentialType="None" realm="" /> <message clientCredentialType="UserName" algorithmSuite="Default" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost/SimpleWorkflowService/Service1.xamlx" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService" contract="ExternalServices.IService" name="BasicHttpBinding_IService" /> </client> </system.serviceModel> </configuration>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost/SimpleWorkflowService/Service1.xamlx"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService"
contract="ExternalServices.IService" name="BasicHttpBinding_IService" />
</client>
</system.serviceModel>
</configuration>
Now we will test the interoperability by activating the Workflow (adding new doc) within SharePoint and verify that it works as expected.
The last entry in the Workflow History confirms the ‘Returned: 42’ value by the Workflow hosted in the Windows Server AppFabric.
Namaste!