Cloud Proxy Demo / Simple BizTalk Workflow Services End-to-end demo

(German version may be written at a later time / Deutsche Version wird gegebenenfalls später geschrieben)

This post combines several concepts from several posts about BizTalk Workflow Services. You should read the intro, about flow control, about send&receive activities and about the little trick to hide some code in conditions. Additionally we will showcase binding of properties in BizTalk Workflow Services.

What shall be achieved?

We want a workflow instance to communicate with a service and be called back by the service. Then the workflow should do something and wait for the client to fetch the result of the work done.

Some challenges:

  1. How to get the WorkflowInstanceId to the service?
  2. How to get information back to the workflow?
  3. How to pass the information from one activity in the workflow to another activity?
  4. How to get the information back to the service?

Challenge 1: How to get the WorkflowInstanceId to the service?

There is currently no out-of-the-box way to access the WorkflowInstanceId and there is no "setter" activity to store a value somewhere. To do this, we will use the technique described in here, where we hide code in conditions to send information via the CloudServiceBusSend activity to the service.

We will use the following condition for that.

System.Convert.ToBoolean(((System.ServiceBus.Workflow.CloudServiceBusSend) this.GetActivityByName("cloudServiceBusSend1") ).GetType().GetProperty("Body").SetValue(this.GetActivityByName("cloudServiceBusSend1"), "<Echo xmlns='http://samples.microsoft.com/ServiceModel/Relay/'><text></text> </Echo>".Insert(69, this.WorkflowInstanceId.ToString()), null)) || True

Note that this condition results in an Xml-string that is longer than 8192 characters. You will have to strip whitespace from that Xml-string to be able to upload it to BizTalk Workflow Services.

Challenge 2: How to get information back to the workflow?

Fortunately we received the InstanceId, now we need to tell the workflow that we want it to do something for us. Unfortunately CloudServiceBusSend so far does not work with returned values. So: The only way to call the workflow is through the CloudHttpReceive activity and we need the InstanceId to call the proper instance. Using the CloudHttpReceive was demonstrated here.

Challenge 3: How to pass the information from one activity in the workflow to another activity?

As mentioned, we have no activities for getting/setting or manipulating data. We could either employ the previously mentioned trick to hide some code in conditions or we can use property binding. We will do the latter.

We bind the request property of a CloudHttpReceive activity to the URL property of a CloudHttpSend activity. So, the URL of the CloudHttpSend will be "filled" by the incoming WebHttpRequest and when CloudHttpSend gets executed it will request data from the URL that was supplied externally.

Activity=cloudHttpSend1, Path=URL

The complete activity with activity binding would look like this:

<ns0:CloudHttpReceive Request="{ActivityBind cloudHttpSend1,Path=URL}" x:Name="cloudHttpReceive1" StatusCode="0" Response="Hello" />

Challenge 4: How to get the information back to the service?

To solve that challenge we can either use a CloudServiceBusSend or we can have the service asking the workflow via a CloudHttpReceive activity. We will use CloudHttpReceive in this case because we want the service to "control" when it asks for the result.

The workflow

Now let us look at the complete workflow first

SendSbReceiveHttpWf1

and as Xoml definition:

<SequentialWorkflowActivity x:Class="WorkflowProject1.Workflow1" x:Name="Workflow1" xmlns:ns0="http://schemas.microsoft.com/2007/11/CloudWorkflowActivityLibrary" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

  <ns0:CloudWhile x:Name="cloudWhile1">

    <ns0:CloudWhile.Condition>

      <RuleConditionReference ConditionName="While Condition" />

    </ns0:CloudWhile.Condition>

    <ns0:CloudSequence x:Name="cloudSequence1">

      <ns0:CloudServiceBusSend x:Name="cloudServiceBusSend1" URL="sb://connect.biztalk.net/services/YOURUSERNAMEHERE/EchoService/" Response="" Body="&lt;Echo xmlns='http://samples.microsoft.com/ServiceModel/Relay/'&gt; &lt;text&gt;Original&lt;/text&gt;&lt;/Echo&gt;" Action="http://samples.microsoft.com/ServiceModel/Relay/EchoContract/Echo" ConnectionMode="RelayedDuplexSession" />

      <ns0:CloudHttpReceive Request="{ActivityBind cloudHttpSend1,Path=URL}" x:Name="cloudHttpReceive1" StatusCode="0" Response="Hello" />

      <ns0:CloudHttpSend x:Name="cloudHttpSend1" StatusCode="0" URL="http://www.microsoft.com" Method="GET" Request="{x:Null}" RequestContentType="{x:Null}" Response="{ActivityBind cloudHttpReceive2,Path=Response}" />

      <ns0:CloudHttpReceive Request="{x:Null}" x:Name="cloudHttpReceive2" StatusCode="0" Response="Bye" />

      <ns0:CloudDelay x:Name="cloudDelay1" Timeout="00:00:10" />

    </ns0:CloudSequence>

  </ns0:CloudWhile>

