Defining service contracts programmatically on .NET Framework 3.5

As I referred on a previous post, one of the most attractive features in the upcoming .NET Framework 3.5 (Orcas) is the combination of Workflow Services and Durable Services to implement long running services combining Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF). The main vision behind Workflow Services is to integrate the long running capabilities of WF with the messaging and contract authoring capabilities of WCF. Specifically for contract authoring, Workflow Services provide two main authoring styles:

  • Contract-First Workflow Services: A contract-first workflow service is a workflow that uses preexisting service contract information. The contract information can be either downloaded from a url or defined within the workflow project.
  • Workflow-First Workflow Services: The alternative to the contract first approach is to create the contract together with the workflow. The typical way of creating workflow-first workflow services is using the editors tools included in the Receive activity.

Workflow-First Services can be defined at design time; my friend Matt Winkle has posted a few samples about this technique. Complementary, Workflow-First Workflow Services can also be generated programmatically.

Generating contracts dynamically is mostly applied on scenarios on which contracts are based on metadata that is constantly changing. A classic example is contracts that reflect the type information of Line of business (LOB) systems such as SAP or Siebel.  What makes those scenarios different from the classic contract design, is the fact that the WSDL needs to reflect type information that is constantly changing which makes the traditional approach to contract creation unpractical in most of the cases. Including strongly typed information as part of the WSDL facilitates the consumption of the services from applications that depend heavily on type information at design time such as BizTalk Server or SQL Server Integration Services. The Salesforce.com Apex API is a great example of dynamic contract generation; in this case for a CRM system. If you want to get more familiar with technologies that leverage this approach of generating dynamic Service contracts take a look at the recently released WCF LOB Adapters SDK.

In the current WF version included as part of Orcas we can create WCF contracts programmatically using the OperationInfo and the OperationParameterInfo classes. In order to associate the generated contract with a Workflow Service we need to populate ServiceOperationInfo of the ReceiveActivity

Let's take the following sample workflow composed only by a ReceiveActivity.

Dynamic WF

The following code highlights how to create an OperationInfo instance that describes the contract implemented by the workflow. In our scenario the contract contains a single operation that receives two parameters as input. The code for generating the contract is included as part of the workflow constructor.

public sealed partial class Workflow1: SequentialWorkflowActivity

                {

        public string echoParam;

 

                                public Workflow1()

                                {

                                                InitializeComponent();

                                           CreateContract();

                                }

 

        private void CreateContract()

        {

            if (receiveActivity1.ServiceOperationInfo.Name != "TestOp")

            {

                OperationInfo opInfo = new OperationInfo();

                opInfo.ContractName = "DynamicContract";

                opInfo.Name = "TestOp";

                OperationParameterInfo param1Info = new OperationParameterInfo("param1");

                param1Info.ParameterType = typeof(int);

                param1Info.Attributes = ((System.Reflection.ParameterAttributes)((System.Reflection.ParameterAttributes.In)));

                param1Info.Position = 0;

                OperationParameterInfo param2Info = new OperationParameterInfo("param2");

                param2Info.ParameterType = typeof(int);

                param2Info.Position = 1;

                param2Info.Attributes = ((System.Reflection.ParameterAttributes)((System.Reflection.ParameterAttributes.In)));

                opInfo.Parameters.Add(param1Info);

                opInfo.Parameters.Add(param2Info);

                receiveActivity1.ServiceOperationInfo = opInfo;

            }

        }

 

        private void codeActivity2_ExecuteCode(object sender, EventArgs e)

        {

   Console.WriteLine(OperationContext.Current.RequestContext.RequestMessage.Headers.MessageId);

  Process the message...

        }

}

 

After that, we need to create an application that hosts the workflow using the WorkflowServiceHost class.

 static void Main(string[] args)
{
WorkflowServiceHost workflowHost = new WorkflowServiceHost(typeof(Workflow1));
workflowHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.WorkflowTerminated +=
delegate(object sender, WorkflowTerminatedEventArgs e) { Console.WriteLine("WorkflowTerminated: " + e.Exception.Message); };
workflowHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.WorkflowCompleted +=
delegate(object sender, WorkflowCompletedEventArgs e) { Console.WriteLine("WorkflowCompleted: " + e.WorkflowInstance.InstanceId.ToString()); };
workflowHost.Open();
Console.WriteLine("SequentialCalculatorService is ready.");
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Press <enter> to exit.");
Console.ResetColor();
Console.ReadLine();
workflowHost.Close();
}

 

Finally we need to associate the service with the corresponding WCF bindings.  Specifically on this case we are using the wsHttpContextBinding binding.

