Ever wanted to send a strongly typed message over a Windows Azure Storage Queue? Here is a neat little trick that helps you do that a bit better than the string payload the CloudQueueMessage carries.
Note: The code and artifacts for this post are available on the AzureContrib site or with your friendly call to the nuget package manager: PM> Install-Package AzureContrib.
The Windows Azure Storage Queue Service is a light weight easy to use queuing service used for “reliable, persistent messaging between Web Roles and Worker Roles”. It can be used for so much more like serializing and breaking down work tasks to smaller more manageable bits.
It’s light weight alright. A bit too light weight.
The problem is that the CloudQueueMessage sent through the queue has a payload of a, up to 8KB, string. So if you send a message as a string you have manually to agree with the receiving end on the format for this message. There is no format enforcement or verification and no strongly typed objects passed as the message. This is a bit of a nuisance in my book so let’s see what we can do to mitigate this!
Rather than adding the message body as a string we want to serialize a small object with the data and send that over the queue. Since we only have 8KB of space I opted to use the compact JSON format by way of the System.Web.Script.Serialization.JavaScriptSerializer. When we add a CloudQueueMessage to the CloudQueue we serialize our payload as JSON. On the receiving end we deserialize back to an object. One great advantage of using JSON is that the sender and receiver does not have to agree on type only on format. Why this is positive I will show you below.
Here is a sample usage:
namespace AzureContrib.WindowsAzure.Tests.Integration.StorageClient
{
[TestClass]
public class QueueIntegrationTests
{
public class A
{
public string Foo { get; set; }
public int Bar { get; set; }
public bool Baz { get; set; }
public C Qux { get; set; }
}
public class B
{
public stringFoo { get; set; }
public int Bar { get; set; }
public bool Baz { get; set; }
public D Qux { get; set; }
}
public class C
{
public DateTime Bar { get; set; }
}
public class D
{
public DateTime Bar { get; set; }
public string Baz { get; set; }
}
[TestMethod]
public void Use_Queue_with_JSON_serialized_messages()
{
var a = new A
{
Foo = "something",
Bar = 42,
Baz = true,
Qux = new C
{
Bar = DateTime.Now
}
};
var cloudStorageAccount = CloudStorageAccount.DevelopmentStorageAccount;
var cloudQueueClient = new CloudQueueClient(cloudStorageAccount.QueueEndpoint, cloudStorageAccount.Credentials);
var queue = cloudQueueClient.GetQueueReference("queuename");
queue.CreateIfNotExist();
CloudQueueMessage queueMessage = a.AsMessage();
queue.AddMessage(queueMessage);
CloudQueueMessage cloudQueueMessage = queue.GetMessage(TimeSpan.FromSeconds(2));
queue.Delete();
B b = cloudQueueMessage.Payload<B>();
Assert.AreEqual("something", b.Foo);
Assert.AreEqual(42, b.Bar);
Assert.AreEqual(true, b.Baz);
Assert.IsNotNull(b.Qux);
Assert.IsInstanceOfType(b.Qux.Bar, typeof(DateTime));
Assert.IsNull(b.Qux.Baz);
}
}
}
We are sending a small message over the queue by means of an instance of the fictive class A. All we need to do is call a.AsMessaage() to get the CloudQueueMessage to add to the queue.
On the other end (here just on the next code line but still sent through the Development Storage Emulator) we call cloudQueueMessage.Payload<B>() to get the data deserialized back to a strong type B. But wait a minute ‘B’? Didn’t’ we send an ‘A’? As I hinted at above JSON does not care about the concrete type it serializes to in other respects than the names and types of the properties it deserializes. You can even send A with a property of type C to B with a property of type D where C and D are not the same without problems. In fact C is a class and D is a struct! If you screw it up too badly you will get a System.FormatException.
OK so it’s not exactly very strongly typed is it? ;~) Also I only promised a trick that “helps you do this a bit better! The code that does this is really small as you will see below. And after that I summarize what I feel is the benefit of this.
If you look at the message being sent on the wire in the REST call to the Windows Azure Storage Queue Service using Fiddler2 this is what you’ll find:
POST http://127.0.0.1:10001/devstoreaccount1/queuename/messages?timeout=90 HTTP/1.1
x-ms-version: 2009-09-19
User-Agent: WA-Storage/6.0.6002.18312
x-ms-date: Wed, 31 Aug 2011 05:25:13 GMT
Authorization: SharedKey devstoreaccount1:izFvglhwnqlAchjW1P2TgKXvG6jeN5JJNSN1Ql3BwZc=
Host: 127.0.0.1:10001
Content-Length: 161
<?xml version="1.0" encoding="utf-8"?><QueueMessage><MessageText>eyJGb28iOiJ0ZXN0aW5nIiwiQmFyIjowLCJCYXoiOmZhbHNlLCJRdXgiOm51bGx9</MessageText></QueueMessage>
I’m only kidding, the <MessageText> is Base64 encoded. The decoded version looks like this:
<MessageText>{"Foo":"testing","Bar":0,"Baz":false,"Qux":null}</MessageText>
Even if the receiving end does not decode the message automatically it is actually quite readable and useful.
Here is all the code you need to make the sample above work:
using System.Web.Script.Serialization;
using Microsoft.WindowsAzure.StorageClient;
namespace AzureContrib.WindowsAzure.StorageClient
{
public static class CloudQueueMessageExtensions
{
public const int MaxMessageBuffer = 8 * 1024;
private static JavaScriptSerializer JavaScriptSerializer
{
get
{
return new JavaScriptSerializer { MaxJsonLength = MaxMessageBuffer };
}
}
public static T Payload<T>(this CloudQueueMessage cloudQueueMessage)
{
var toDeserialize = cloudQueueMessage.AsString;
return JavaScriptSerializer.Deserialize<T>(toDeserialize);
}
public static CloudQueueMessage AsMessage<T>(this T instance)
{
var serialized = JavaScriptSerializer.Serialize(instance);
return new CloudQueueMessage(serialized);
}
}
}
Just a few lines really which is nice. Both extension methods use a JavaScriptSerializer. .AsMessage<T>() returns the CloudQueueMessage we want to send over our CloudQueue. .Payload<T>() returns the actual payload as the message serialized back into something you can nicely use in your code.
We win two things using this approach:
- It is easier to agree on format if it has some well known (JSON) structure between the sending party and the receiving party.
- In our code we deal only with strongly typed instances and not at all with any serializing and deserializing some message as a string having a particular format.
Only thing you need to be careful with is not to serialize a playload into more than 8KB. But you always have to do that!
HTH!
Cheers,
M.
posted @ Tuesday, August 30, 2011 9:13 PM