</SequentialWorkflowActivity>

Let's look at the activities from top to bottom.

 

cloudSequence1Properties

There is not really anything to say about cloudSequence1.

 

cloudWhile1Properties

The cloudWhile1 activity has the condition set as seen in Challenge 1.

The condition is represented by this Xml structure:

<RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

  <RuleDefinitions.Conditions>

    <RuleExpressionCondition Name="While Condition">

      <RuleExpressionCondition.Expression>

        <ns0:CodeBinaryOperatorExpression Operator="BooleanOr" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">

          <ns0:CodeBinaryOperatorExpression.Left>

            <ns0:CodeMethodInvokeExpression>

              <ns0:CodeMethodInvokeExpression.Parameters>

                <ns0:CodeMethodInvokeExpression>

                  <ns0:CodeMethodInvokeExpression.Parameters>

                    <ns0:CodeMethodInvokeExpression>

                      <ns0:CodeMethodInvokeExpression.Parameters>

                        <ns0:CodePrimitiveExpression>

                          <ns0:CodePrimitiveExpression.Value>

                            <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">cloudServiceBusSend1</ns1:String>

                          </ns0:CodePrimitiveExpression.Value>

                        </ns0:CodePrimitiveExpression>

                      </ns0:CodeMethodInvokeExpression.Parameters>

                      <ns0:CodeMethodInvokeExpression.Method>

                        <ns0:CodeMethodReferenceExpression MethodName="GetActivityByName">

                          <ns0:CodeMethodReferenceExpression.TargetObject>

                            <ns0:CodeThisReferenceExpression />

                          </ns0:CodeMethodReferenceExpression.TargetObject>

                        </ns0:CodeMethodReferenceExpression>

                      </ns0:CodeMethodInvokeExpression.Method>

                    </ns0:CodeMethodInvokeExpression>

                    <ns0:CodeMethodInvokeExpression>

                      <ns0:CodeMethodInvokeExpression.Parameters>

                        <ns0:CodePrimitiveExpression>

                          <ns0:CodePrimitiveExpression.Value>

                            <ns1:Int32 xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">69</ns1:Int32>

                          </ns0:CodePrimitiveExpression.Value>

                        </ns0:CodePrimitiveExpression>

                        <ns0:CodeMethodInvokeExpression>

                          <ns0:CodeMethodInvokeExpression.Method>

                            <ns0:CodeMethodReferenceExpression MethodName="ToString">

                              <ns0:CodeMethodReferenceExpression.TargetObject>

                                <ns0:CodePropertyReferenceExpression PropertyName="WorkflowInstanceId">

                                  <ns0:CodePropertyReferenceExpression.TargetObject>

                                    <ns0:CodeThisReferenceExpression />

                                  </ns0:CodePropertyReferenceExpression.TargetObject>

                                </ns0:CodePropertyReferenceExpression>

                              </ns0:CodeMethodReferenceExpression.TargetObject>

                            </ns0:CodeMethodReferenceExpression>

                          </ns0:CodeMethodInvokeExpression.Method>

                        </ns0:CodeMethodInvokeExpression>

                      </ns0:CodeMethodInvokeExpression.Parameters>

                      <ns0:CodeMethodInvokeExpression.Method>

                        <ns0:CodeMethodReferenceExpression MethodName="Insert">

                          <ns0:CodeMethodReferenceExpression.TargetObject>

                            <ns0:CodePrimitiveExpression>

                              <ns0:CodePrimitiveExpression.Value>

                                <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">&lt;Echo xmlns='http://samples.microsoft.com/ServiceModel/Relay/'&gt;&lt;text&gt;&lt;/text&gt; &lt;/Echo&gt;</ns1:String>

                              </ns0:CodePrimitiveExpression.Value>

                            </ns0:CodePrimitiveExpression>

                          </ns0:CodeMethodReferenceExpression.TargetObject>

                        </ns0:CodeMethodReferenceExpression>

                      </ns0:CodeMethodInvokeExpression.Method>

                    </ns0:CodeMethodInvokeExpression>

                    <ns0:CodePrimitiveExpression />

                  </ns0:CodeMethodInvokeExpression.Parameters>

                  <ns0:CodeMethodInvokeExpression.Method>

                    <ns0:CodeMethodReferenceExpression MethodName="SetValue">

                      <ns0:CodeMethodReferenceExpression.TargetObject>

                        <ns0:CodeMethodInvokeExpression>

                          <ns0:CodeMethodInvokeExpression.Parameters>

                            <ns0:CodePrimitiveExpression>

                              <ns0:CodePrimitiveExpression.Value>

                                <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">Body</ns1:String>

                              </ns0:CodePrimitiveExpression.Value>

                            </ns0:CodePrimitiveExpression>

                          </ns0:CodeMethodInvokeExpression.Parameters>

                          <ns0:CodeMethodInvokeExpression.Method>

                            <ns0:CodeMethodReferenceExpression MethodName="GetProperty">

                              <ns0:CodeMethodReferenceExpression.TargetObject>

                                <ns0:CodeMethodInvokeExpression>

                                  <ns0:CodeMethodInvokeExpression.Method>

                                    <ns0:CodeMethodReferenceExpression MethodName="GetType">

                                      <ns0:CodeMethodReferenceExpression.TargetObject>

                                        <ns0:CodeCastExpression TargetType="System.ServiceBus.Workflow.CloudServiceBusSend, System.ServiceBus.Workflow, Version=0.12.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

                                          <ns0:CodeCastExpression.Expression>

                                            <ns0:CodeMethodInvokeExpression>

                                              <ns0:CodeMethodInvokeExpression.Parameters>

                                                <ns0:CodePrimitiveExpression>

                                                  <ns0:CodePrimitiveExpression.Value>

                                                    <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">cloudServiceBusSend1</ns1:String>

                                                  </ns0:CodePrimitiveExpression.Value>

                                                </ns0:CodePrimitiveExpression>

                                              </ns0:CodeMethodInvokeExpression.Parameters>

                                              <ns0:CodeMethodInvokeExpression.Method>

                                                <ns0:CodeMethodReferenceExpression MethodName="GetActivityByName">

                                                  <ns0:CodeMethodReferenceExpression.TargetObject>

                                                    <ns0:CodeThisReferenceExpression />

                                                  </ns0:CodeMethodReferenceExpression.TargetObject>

                                                </ns0:CodeMethodReferenceExpression>

                                              </ns0:CodeMethodInvokeExpression.Method>

                                            </ns0:CodeMethodInvokeExpression>

                                          </ns0:CodeCastExpression.Expression>

                                        </ns0:CodeCastExpression>

                                      </ns0:CodeMethodReferenceExpression.TargetObject>

                                    </ns0:CodeMethodReferenceExpression>

                                  </ns0:CodeMethodInvokeExpression.Method>

                                </ns0:CodeMethodInvokeExpression>

                              </ns0:CodeMethodReferenceExpression.TargetObject>

                            </ns0:CodeMethodReferenceExpression>

                          </ns0:CodeMethodInvokeExpression.Method>

                        </ns0:CodeMethodInvokeExpression>

                      </ns0:CodeMethodReferenceExpression.TargetObject>

                    </ns0:CodeMethodReferenceExpression>

                  </ns0:CodeMethodInvokeExpression.Method>

                </ns0:CodeMethodInvokeExpression>

              </ns0:CodeMethodInvokeExpression.Parameters>

              <ns0:CodeMethodInvokeExpression.Method>

                <ns0:CodeMethodReferenceExpression MethodName="ToBoolean">

                  <ns0:CodeMethodReferenceExpression.TargetObject>

                    <ns0:CodeTypeReferenceExpression Type="System.Convert, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

                  </ns0:CodeMethodReferenceExpression.TargetObject>

                </ns0:CodeMethodReferenceExpression>

              </ns0:CodeMethodInvokeExpression.Method>

            </ns0:CodeMethodInvokeExpression>

          </ns0:CodeBinaryOperatorExpression.Left>

          <ns0:CodeBinaryOperatorExpression.Right>

            <ns0:CodePrimitiveExpression>

              <ns0:CodePrimitiveExpression.Value>

                <ns1:Boolean xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">true</ns1:Boolean>

              </ns0:CodePrimitiveExpression.Value>

            </ns0:CodePrimitiveExpression>

          </ns0:CodeBinaryOperatorExpression.Right>

        </ns0:CodeBinaryOperatorExpression>

      </RuleExpressionCondition.Expression>

    </RuleExpressionCondition>

  </RuleDefinitions.Conditions>