<configuration>

  <system.serviceModel>

    <services>

      <service name="WFDynamicContract.Workflow1" behaviorConfiguration="ServiceBehavior" >

        <host>

          <baseAddresses>

            <add baseAddress="http://myserver:myport/ServiceHost/Calculator.svc" />

          </baseAddresses>

        </host>

 

        <endpoint address=""

                  binding="wsHttpContextBinding"

                  contract="DynamicContract" />

      </service>

    </services>

 

    <behaviors>

      <serviceBehaviors>

        <behavior name="ServiceBehavior"  >

          <serviceMetadata httpGetEnabled="true" />

          <serviceDebug includeExceptionDetailInFaults="true" />

          <!-- Comment out the following behavior to disable persistence store -->

          <workflowRuntime name="WorkflowServiceHostRuntime" validateOnCreate="true" enablePerformanceCounters="true">

            <services>

              <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

                   connectionString="Data Source=myserver;Initial Catalog=WFStore;Integrated Security=True;Pooling=False"

                   LoadIntervalSeconds="1" UnLoadOnIdle= "true" />

            </services>

          </workflowRuntime>

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

 

When we start the host the workflow constructor is called producing the following WSDL.

<wsdl:definitions name="WFDynamicContract.Workflow1" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://tempuri.org/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex">

  <wsp:Policy wsu:Id="WSHttpContextBinding_DynamicContract_policy">

    Policy Info...

  </wsp:Policy>

  <wsdl:types>

    <xs:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://tempuri.org/">

      <xs:element name="TestOp">

        <xs:complexType>

          <xs:sequence>

            <xs:element minOccurs="0" name="param1" type="xs:int"/>

            <xs:element minOccurs="0" name="param2" type="xs:int"/>

          </xs:sequence>

        </xs:complexType>

      </xs:element>

      <xs:element name="TestOpResponse">

        <xs:complexType>

          <xs:sequence/>

        </xs:complexType>

      </xs:element>

    </xs:schema>

    <xsd:schema targetNamespace="http://tempuri.org/Imports">

      <xsd:import schemaLocation="http://localhost:8888/ServiceHost/Calculator.svc?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/>

    </xsd:schema>

  </wsdl:types>

  <wsdl:message name="DynamicContract_TestOp_InputMessage">

    <wsdl:part name="parameters" element="tns:TestOp"/>

  </wsdl:message>

  <wsdl:message name="DynamicContract_TestOp_OutputMessage">

    <wsdl:part name="parameters" element="tns:TestOpResponse"/>

  </wsdl:message>

  <wsdl:portType name="DynamicContract">

    <wsdl:operation name="TestOp">

      <wsdl:input wsaw:Action="http://tempuri.org/DynamicContract/TestOp" message="tns:DynamicContract_TestOp_InputMessage"/>

      <wsdl:output wsaw:Action="http://tempuri.org/DynamicContract/TestOpResponse" message="tns:DynamicContract_TestOp_OutputMessage"/>

    </wsdl:operation>

  </wsdl:portType>

  <wsdl:binding name="WSHttpContextBinding_DynamicContract" type="tns:DynamicContract">

    <wsp:PolicyReference URI="#WSHttpContextBinding_DynamicContract_policy"/>

    <soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/>

    <wsdl:operation name="TestOp">

      <soap12:operation soapAction="http://tempuri.org/DynamicContract/TestOp" style="document"/>

      <wsdl:input>

        <wsp:PolicyReference URI="#WSHttpContextBinding_DynamicContract_TestOp_Input_policy"/>

        <soap12:body use="literal"/>

      </wsdl:input>

      <wsdl:output>

        <wsp:PolicyReference URI="#WSHttpContextBinding_DynamicContract_TestOp_output_policy"/>

        <soap12:body use="literal"/>

      </wsdl:output>

    </wsdl:operation>

  </wsdl:binding>

  <wsdl:service name="WFDynamicContract.Workflow1">

    <wsdl:port name="WSHttpContextBinding_DynamicContract" binding="tns:WSHttpContextBinding_DynamicContract">

      <soap12:address location="http://localhost:8888/ServiceHost/Calculator.svc"/>

      <wsa10:EndpointReference>

        <wsa10:Address>http://localhost:8888/ServiceHost/Calculator.svc</wsa10:Address>

        Identity Information....

      </wsa10:EndpointReference>

    </wsdl:port>

  </wsdl:service>

</wsdl:definitions>

 

As you can see the WSDL reflects the type information generated on the Workflow instance creation. Arguably, one of the drawbacks of this approach is that the service implementation can't take advantage of the object-xml serialization-deserialization processes because the contract is dynamically generated. On the other hands it is undoubtedly that there are a lot of scenarios that can be a great fit for dynamically generated contracts. Specifically, in WF this approach is a natural complement to the Workflow-First contract authoring style.

2 Comments

Comments have been disabled for this content.