Contents tagged with WWF

  • WCF Workflow services in business applications

    It's pretty interesting to see how useful workflow services can be sometimes, specially for business applications that require orchestrating several back-end services calls.

    As one of the programmers involved in the development of the WS-I Sample application for .NET, an application that demonstrates  how to build interoperable web services in .NET using the WS-I Basic Profile 1.0 (Each vendor in the WS-I working group basically implemented the same application using its development platform, more information can be found here), it was a surprise to me to find another implementation as part of the WCF SDK, made this time all declaratively through WCF workflow services and just a few activities. To give you an idea, the original version of the same application took us some days and hundred lines of code just to have something complete and working.

    The SDK version is available under the folder, [SDK Folder]\WCF_WF_CardSpace_Samples\WCF\Scenario\WorkflowServices\Conversations

    Workflow services, initially introduced in WCF 3.5 with the Receive and Send activities, will now take a very important role as part of the Oslo vision. We will have the opportunity to write pure declarative long-running services with XAML, including all the WCF contracts for receiving/sending messages. This will make possible to store the complete workflow representation in the Oslo repository, and take later full control of this workflow's instances from Dublin, the new hosting environment for long-running services. David Chappell has written an excellent article about WF 4.0, Dublin the oslo vision, you can find it here, this a must read for developers interested in this area.

    Read more...

  • Routing to the right workflow service instance through URI templates (REST workflows part III)

    Continuing from my previous posts "REST and Workflow services play well together I" and "REST and Workflow services play well together II", in which I created a new binding to send the workflow context as an http header, I've applied some modifications to that sample to support also routing through URI templates. This is very helpful for scenarios where client applications are not necessarily aware of the existence of a workflow services on the other end, so the context information is reconstructed from the resource URI using Uri templates.

    For example, the following Uri template "/orders/{instanceId}" applied to the Uri http://localhost:8000/orders/3C3DE415-71F4-4538-ADA6-87A735827DF6 will result in a match for the key "instanceId" with a value of "3C3DE415-71F4-4538-ADA6-87A735827DF6". We can now pass that instanceId variable to the WF context correlation manager so it will be able to locate and load the corresponding workflow service instance.

    The configuration for the binding on the service side looks as follow,

    <webHttpContext>

    <binding name="myServiceBinding" contextMode="UriTemplate">

      <uriTemplates>

        <add name="orders" value="order/{instanceId}"></add>

        <add name="payments" value="payment/order/{instanceId}"></add>

      </uriTemplates>

    </binding>

    </webHttpContext>

    UriTemplates is a collection, you can configure there all the possible URIs that your workflow can handle. In that example, instanceId is the name of the variable used by WF to locate the workflow instance, other variable names can also be used. For example "conversationId" can be used to continue the workflow in a specific ReceiveActivity (Usually required when the workflow is waiting for some event in a parallel branch activity)

    We can now return the following links (representing the possible branches in the workflow) to the client,

    currentOrder.Next = new Next[] {

        new Next

        {

            Rel = "http://starbucks.example.org/payment",

            Uri = "http://localhost:8000/payment/order/" + WorkflowEnvironment.WorkflowInstanceId.ToString(),

        },

        new Next

        {

            Rel = "http://starbucks.example/order/update",

            Uri = "http://localhost:8000/order/" + WorkflowEnvironment.WorkflowInstanceId.ToString()

        }

    };

    The client application will not need to remember to send a context header in the next execution to resume an existing workflow instance, the Uri will be enough to parse the context information.

    Download the complete sample from this location

    Read more...

  • REST and Workflow services play well together - Part II

    In my previous post "REST and Workflow services play well together", I mentioned that Http Cookies were one of the built-in mechanisms for transferring the workflow context across calls between the client and the service. While cookies work well for http services, from my point of view, simple Http Headers naturally fit better in a REST architecture. As consequence, I decided to extend the WCF channel stack to support a new a custom channel (Or custom binding) for transferring the workflow context as a http header.

    The configuration of this binding WebHttpContextBinding is quite straightforward, I just created a new binding "WebHttpContextBinding" that can easily configured for workflow service,

    <services>

      <service name="RestWorkflows.OrderWorkflow">

        <endpoint address="" behaviorConfiguration="MyServiceBehavior" binding="webHttpContext" bindingConfiguration="myServiceBinding" contract="RestWorkflows.IOrderService" />

      </service>

    </services>

    <bindings>

      <webHttpContext>

        <binding name="myServiceBinding"></binding>

      </webHttpContext>

    </bindings>

    <behaviors>

      <endpointBehaviors>

         <behavior name="MyServiceBehavior">

           <webHttp />

         </behavior>

      </endpointBehaviors>

    </behaviors>

    <extensions>

      <bindingExtensions>

        <add name="webHttpContext" type="Microsoft.ServiceModel.Samples.WebHttpContextBindingCollectionElement, WebHttpContext, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

      </bindingExtensions>

    </extensions>

    The result of using this binding will be a new Http Header WscContext (encoded as Base64) being transmitted between the client and the service,

    <MessageLogTraceRecord Time="2008-10-29T14:22:59.5258021-02:00" Source="TransportSend" Type="System.ServiceModel.Channels.BodyWriterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
       <Addressing>
         <Action></Action>
       </Addressing>
       <HttpResponse>
          <StatusCode>Created</StatusCode>
          <WebHeaders>      <WscContext>77u/PENvbnRleHQgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwNi8wNS9jb250ZXh0Ij48UHJvcGVydHkgbmFtZT0iaW5zdGFuY2VJZCI+NDZkZGY3NTktMzdjNS00ZDZlLWI2ZTItNjVmMDNjMDVmYTAyPC9Qcm9wZXJ0eT48L0NvbnRleHQ+</WscContext>
           </WebHeaders>
        </HttpResponse>
        <order xmlns="http://starbucks.example.org" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
          <cost>6</cost>
          <drink>latte</drink>
          <next xmlns:a="http://example.org/state-machine">
             <a:next>

              <a:rel>http://starbucks.example.org/payment</a:rel>
               <a:type>application/xml</a:type>
               <a:uri>http://localhost:8000/payment/order/0b756654-161b-43d0-912f-4236552dc337</a:uri>
             </a:next>
             <a:next>
                 <a:rel>http://starbucks.example/order/update</a:rel>
                 <a:type>application/xml</a:type>
                 <a:uri>http://localhost:8000/order/0b756654-161b-43d0-912f-4236552dc337</a:uri>
             </a:next>
           </next>
         </order>
    </MessageLogTraceRecord>

    <MessageLogTraceRecord Time="2008-10-29T14:22:59.5726021-02:00" Source="TransportReceive" Type="System.ServiceModel.Channels.BufferedMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
       <HttpRequest>
          <Method>PUT</Method>
           <QueryString></QueryString>
           <WebHeaders>
          <WscContext>77u/PENvbnRleHQgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwNi8wNS9jb250ZXh0Ij48UHJvcGVydHkgbmFtZT0iaW5zdGFuY2VJZCI+NDZkZGY3NTktMzdjNS00ZDZlLWI2ZTItNjVmMDNjMDVmYTAyPC9Qcm9wZXJ0eT48L0NvbnRleHQ+</WscContext>
            <Content-Length>566</Content-Length>
            <Content-Type>application/xml</Content-Type>
            <Expect>100-continue</Expect>
             <Host>localhost:8000</Host>
          </WebHeaders>
        </HttpRequest>
        <order xmlns="http://starbucks.example.org" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
              <cost>6</cost>
              <drink>cappuchino</drink>
              <next xmlns:a="http://example.org/state-machine">
                <a:next>
                  <a:rel>http://starbucks.example.org/payment</a:rel>
                  <a:type>application/xml</a:type>
                  <a:uri>http://localhost:8000/payment/order/0b756654-161b-43d0-912f-4236552dc337</a:uri>
                 </a:next>
                 <a:next>
                   <a:rel>http://starbucks.example/order/update</a:rel>
                   <a:type>application/xml</a:type>
                   <a:uri>http://localhost:8000/order/0b756654-161b-43d0-912f-4236552dc337</a:uri>
                 </a:next>
               </next>
           </order>
    </MessageLogTraceRecord>

    The code for the custom binding is available to download from this location.

    Read more...

  • REST and Workflow services play well together

    REST is not just about CRUD interfaces for your services, a complete long-running workflow can be modeled through the basic verbs as well. As my friend Jesus mentioned, the article "How to get a cup of coffee" represents an excellent source about this topic.

    The example I will show here is based on that article, during the course of this post I will try to illustrate all the steps required to implement a workflow as the one mentioned there using WF services. Unfortunately, no examples exist (or at least I could not find any) about how to configure the WCF web model to use the new WF services, so I decided to create a new one from scratch.

    This example only illustrates the workflow from the customer point of view, he can place an order, pay it and wait for his drink. He can also modify the order in the middle before it is paid.

     

    The interface for our REST service will looks like this:

    [ServiceContract]

    public interface IOrderService

    {

      [OperationContract]

      [WebInvoke(Method="POST", UriTemplate="order")]

      Order PlaceOrder(Order order);

     

      [OperationContract]

      [WebInvoke(Method = "PUT", UriTemplate = "order/{id}")]

      Order UpdateOrder(string id, Order order);

     

      [OperationContract]

      [WebInvoke(Method="PUT", UriTemplate="payment/order/{id}")]

      void PayOrder(string id, Payment payment);

    }

    Only three operations available, each one maps exactly with one ReceiveActivity in the workflow. For instance, the receive activity OnOrderPlaced waits for a client call to the PlaceOrder operation and then moves the workflow to the next state, which is "OrderPlaced" in this case. While the workflow is in the state "OrderPlaced", only the operations UpdateOrder and PayOrder can be executed by the client (If he try to execute PlaceOrder again, it will receive a 404 http error, "Not found").

    The OnOrderPlacedCode is an simple code activity that contains the actual operation implementation. I used a code activity for a sake of simplicity, a custom activity for creating the order could also be used there.

    The code for this activity is quite simple, most of it is hard-coded,

    private void OnOrderPlacedCode_ExecuteCode(object sender, EventArgs e)

    {

      currentOrder = new Order();

      currentOrder.OrderId = Guid.NewGuid().ToString();

      currentOrder.Cost = (receivedOrder.Drink == "latte") ? 6 : 10;

      currentOrder.Drink = receivedOrder.Drink;

      currentOrder.Next = new Next[] {

          new Next

          {

             Rel = "http://starbucks.example.org/payment",

             Uri = "http://localhost:8000/payment/order/" + currentOrder.OrderId.ToString(),

          },

          new Next

          {

             Rel = "http://starbucks.example/order/update",

             Uri = "http://localhost:8000/order/" + currentOrder.OrderId.ToString()

          }};

     

      //Persist the order in some place.....

     

      WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Created;

    }

    currentOrder contains the instance of the order that will be returned by the service operation whereas receivedOrder is the order sent by the client application. This was map to the operation signature in the receive activity,

     

    Once the workflow is ready to be used, all we need is to configure the service host so the client application can start consuming the operations. At this point, we will face three issues:

    1. If you want to host a WF service, a WorkflowServiceHost class has to be used in the host program. Therefore, we will not have all the automatic infrastructure configuration provided by WebServiceHost, all the configuration must be done manually. It would be great to have here a combination of both service hosts.

    2. There is not a context binding for WebHttpBinding, only BasicHttpContextBinding, NetTcpContextBinding and WsHttpContextBinding are supported. We will have to use a custom binding.

    3. A cookie must be used to transfer the context information between the client and the service, it would much better to have support for http headers here. According to this post written by Jesus, this should not be complex to do.

    <system.serviceModel>

      <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

      <services>

        <service name="RestWorkflows.OrderWorkflow">

         <endpoint address="" behaviorConfiguration="MyServiceBehavior" binding="customBinding" bindingConfiguration="myServiceBinding" contract="RestWorkflows.IOrderService" />

      </service>

    </services>

    <bindings>

    <customBinding>

      <binding name="myServiceBinding">

      <webMessageEncoding></webMessageEncoding>

      <context contextExchangeMechanism="HttpCookie" protectionLevel="None"/>

      <httpTransport manualAddressing="true"/>

      </binding>

    </customBinding>

    </bindings>

    <behaviors>

    <endpointBehaviors>

      <behavior name="MyServiceBehavior">

        <webHttp />

      </behavior>

    </endpointBehaviors>

    </behaviors>

    </system.serviceModel>

    The code for the client application looks also quite simple, you only have to remember to include the cookie with the context between calls, otherwise WF will try to create a new instance of the workflow resulting in a bad request error.

    //Lets create a new order

    Order order = new Order { Drink = "latte" };

     

    HttpWebRequest createOrderRequest = CreateRequest(new Uri("http://localhost:8000/order"), "POST", null, order);

    string context = GetContext((HttpWebResponse)createOrderRequest.GetResponse());

    order = Execute<Order>(createOrderRequest.GetResponse());

     

    Console.WriteLine("Order Cost {0}", order.Cost);

    foreach (Next next in order.Next)

    {

      Console.WriteLine("Possible next step {0}", next.Uri);

    }

     

    //Lets update the existing order....

    order.Drink = "cappuchino";

     

    Uri updateOrderUri = new Uri(order.Next.Where(n => n.Rel == "http://starbucks.example/order/update").Single().Uri);

    HttpWebRequest updateOrderRequest = CreateRequest(updateOrderUri, "PUT", context, order);

    Order updatedOrder = Execute<Order>(updateOrderRequest.GetResponse());

     

    Console.WriteLine("Order Cost {0}", updatedOrder.Cost);

    foreach (Next next in updatedOrder.Next)

    {

      Console.WriteLine("Possible next step {0}", next.Uri);

    }

     

    //Lets have our drink...

    Payment payment = new Payment { Name = "John Doe", CardNumber = "1234567", Expires = "06/08", Amount = updatedOrder.Cost.GetValueOrDefault() };

     

    Uri payOrderUri = new Uri(order.Next.Where(n => n.Rel == "http://starbucks.example.org/payment").Single().Uri);

    HttpWebRequest payOrderRequest = CreateRequest(payOrderUri, "PUT", context, payment);

     

    int statusCode = Execute(payOrderRequest.GetResponse());

     

    if (statusCode == 201)

      Console.WriteLine("Here is your drink!!!");

    else

      Console.WriteLine("Sorry, there was some error while trying to process the payment");

    As you can see in the code above, I used some helper methods to execute the operations and retrieve the response/context information. These methods only represent a few lines of code,

    static HttpWebRequest CreateRequest(Uri address, string method, string context, object contract)

    {

      HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(address);

      webRequest.ContentType = "application/xml";

      webRequest.Timeout = 30000;

      webRequest.Method = method;

      webRequest.CookieContainer = new CookieContainer();

     

      if (context != null)

      {

        Cookie cookie = new Cookie("WscContext", context, address.PathAndQuery, address.Authority);

        webRequest.CookieContainer.Add(cookie);

      }

     

      DataContractSerializer serializer = new DataContractSerializer(contract.GetType());

      using (Stream stream = webRequest.GetRequestStream())

      {

        serializer.WriteObject(stream, contract);

        stream.Flush();

      }

     

      return webRequest;

    }

     

    static string GetContext(HttpWebResponse response)

    {

      if (response.Cookies["WscContext"] != null)

      {

        return response.Cookies["WscContext"].Value;

      }

     

      return null;

    }

     

    static T Execute<T>(WebResponse response)

    {

      DataContractSerializer serializer = new DataContractSerializer(typeof(T));

      using (Stream stream = response.GetResponseStream())

      {

        return (T)serializer.ReadObject(stream);   

      }

    }

    The complete solution is available to download from here.

    Read more...