</RuleDefinitions>

For "copy/paste" purposes, here is the whitespace-stripped Xml-representation:

<RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"><RuleDefinitions.Conditions><RuleExpressionCondition Name="While Condition"><RuleExpressionCondition.Expression><ns0:CodeBinaryOperatorExpression Operator="BooleanOr" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"><ns0:CodeBinaryOperatorExpression.Left><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Parameters><ns0:CodePrimitiveExpression><ns0:CodePrimitiveExpression.Value><ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">cloudServiceBusSend1</ns1:String></ns0:CodePrimitiveExpression.Value></ns0:CodePrimitiveExpression></ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="GetActivityByName"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodeThisReferenceExpression /></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Parameters><ns0:CodePrimitiveExpression><ns0:CodePrimitiveExpression.Value><ns1:Int32 xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">69</ns1:Int32></ns0:CodePrimitiveExpression.Value></ns0:CodePrimitiveExpression><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="ToString"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodePropertyReferenceExpression PropertyName="WorkflowInstanceId"><ns0:CodePropertyReferenceExpression.TargetObject><ns0:CodeThisReferenceExpression /></ns0:CodePropertyReferenceExpression.TargetObject></ns0:CodePropertyReferenceExpression></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression></ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="Insert"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodePrimitiveExpression><ns0:CodePrimitiveExpression.Value><ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">&lt;Echo xmlns='http://samples.microsoft.com/ServiceModel/Relay/'&gt;&lt;text&gt;&lt;/text&gt; &lt;/Echo&gt;</ns1:String></ns0:CodePrimitiveExpression.Value></ns0:CodePrimitiveExpression></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression><ns0:CodePrimitiveExpression /></ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="SetValue"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Parameters><ns0:CodePrimitiveExpression><ns0:CodePrimitiveExpression.Value><ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">Body</ns1:String></ns0:CodePrimitiveExpression.Value></ns0:CodePrimitiveExpression></ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="GetProperty"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="GetType"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodeCastExpression TargetType="System.ServiceBus.Workflow.CloudServiceBusSend, System.ServiceBus.Workflow, Version=0.12.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"><ns0:CodeCastExpression.Expression><ns0:CodeMethodInvokeExpression><ns0:CodeMethodInvokeExpression.Parameters><ns0:CodePrimitiveExpression><ns0:CodePrimitiveExpression.Value><ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">cloudServiceBusSend1</ns1:String></ns0:CodePrimitiveExpression.Value></ns0:CodePrimitiveExpression></ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="GetActivityByName"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodeThisReferenceExpression /></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression></ns0:CodeCastExpression.Expression></ns0:CodeCastExpression></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression></ns0:CodeMethodInvokeExpression.Parameters><ns0:CodeMethodInvokeExpression.Method><ns0:CodeMethodReferenceExpression MethodName="ToBoolean"><ns0:CodeMethodReferenceExpression.TargetObject><ns0:CodeTypeReferenceExpression Type="System.Convert, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /></ns0:CodeMethodReferenceExpression.TargetObject></ns0:CodeMethodReferenceExpression></ns0:CodeMethodInvokeExpression.Method></ns0:CodeMethodInvokeExpression></ns0:CodeBinaryOperatorExpression.Left><ns0:CodeBinaryOperatorExpression.Right><ns0:CodePrimitiveExpression><ns0:CodePrimitiveExpression.Value><ns1:Boolean xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">true</ns1:Boolean></ns0:CodePrimitiveExpression.Value></ns0:CodePrimitiveExpression></ns0:CodeBinaryOperatorExpression.Right></ns0:CodeBinaryOperatorExpression></RuleExpressionCondition.Expression></RuleExpressionCondition></RuleDefinitions.Conditions></RuleDefinitions>

 

