Cloudy messages / Send & Receive Activities in BizTalk Workflow Services

(German version may be provided at a later stage / Deutsche Version wird gegebenenfalls nachgereicht)

In previous posts, I gave an introduction to BizTalk Workflow Services, I wrote about flow control and rambled about tricks you can do in conditions.

This post will deal with the send and receive activities available now.

BizTalk Workflow Services offer the following send and receive activities:

  • CloudHttpReceive System.ServiceBus.Workflow.httpReceive
  • CloudHttpSend System.ServiceBus.Workflow.httpSend
  • CloudServiceBusSend System.ServiceBus.Workflow.sbSend

 

CloudServiceBusSend System.ServiceBus.Workflow.sbSend

CloudServiceBusSend System.ServiceBus.Workflow.sbSend was already used in the introduction post, so I will start with this.

CloudServiceBusSend System.ServiceBus.Workflow.sbSend with ConnectionMode RelayedMultiCast

Please look at the provided sample typically at Program Files\Microsoft BizTalk Services SDK\Samples\Workflow\GettingStarted and find the activity "cloudServiceBusSend1".

BizTalkServicesWorkflowGettingStarted01

The activity corresponds to the following Xoml-fragment:

<ns0:CloudServiceBusSend x:Name="cloudServiceBusSend1" URL="sb://connect.biztalk.net/services/ChatRooms/watchdog/MulticastService/" Response="{x:Null}" Body="&lt;Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'&gt; &lt;nickName&gt;watchdog&lt;/nickName&gt; &lt;text&gt;The Microsoft site is responding.&lt;/text&gt;&lt;/Chat&gt;" Action="http://samples.microsoft.com/ServiceModel/Relay/MulticastContract/Chat" ConnectionMode="RelayedMulticast" />

 

Select cloudServiceBusSend1 and look at the properties:

ServiceBusSendRelayedMultiCast 

ConnectionMode

There are two implemented ConnectionModes: "RelayedMultiCast" and "RelayedDuplexSession".
RelayedMultiCast requires a contract that only contains OneWay operations. "RelayedDuplexSession" in contrast will theoretically allow a response, but there seems to be a bug in the present version and the response will get lost. The posted example uses the "RelayedMultiCast" ConnectionMode. It interacts with the sample Program Files\Microsoft BizTalk Services SDK\Samples\Communication\ExploringFeatures\ConnectionModes\Multicast.

URL

This is the URL of the ServiceBus address you are trying to reach. A WorkflowValidator will not only check it for being not empty but also use the scheme "sb".

Action

Nothing really surprising. It is best understood though when looking at the contract of the corresponding sample

[ServiceContract(Name = "MulticastContract", Namespace = "http://samples.microsoft.com/ServiceModel/Relay/")]

public interface MulticastContract

{

    [OperationContract(IsOneWay=true)]

    void Hello(string nickName);

 

    [OperationContract(IsOneWay = true)]

    void Chat(string nickName, string text);

 

    [OperationContract(IsOneWay = true)]

    void Bye(string nickName);

}

So, it is basically Namespace+Name+Operation.

Body

<Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'> <nickName>watchdog</nickName> <text>The Microsoft site is responding.</text></Chat>

What is actually being sent. A WorkflowValidator will check if it is valid XML.
In this example you see the outer element being named "Chat" like the operation that is going to be called on the interface with the namespace "http://samples.microsoft.com/ServiceModel/Relay/" of the ServiceContract. Then there are two string parameters, each represented by an XML-Tag corresponding to the parameter names of the operation: "nickName" with its value "watchdog" and "text" with its value "The Microsoft site is responding.".

Response

The response is empty, with RelayedMultiCast it will always stay empty even after the call (since it only involves OneWay operations) and unfortunately in the present CTP drop I found it impossible so far to be filled.

 

CloudServiceBusSend System.ServiceBus.Workflow.sbSend with ConnectionMode RelayedDuplexSession

Let's look at a slightly different configuration for this activity

<ns0:CloudServiceBusSend x:Name="cloudServiceBusSend1" URL="sb://connect.biztalk.net/services/andreaserben/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" />

