Connecting BizTalk with a mobile app using Azure Appfabric ServiceBus Queues, REST and WCF

| October 24, 2011 | 6

The Azure Appfabric Service Bus 1.5 released in September had a feature I really have been looking forward to, durable queues and topics (pub/sub). A cloud based MSMQ ‘like’ queue can help both enterprise-enterprise scenarios as well as in connecting mobile applications with on-premise or cloud-hosted services in a reliable way.

Appfabric Service Bus queues and topics support three programming models:

 

  • .NET API
  • REST API
  • WCF Bindings

While the.NET API offers great control and simplicity, the REST API is supported on almost all platforms. Finally the WCF binding allows .NET programmers not having to learn yet another programming model.

In BizTalk the easiest way to utilize queues and topics are by the WCF binding NetMessagingBinding. Although we surely could use the REST API, just getting the security token out-of-band and keeping it updated, in the WCF-pipeline or elsewhere, would make it a bit cumbersome. NetMessagingBinding will automatically do the token acquisition before accessing the queue/topic.

If both sender and receiver use WCF things are pretty straight forward but if different programming models are used there are some things that need to be addressed.

Scenario

This scenario consists of an Android device which will use REST to send messages to a Service Bus queue then picked up by BizTalk using WCF.

I want the the client to be able to send any data (XML, text or binary) to the same Receive Location in BizTalk.

The Service Bus authorization is claims-based and the Azure Appfabric ACS (Access Control Service) is automatically configured when the Service Bus namespace is created. For simplicity I use ACS Service identities for both BizTalk and Android. In real world, the Android application would probably use something else such as an external IdP together with ACS. More on that in a later post!

Setup

Appfabric 1.5 SDK does not add entries to the machine.config as the previous versions did and we need those in BizTalk. I copy/pasted the necessary lines from http://msdn.microsoft.com/en-us/library/windowsazure/hh148155.aspx. Make sure to add them both to 32 & 64 bit machine.config!

Create a new one-way Receive Location hosted ‘in-process’ using the WCF-Custom adapter.

To be able to receive anything the client sends in the body of the HTTP POST we have to use CustomBinding instead of the pre-configured NetMessagingBinding. This allows us to replace the default TextMessageEncoding binding element with a WebMessageEncodingElement instead. Remember to move it above the NetMessagingTransport element after it is added_._

WebMessageEncoding can output XML, JSON or binary data and it defaults to binary. It is possible to control this behavior using a custom WebContentTypeMapper but in this scenario it works fine with the default.

On the general tab enter the address of the already existing queue using the following format :  “sb://{servicebusnamespace}.servicebus.windows.net/{Queue}”.

Authentication and authorization

To receive messages from the queue the authorization will require a claim of type ‘net.windows.servicebus.action’ with value ‘Listen’.

The pre-configured ACS Service Identity ‘owner’ has this claim but it is considered more secure to create a separate identity with a shared symmetric key or an X509 certificate as identification. If you use something different than ‘owner’ do not forget to update the Rule Group to create the claim.

To specify how to authenticate against Service Bus add a TransportClient Endpoint Behavior and specify the identity and shared key to use.

Addressing

By default the NetMessageTransport assumes WCF and that WS-Addressing “To” is present in the message. So if you make a HTTP POST with a non-SOAP body to the queue the WCF trace will show that the adapter throws this exception:

The message with To ” cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher. Check that the sender and receiver’s EndpointAddresses agree.

To fix this we need to insert the more forgiving Address Filter such as “System.ServiceModel.Dispatcher.MatchAllMessageFilter”. This must be done in a custom behavior extension.

namespace Kramerica.Extensions.WCF
{
    class MatchAllMessageFilterEndpointBehaviorExtensionElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get
            {
                return typeof(MatchAllMessageFilterEndpointBehavior);
            }
        }

        protected override object CreateBehavior()
        {
            return new MatchAllMessageFilterEndpointBehavior();
        }
    }

    class MatchAllMessageFilterEndpointBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.AddressFilter = new System.ServiceModel.Dispatcher.MatchAllMessageFilter();
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }
}