cloudServiceBusSend1Properties

cloudServiceBusSend1 is configured like this with the "Body" property actually at execution time being overwritten by the condition of cloudWhile1.

 

 

cloudHttpReceive1Properties

cloudHttpReceive1 will get data in the "Request" property and put it into the "URL" property of cloudHttpSend1.

 

 

cloudHttpSend1Properties

cloudHttpSend1 will find it's URL property overwritten by the data received at cloudHttpReceive1. Then it will execute the WebHttpRequest and put the body of the result in the "Response" property of cloudHttpReceive2.

 

cloudHttpReceive2Properties

cloudHttpReceive2 looks very empty. The "Response" property however will be overwritten by the data bound in cloudHttpSend1.

 

cloudDelay1Properties

cloudDelay1 is configured to wait 10 seconds.

Create this workflow by copy/pasting the Xoml and rules and setting the type name to "SimpleEnd2End".

The service

Now you need to set up the service. To do that work on a copy of Program Files\Microsoft BizTalk Services SDK\Samples\Communication\GettingStarted\Echo.

You will need to modify the file "EchoService.cs".

Modify the "using" statements like this:

using System;

using System.ServiceModel;

using System.Threading;

using System.Net;

using System.IO;

using System.Text;

Copy the methods "CallHttpReceiveActivityWithLogin", "GetTokenGrantingToken" and "CallHttpReceiveActivity" from the code snippet at the bottom of send&receive activities to the class "EchoService".

