In this article I will discuss extending the RPC sample from the previous article to include some error handling incase the server could not process your message. In this scenario we want to achieve the following:
- Setup Windows Azure Service Bus to support this sample
- Define a schema for our message and generate the .net types which represent it
- Create a message handler to handle the message
- Start the host to listen for messages
- Create a client application to send the message
- Have the server reject the message because of some error condition
- The client will get a response message via the response queue
- The framework will convert that response message to an exception and throw it for the client
Let's get on with things.
Setting up Azure Service Bus
In this sample we will use two queues. The first queue will be one which will be listened to by the server side host. This queue will be called Sample2-RPCRequest. You can create all of the appropriate queues for the sample by importing the Appfx-Servicebus-Samples_Entities.xml file which comes with the source code.
There is nothing particularly special about the Sample2-RPCRequest queue.
We will also create another queue called Sample2-RPCResponse. This queue is used when responses are sent from the server back to the client. One thing to note about this queue is that it has the "Requires Session" property set to true. This is so that the queue can receive the responses for multiple requests and have different listeners concurrently listening to the queue for their own response by using a session (note this is taken care of by the framework).
Creating your schema & message
When creating our message with AppFx.ServiceBus we prefer the technique of creating a schema to represent your message. This means we have a good contract for the message which we can then create classes in most programming languages to represent these schemas. This also allows us to serialize an object based on this schema to numerous formats such as XML or JSON.
One convention which we do use which is quite important is borrowed from BizTalk world where we use the combination of xml namespace + root element name to uniquely identify a message. The AppFx.ServiceBus framework will use this to be able to work out how to handle a message and how it can be serialized and deserialized.
In this example there are two messages (the request and the response), the message types are
http://contracts/v1.0#GetCustomerRequest
http://contracts/v1.0#GetCustomerResponse
Note: Please note the # character used to separate the namespace and root element name
In the sample we will begin in the Contracts c# project. Here we have created a schema in the schemas folder called contracts_v1_0.xsd and we have populated this with a simple definition of a type shown below:
<?xml version="1.0" encoding="utf-16"?>
<xs:schema
id ="Contracts_v1_0"
xmlns="http://contracts/v1.0"
elementFormDefault="qualified"
targetNamespace="http://contracts/v1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="GetCustomerRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="MessageID" type="xs:string" />
<xs:element name="CustomerId" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GetCustomerResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="CorrelationId" type="xs:string" />
<xs:element name="CustomerId" type="xs:string" />
<xs:element name="CustomerName" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Now you would be able to give this schema to other parties who should be able to create classes from it and use them, but in our very simple demo we will next generate a class from this schema and keep it in the contracts assembly which will be shared either side of the queue just to keep it simple.
In the overridebuild.targets file you will see how we generate the class using svcutil which will produce a class in our contracts assembly.
<Exec Command='"$(SvcUtilPath)\svcutil" /dconly Schema\Contracts_v1_0.xsd /language:C# /serializable /directory:Types /namespace:http://contracts/v1.0,Contracts.Types.Contracts_v1_0' />
The contracts assembly is now done and we have a message which we can use in this sample.
Creating your message handler
In the server project in the sample we will hold our logic to handle the message and do whatever our application needs to with that message. To begin with we will create a class called GetCustomer_v1_0_Handler.
This class will implement the IHandleMessage interface so that it can handle messages and work in the AppFx.ServiceBus framework.
In addition to handling this interface we also need to add the MEF (managed extensibility framework) attributes so that during start up the host will identify this class as a message handler and workout what type of messages it handles.
The below snippet shows you what the class looks like when decorated with these attributes:
[Export(typeof(IHandleMessage))]
[ExportMetadata("MessageType", XsdMessageType)]
public class GetCustomer_v1_0_Handler : IHandleMessage
{
public const string XsdMessageType = "http://contracts/v1.0#GetCustomerRequest";
We now have a class which the framework will identify as handling the GetCustomerRequest message we defined earlier. I assume you have probably already read the earlier samples explaining what the different parts of the IHandleMessage interface relate, but if not please refer to the codeplex documentation page for more info.
In the message handler for our GetCustomer message in this sample we will only be handling two way messages so you will see the implementation in that class to look like below.
public object HandleRequestResponse(object message)
{
var request = message as Contracts.Types.Contracts_v1_0.GetCustomerRequest;
if (request == null)
{
Logger.Instance.Warn(string.Format("{0} recieved a null message", this.GetType().FullName));
throw new ApplicationException("Null message recieved")
{
RetrySupported = false
};
}
Logger.Instance.Info(string.Format("Message Id:{0}, Type={2} Handling Get Customer Request:{1}",
MessageProcessorContext.Current.MessageId, request.MessageID,
this.GetType().FullName));
throw new ApplicationException("I am throwing an error because this message handler is grumpy")
{
RetrySupported = false
};
}
For the purposes of the sample we will simply record that we received the message and then throw an error. To throw an error for RPC simply throw an AppFx.ServiceBus.Exceptions.ApplicationException. The exception allows you to specify if you wish to retry on the receive side. This is unlikely in an RPC scenario so you are most likely to specify false and return the framework will push an error response to the client.
Our message handler is now complete and ready to be used.
Configuring the receive side Host
The receive side configuration is exactly the same as for sample 2 which introduced the RPC pattern. Ill include the code snippets below but if you want to see more detail about the how and why of the configuration please refer to sample 2.
In the config file we have firstly declared the sections for AppFx.ServiceBus and also for log4net. As shown below:
Next we have the configuration for any connection strings we wish to use for connecting to the Windows Azure Service Bus namespace. In this case we only have one because most of the samples will run in the same namespace. The below picture shows you the connection string.
Next we will implement the AppFx.ServiceBus configuration. The main part to talk about in this sample is the listeners within the appfx.servicebus.receiver section pictured below.
<appfx.servicebus.receiver
defaultMaxRetries="3"
defaultMessageHandler="">
<listeners>
<add name="Default" connectionStringName="ServiceBusConnection" messagingEntity="Sample2-RPCRequest" noThreads="3"/>
</listeners>
<messageHandlers>
<!-- MEF used by default but this allows overriding of message handlers -->
</messageHandlers>
</appfx.servicebus.receiver>
Starting the receive side Host
To start the receive side host simple double click the AppFx.ServiceBus.Hosts.Console.exe application in the ..\Library\AppFx.ServiceBus.Hosts.Console folder.
Because you compiled the server project which copied over our contracts.dll containing our message types, the server.dll containing our message handlers and the AppFx.ServiceBus.Hosts.Console.exe.config file containing our configuration, you should see the console application will fire up and display some trace messages to show you that multiple listener instances are now polling the queue for messages.
The console application will look like the below:
The console is now waiting to process messages.
Configuring the client
In this sample you now need to refer to the client c# project in the solution. In this project you will see the app.config file which contains our configuration for AppFx.ServiceBus. The below picture shows this:
<configSections>
<section name="appfx.servicebus.client" type="AppFx.ServiceBus.Client.Configuration.ServiceBusConfiguration, AppFx.ServiceBus.Client"/>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<connectionStrings>
<add name="ServiceBusConnection" connectionString="Endpoint=sb://****.servicebus.windows.net/;SharedSecretIssuer=owner;SharedSecretValue=****
</connectionStrings>
<appfx.servicebus.client>
<clients>
<add name="Default" connectionString="ServiceBusConnection" sendToMessagingEntity="Sample2-RPCRequest" responseQueue="Sample2-RPCResponse" serializationFormat="Json"/>
</clients>
</appfx.servicebus.client>
The connection strings are the same as on the receive side where you will use the Windows Azure Service Bus connection string format.
In the appfx.servicebus.client element you can now add a number of clients. These clients can be referenced from your code to provide settings for sending a message. The example above specifies a client which will point to the Sample1-HelloWorld queue and send messages using JSON. In the default client you can see that the configuration is very similar to the one way message sample but the key difference is that we have supplied a responseQueue. This is the response queue which the framework will check for your RPC response.
Sending the message from the client
In the client application there is a windows form with a button which will allow you to test sending a message. The code behind this button is as follows:
private void button1_Click(object sender, EventArgs e)
{
var message = new Contracts.Types.Contracts_v1_0.GetCustomerRequest()
{
MessageID = Guid.NewGuid().ToString(),
CustomerId = "1"
};
var start = DateTime.Now;
var client =
new AppFx.ServiceBus.Client.SessionRequestResponseMessagingClient<Contracts.Types.Contracts_v1_0.GetCustomerRequest, Contracts.Types.Contracts_v1_0.GetCustomerResponse>("Default");
try
{
var response = client.SendRequestResponse(message);
var duration = DateTime.Now - start;
MessageBox.Show(string.Format("A successful response was recieved!, latency={0}ms", duration.TotalMilliseconds));
}
catch (AppFx.ServiceBus.Exceptions.ApplicationException appEx)
{
var duration = DateTime.Now - start;
MessageBox.Show(string.Format("Error recieved: {1}, latency={0}ms", duration.TotalMilliseconds, appEx.Message));
}
}
You can see that you will use the Generic MessagingClient class where you will tell it the two message types you wish to work with. You will also supply the client name which refers to your settings in the configuration file.
Next you use the SendRequestResponse method, and that will dispatch the message onto the queue.
When you click the button the message will be received by the server component. The component will throw an error as we coded earlier in the message handler. The error will come back across the response queue and the client side framework will throw an AppFx.ServiceBus.Exceptions.ApplicationException to the calling code. The above example shows how the client has caught this error and displayed it to the user.
When the message is flown over the queues or topic/subscriptions the framework will include an IsError property on the response message so the client framework waiting for the response message can work out the message is an error and the Service Bus message body will contain the error message.
In the sample the client will display a message like the following:
On the console application side you will spot that very soon after the message is submitted the console text will change to show that your message handler has executed. The console text will also show in red the error being logged on the server side and in yellow that the error has been returned to the client.
Summary
Hopefully this article shows you how much simpler AppFx.ServiceBus can make it for you to use Windows Azure Service Bus with the RPC pattern and including the handling or errors.