with the properties being like

 

ServiceBusSendRelayedDuplexSession

ConnectionMode

As mentioned "RelayedDuplexSession" will theoretically allow a response, but there seems to be a bug in the present version and the response will get lost. This example interacts with the sample Program Files\Microsoft BizTalk Services SDK\Samples\Communication\GettingStarted\Echo.

URL

This is the URL of the ServiceBus address you are trying to reach in this case contains your username because this is how the "Echo" sample sets up its endpoint.

Action

Again nothing really surprising. It is best understood though when looking at the contract of the corresponding sample

[ServiceContract(Name = "EchoContract", Namespace = "http://samples.microsoft.com/ServiceModel/Relay/")]

public interface EchoContract

{

    [OperationContract]

    string Echo(string text);

}

 

Again it is Namespace+Name+Operation. (Note that this is not a one-way operation now.)

Body

<Echo xmlns='http://samples.microsoft.com/ServiceModel/Relay/'> <text>Original</text></Echo>


The outer element is named "Echo" like the operation that is going to be called on the interface with the namespace "http://samples.microsoft.com/ServiceModel/Relay/" of the ServiceContract. Then there is the parameter "text" with its value "Original".

Response

The response is empty, here you would expect your value to show up that you get back from the ServiceBus as return values, however, it does not seem to work at the present time.

 

CloudHttpSend System.ServiceBus.Workflow.httpSend

Again look at the sample at Program Files\Microsoft BizTalk Services SDK\Samples\Workflow\GettingStarted  and this time find the activity cloudHttpSend1.

The activity corresponds to the following Xoml-fragment:

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

Select the activity and look at its properties:

CloudHttpSendGet

 

URL

The address of the resource you want to access. The WorkflowValidator will allow the Uri-Schemes "http", "https" and "ftp". The URL has to be given in absolute format. (The actual access is done through HttpWebRequest).

 

Method

GET

Allowed values are "GET" and "POST".

RequestContentType

This only is being considered for the "POST"-method. An example would be "text/plain".

Request

This is only being considered for the "POST"-method. The string here will be written to the RequestStream of the HttpWebRequest by interpreting the the String as UTF8-encoded through Encoding.UTF8.GetBytes(REQUESTSTRING).

StatusCode

The StatusCode that the HTTP-Request receives will appear here after the call was being made.

Response

The "Response" property will contain the content of the ResponseStream of the HttpWebRequest interpreted as UTF8-encoded.

 

There is really not much more to it, I will however try to give an end-to-end example that utilizes this activity in a bit more meaningful manner.

 

CloudHttpReceive System.ServiceBus.Workflow.httpReceive

CloudHttpReceive System.ServiceBus.Workflow.httpReceive is tough to impossible to figure out with the given documentation as to date.

Let me first quote the documentation:

CloudHttpReceive: This activity receives an HTTP request at the URI http://workflow.biztalk.net/servicesHttp/<UserName>/workflows/<WorkflowTypeName>/instances/
<InstanceId>/<ActivityName> and returns a response. The HTTP listener uses Identity service access control and the operation name "Send"

Properties

  • Response: the HTTP content to send in response to the HTTP request
  • StatusCode: the HTTP status code to return in response to the HTTP request
  • Request: the content of the received HTTP request. This property is set by the activity when a request is received.

It sounds easier than it is.

This is the smallest possible workflow utilizing this activity:

MinimalHttpReceiveWf

and its corresponding 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:CloudHttpReceive Request="{x:Null}" x:Name="cloudHttpReceive1" StatusCode="0" Response="Hello" />

</SequentialWorkflowActivity>

 

A closer look of the Xoml-fragment of that very simple cloudHttpReceive:

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

It corresponds to those properties:

CloudHttpReceiveHello

 

Request

This property will contain the string you received in the Request-Stream interpreted as UTF8-encoded.

Response

What you set here will be returned to the caller.

StatusCode

This is the statuscode that you will send to the caller.

 