Modify the "Echo"-method like this:

public string Echo(string text)

{

    Console.WriteLine(text + ":InstanceId received");

    Threading.ThreadStart starter = delegate { HttpCall(text); };

    new Threading.Thread(starter).Start();

    return "nothing to return really";       

}

You see that a new thread will be started with a delegate to HttpCall(text). This is because the service shall continue processing after returning from the "Echo"-method

Add a method "HttpCall" like this:

private void HttpCall(string instanceId)

{

    Console.WriteLine(instanceId+":Thread started");

    string username = "YOURUSERNAME";

    string password = "YOURPASSWORD";

    string workflowTypeName = "SimpleEnd2End";

    string activityName1 = "cloudHttpReceive1";

    string activityName2 = "cloudHttpReceive2";

    string dummyString = "does not matter";

    string urlToGet = "http://www.daenet.eu";

    //wait a bit before the first call

    Thread.Sleep(1000);

    Console.WriteLine(instanceId + ":request "+urlToGet);

    string result1 = CallHttpReceiveActivityWithLogin(

                                urlToGet, username, password,

                                workflowTypeName, activityName1,

                                instanceId);

    //wait a bit before the second call to allow loading of URL

    Thread.Sleep(5000);

    Console.WriteLine(instanceId + ":retrieve data for "

                      + urlToGet);

    string result2 = CallHttpReceiveActivityWithLogin(

                                dummyString, username, password,

                                workflowTypeName, activityName2,

                                instanceId);

    Console.WriteLine(instanceId + ":WebHttpResponse length "

                      + result2.Length);

    Console.WriteLine(instanceId + ":Thread exits");

}

After some initialization this method waits for 1 second and then does a WebHttpRequest to the activity cloudHttpReceive1, sending "http://www.daenet.eu" in the body of the request (which will then overwrite "http://www.microsoft.com" in the cloudHttpSend1 activity). At the same time the workflow in the cloud should continue and retrieve the wanted data. Then it waits another 5 seconds and executes another WebHttpRequest, but this time to the activity cloudHttpReceive2, where it will retrieve the result of what the workflow hopefully loaded meanwhile from the passed URL. Some output is printed to the console and the method and thread exits.
Please note that the workflow itself however will loop endlessly and likely block at some point since it may reach a CloudHttpReceive when there is nobody to send anymore.

 

For your convenience, the complete code of the modified file "EchoService.cs":

//---------------------------------------------------------------------------------

// Microsoft (R) BizTalk Services

// Software Development Kit

//

// Copyright (c) Microsoft Corporation. All rights reserved. 

// Copyright (c) of modifications: Andreas Erben

// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,

// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES

// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.

//---------------------------------------------------------------------------------

 

namespace System.ServiceBus.Samples