Add this BehaviorExtensionElement to machine.config (32 & 64-bit) in the behaviorExtensions section.

<add type="Kramerica.Extensions.WCF.MatchAllMessageFilterEndpointBehaviorExtensionElement, Kramerica.Extensions.WCF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1234567890123456"/>

Go to the Receive Location and add this in the EndpointBehaviors.

BizTalk message source

If you would make a REST HTTP POST to the queue now BizTalk would publish the message from WebMessageEncoding and it would look something like this:

Qk3WAAAAAAAAADYAAAAoAAAA==

This is Base64 encoded data and to fix that we just need to change what BizTalk publishes to the message box.

Go to the Messages tab and enter /*[local-name()=’Binary’] as the Body path expression and Base64 as Node encoding.

Now the WCF adapter will decode and publish everything inside to the messagebox where a subscribing orchestration or send port can use the message.

Client

To test it I would use a REST capable test-client such Fiddler, SoapUI or the test client in WCF WEB API

Make a HTTP POST to “https://{servicebusnamespace}.servicebus.windows.net/{QueueName}/Messaging” with the token received from ACS in the authorization http header.

To test the mobile scenario i made a heavily simplified Android application that can send an image from the phones image gallery to the Azure Appfabric Service Bus Queue.

public void button_SendImage_OnClick(View view) {
    	if (selectedImage!=null)
    	{
    		try {
    			getTokenFromACS();
    			sendImageToHQ();
    		} catch (Exception e) {
    			Toast.makeText(getApplicationContext(), e.getMessage(),1).show();
    		}
    	}
    }

	private void getTokenFromACS() throws ClientProtocolException, IOException {

		HttpClient httpclient = new DefaultHttpClient();
		HttpPost httppost = new HttpPost(ACSBASEADDRESS);

		List<NameValuePair> parameters = new ArrayList<NameValuePair>(3);
		parameters.add(new BasicNameValuePair("wrap_scope", RELYINGPARTYADDRESS));
		parameters.add(new BasicNameValuePair("wrap_name", ISSUER));
		parameters.add(new BasicNameValuePair("wrap_password", ISSUERSECRET));

		httppost.setEntity(new UrlEncodedFormEntity(parameters, HTTP.UTF_8));

		BasicHttpResponse httpResponse = null;
	    httpResponse = (BasicHttpResponse) httpclient.execute(httppost);

		String response = null;
		response = EntityUtils.toString(httpResponse.getEntity());

		String[] tokenVariables = response.split("&");
		String[] tokenVariable = tokenVariables[0].split("=");
		authorizationToken = java.net.URLDecoder.decode(tokenVariable[1]);
	}

	private void sendImageToHQ() throws ClientProtocolException, IOException {
		String queueAddress = SERVICEADDRESS + QUEUENAME + "/messages?timeout=60";

		HttpClient httpclient = getSSLReadyHttpClient();
		HttpPost httppost = new HttpPost(queueAddress);

		httppost.setHeader("Content-Type", "image/jpg");
		httppost.setHeader("Authorization", "WRAP access_token="" + authorizationToken + """);

		ByteArrayOutputStream out = new ByteArrayOutputStream(100240);
		selectedImage.compress(CompressFormat.JPEG, 100, out);

		httppost.setEntity(new ByteArrayEntity(out.toByteArray()));
		HttpResponse response = httpclient.execute(httppost);

		if (response.getStatusLine().getStatusCode() == 201)
			Toast.makeText(getApplicationContext(), "Image sent to queue!", 1).show();
		else
			Toast.makeText(getApplicationContext(), "Image NOT sent to queue! Reponse was : " + response.getStatusLine(), 1).show();
}

Code handling image-resize and some basic Android stuff are not shown above.

In BizTalk we now get the binary representation of the image published to the messagebox and a simple FILE Sendport makes the image viewable. In a real world scenario, metadata about the image could go as custom http headers or similar.

The takeaway here is that we can use the same queue and BizTalk Receive Location no matter what client or Service Bus API (WCF, REST or .NET) is used on the other side!

comments powered by Disqus