Log in to BizTalk Workflow Services and create a type named "MinimalHttpReceive" with that Xoml definition and click "save changes".

CreateMinimalHttpReceive

Note that you did not need to provide a rules definition.

Create an instance of that type and start it and look at your instance manager:

StartMinimalHttpReceive

Note the GUID of the workflow instance: 7bf7a7c4-82f5-4935-9c03-62ba1e7991aa
This will vary for you and you will have to remember your own GUID.

Now start the workflow instance and go to this URL in your browser:

http://workflow.biztalk.net/servicesHttp/YOURUSERNAMEHERE/workflows/MinimalHttpReceive/instances/YOURGUIDHERE/cloudHttpReceive1

If your browser does not display the complete URL, here it is again as a link:
http://workflow.biztalk.net/servicesHttp/YOURUSERNAMEHERE/workflows/
MinimalHttpReceive/instances/YOURGUIDHERE/cloudHttpReceive1

You should see something like this:

HttpReceiveBrowserLogin

Now you need to log in with the username and password (or respective Information Card) that you used to create the workflow type and instance.

Then you should see something like that:

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Hello</string>

Your workflow should have been changed to "Completed" state.

Now what is interesting here is that you do not receive a pure string "Hello" but actually an XML serialization representation of the object string with content "Hello".

 

Sending to CloudHttpReceive System.ServiceBus.Workflow.httpReceive programmatically

Ok, but now how can you do this programmatically? As you might realize, the challenge is the need to "log in". Unfortunately you cannot just use NetworkCredentials to log in but need to make a little detour.

At first you need to retrieve a token for logging in. You can find an example how to do it in this sample: Program Files\Microsoft BizTalk Services SDK\Samples\Workflow\ExploringFeatures\CreateWorkflowAPIs in Program.cs method GetTokenGrantingToken.

I took the liberty of modifying the method slightly and to remove any dependency to the SDK:

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);

    }

}

It is pretty straightforward. Basically it is a simple HTTPS request to this URL:

Now, the string that you received represents your token and you need to attach that as a header of type "X-MS-Identity-Token" to your WebHttpRequest you want to send to your workflow instance.

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

This is the code-snipped for a method encapsulating the whole request:

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();

        }

    }

}

You can combine both like this:

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);

}

And an example call to the same workflow instance that we accessed above through the browser would be:

string userName = "YOURUSERNAME";

string password = "YOURPASSWORD";

string workflowTypeName = "MinimalHttpReceive";

string activityName = "cloudHttpReceive1";

string instanceId = "7bf7a7c4-82f5-4935-9c03-62ba1e7991aa";//YOUR GUID HERE INSTEAD

string requestBody="Text to send to cloud";

string result = CallHttpReceiveActivityWithLogin(

                        requestBody, userName, password,

                        workflowTypeName, activityName,

                        instanceId);

 

For the sake of completeness here the complete code together:

using System;

using System.Text;

using System.Net;

using System.IO;

 

namespace SendToHttpReceive

{

    class Program

    {

        static void Main(string[] args)

        {

 

            string userName = "YOURUSERNAME";

            string password = "YOURPASSWORD";

            string workflowTypeName = "MinimalHttpReceive";

            string activityName = "cloudHttpReceive1";

            string instanceId = "7bf7a7c4-82f5-4935-9c03-62ba1e7991aa";//YOUR GUID HERE INSTEAD

            string requestBody="Text to send to cloud";

            string result = CallHttpReceiveActivityWithLogin(

                                    requestBody, userName, password,

                                    workflowTypeName, activityName,

                                    instanceId);

            Console.WriteLine(result);

            Console.ReadLine();

        }

 

 

        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 that against a running workflow instance will (not surprisingly) result in this output:

CallMinimalHttpReceiveActivityOutput 

 

In a future post I will try to show an "end-to-end"-demo that actually integrates a lot of what was shown previously and achieves bidirectional communication with a service in the cloud and has your workflow doing something that the service requests.


Posted Oct 02 2008, 05:14 AM by Andreas Erben
developers.de is a .Net Community Blog powered by daenet GmbH.