{

    using System;

    using System.ServiceModel;

    using System.Threading;

    using System.Net;

    using System.IO;

    using System.Text;

 

 

    [ServiceBehavior(Name = "EchoService",

        Namespace = "http://samples.microsoft.com/ServiceModel/Relay/")]

    class EchoService : EchoContract

    {

        public string Echo(string text)

        {

            Console.WriteLine(text + ":InstanceId received");

            Threading.ThreadStart starter = delegate { HttpCall(text); };

            new Threading.Thread(starter).Start();

            return "nothing to return really";       

        }

 

        private void HttpCall(string instanceId)

        {

            Console.WriteLine(instanceId+":Thread started");

            string username = "YOURUSERNAME";

            string password = "YOURPASSWORD";

            string workflowTypeName = "SimpleEnd2End";

            string activityName1 = "cloudHttpReceive1";

            string activityName2 = "cloudHttpReceive2";

            string dummyString = "does not matter";

            string urlToGet = "http://www.daenet.eu";

            //wait a bit before the first call

            Thread.Sleep(1000);

            Console.WriteLine(instanceId + ":request "+urlToGet);

            string result1 = CallHttpReceiveActivityWithLogin(

                                        urlToGet, username, password,

                                        workflowTypeName, activityName1,

                                        instanceId);

            //wait a bit before the second call to allow loading of URL

            Thread.Sleep(5000);

            Console.WriteLine(instanceId + ":retrieve data for "

                              + urlToGet);

            string result2 = CallHttpReceiveActivityWithLogin(

                                        dummyString, username, password,

                                        workflowTypeName, activityName2,

                                        instanceId);

            Console.WriteLine(instanceId + ":WebHttpResponse length "

                              + result2.Length);

            Console.WriteLine(instanceId + ":Thread exits");

        }

 

        static string CallHttpReceiveActivityWithLogin(

                    string requestBody, string username, string password,

                    string workflowTypeName, string activityName,

                    string instanceId)

        {

            string token = GetTokenGrantingToken(username, password);

            return CallHttpReceiveActivity(

                                    requestBody, username, token,

                                    workflowTypeName, activityName,

                                    instanceId);

        }

 

        static string GetTokenGrantingToken(string userName, string password)

        {

            string tokenUrl = string.Format(

                "https://{0}/issuetoken.aspx?u={1}&p={2}",

                "identity.biztalk.net",

                userName,

                password);

 

            HttpWebRequest tokenRequest = (HttpWebRequest)WebRequest.Create(tokenUrl);

            HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse();

 

            using (Stream responseStream = tokenResponse.GetResponseStream())

            {

                byte[] tokenBody = new byte[500];

                int tokenBodyLength = responseStream.Read(tokenBody, 0, 500);

                return Encoding.UTF8.GetString(tokenBody, 0, tokenBodyLength);

            }

        }

 

        static string CallHttpReceiveActivity(

                            string requestBody, string username, string token,

                            string workflowTypeName, string activityName,

                            string instanceId)

        {

            Uri address = new Uri(@"http://workflow.biztalk.net/servicesHttp/"

                        + username + "/workflows/" + workflowTypeName +

                        "/instances/" + instanceId + "/" + activityName);

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(address);

            //this adds the retrieved token as a header

            request.Headers.Add("X-MS-Identity-Token", token);

            request.Method = "POST";

            request.ContentType = "text/plain";

            //send the body

            byte[] requestBodyBytes = Encoding.UTF8.GetBytes(requestBody);

            request.ContentLength = requestBodyBytes.Length;

            using (Stream requestStream = request.GetRequestStream())

            {

                requestStream.Write(requestBodyBytes, 0, requestBodyBytes.Length);

                requestStream.Flush();

            }

 

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            using (Stream responseStream = response.GetResponseStream())

            {

 

                using (StreamReader reader = new StreamReader(responseStream,

                                                              Encoding.UTF8))

                {

                    return reader.ReadToEnd();

                }

            }

        }

    }

}

 

Running the sample

  1. Start the modifed "Echo"-sample that will start-up its self-hosted ServiceBus endpoint.
  2. Create 1 or more instances of the type SimpleEnd2End.
  3. Start the instances of SimpleEnd2End.
  4. Watch the output in the console of the modified "Echo"-sample until you are happy and press enter.

The output for 1 workflow instance:

d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:InstanceId received
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:Thread started
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:request http://www.daenet.eu
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:retrieve data for http://www.daenet.eu
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:WebHttpResponse length 28427
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:Thread exits
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:InstanceId received
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:Thread started
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:request http://www.daenet.eu
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:retrieve data for http://www.daenet.eu
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:WebHttpResponse length 28427
d81d8ba2-3137-4a6d-aafe-c6c8c5eea1a8:Thread exits

SimpleEnd2EndOutput1Instance

 

The output for 2 concurrent workflow instances:

a7874223-e9d1-430c-b0f9-d36baf6d1076:InstanceId received
a7874223-e9d1-430c-b0f9-d36baf6d1076:Thread started
a7874223-e9d1-430c-b0f9-d36baf6d1076:request http://www.daenet.eu
befa13cf-4b4c-4118-82dc-4a94ef288952:InstanceId received
befa13cf-4b4c-4118-82dc-4a94ef288952:Thread started
befa13cf-4b4c-4118-82dc-4a94ef288952:request http://www.daenet.eu
a7874223-e9d1-430c-b0f9-d36baf6d1076:retrieve data for http://www.daenet.eu
befa13cf-4b4c-4118-82dc-4a94ef288952:retrieve data for http://www.daenet.eu
a7874223-e9d1-430c-b0f9-d36baf6d1076:WebHttpResponse length 28427
a7874223-e9d1-430c-b0f9-d36baf6d1076:Thread exits
befa13cf-4b4c-4118-82dc-4a94ef288952:WebHttpResponse length 28427
befa13cf-4b4c-4118-82dc-4a94ef288952:Thread exits
a7874223-e9d1-430c-b0f9-d36baf6d1076:InstanceId received
a7874223-e9d1-430c-b0f9-d36baf6d1076:Thread started
a7874223-e9d1-430c-b0f9-d36baf6d1076:request http://www.daenet.eu
befa13cf-4b4c-4118-82dc-4a94ef288952:InstanceId received
befa13cf-4b4c-4118-82dc-4a94ef288952:Thread started
befa13cf-4b4c-4118-82dc-4a94ef288952:request http://www.daenet.eu
a7874223-e9d1-430c-b0f9-d36baf6d1076:retrieve data for http://www.daenet.eu
befa13cf-4b4c-4118-82dc-4a94ef288952:retrieve data for http://www.daenet.eu
a7874223-e9d1-430c-b0f9-d36baf6d1076:WebHttpResponse length 28427
a7874223-e9d1-430c-b0f9-d36baf6d1076:Thread exits
befa13cf-4b4c-4118-82dc-4a94ef288952:WebHttpResponse length 28427
befa13cf-4b4c-4118-82dc-4a94ef288952:Thread exits

SimpleEnd2EndOutput2Instances

 

This is part of the actual retrieved data to be found in result2 when breaking after the second call to the workflow:

resultOutput

Note that it will, as seen in the previous blog-post be wrapped in the XML representation of a string object containing the retrieved data. To get the actual data, you would need to parse that XML structure (even though it is a simple parse).

This is all for now, I hope it made BizTalk Workflow Services more "accessible".

 

Finally some advertisment: If you want to talk to us/me about BizTalk Workflow Services or other exciting technologies or approaches then contact us/me. "face 2 face" logistics are most easy in the US or Europe.


Posted Oct 02 2008, 11:55 AM by Andreas Erben

Comments

Andreas Erben wrote re: Cloud Proxy Demo / Simple BizTalk Workflow Services End-to-end demo
on 10-06-2008 2:43

Small errata: During copy paste the first declarative rule condition got mangled up and some XML was lost. So if it did not work earlier and you used a copy/paste, please try again.

System.Convert.ToBoolean(((System.ServiceBus.Workflow.CloudServiceBusSend) this.GetActivityByName("cloudServiceBusSend1") ).GetType().GetProperty("Body").SetValue(this.GetActivityByName("cloudServiceBusSend1"), "<Echo xmlns='samples.microsoft.com/.../& </Echo>".Insert(69, this.WorkflowInstanceId.ToString()), null)) || True

In general please be wary of whitespace when copying. For formatting purposes I entered a few unnecessary spaces in XML strings.

developers.de is a .Net Community Blog powered by daenet GmbH.