[You should read Part 1 of this little series before you proceed reading this one.]
In this 2nd part I am extending the simple messaging example of Part 1 by adding some explicit WS-Addressing trickery. Addressing is so fundamental that its properties are baked right into the Headers collection of the Indigo Message. Even though there are (and I will eventually show) much easier ways to do request/reply management that hide most of what I am doing here very conveniently under the covers, I’ll give you an example of how you can send messages to a service and then instruct the service to explicitly reply back to an endpoint you provide. To make it a little more fun, I am setting up two alternate reply endpoints and have the service come back to them in turns. The Program host class is identical to the one the previous example, so I’ll show only client and service code along with the config.
The server-side code below grew a little bit as you can see. Now, there is a IGenericReplyChannel that is the contract for the replies. It looks suspiciously like the client-side’s IGenericMessageChannel and it is indeed a copy/paste clone of it. I just didn’t want to share code between client and server side. The Receive method has changed insofar as that it no longer prints the message to the console, but now creates a reply and sends the reply to the endpoint that the client indicates through the (WS-Adressing-) ReplyTo header of the incoming message.
To do this, the service constructs a ChannelFactory< IGenericReplyChannel>, using the endpoint address indicated in the incoming message’s ReplyTo header and getting the binding information from the “replyChannel” client setting in the config file shown further down. (Note that this is a bit simplistic, because it assumes that the ReplyTo EPR uses a compatible binding. There is a brilliant way to fix this, but … later). Then, the message body of the incoming message is read into an XmlDocument and if this was a real application, it would likely do something here. For now, we just leave the content as it is and punt it back out.
To construct the reply message, I don’t use the CreateReplyMessage() method provided on the Message class, simply because it doesn’t have an appropriate overload to deal with an XmlReader in the same way as Message.CreateMessage() does. I am sure that’s a minor oversight that’s just a problem with my particular Indigo build. Creating a reply is quite simple, though. All I need to do is to copy the incoming message’s MessageID value into the RelatesTo.Reply property of the outgoing message. For simplicity, I don’t check whether that header is present and set, which I really should do, because there is no actual contract or policy in place (for now). Once I have the reply constructed, just copying the incoming body into it, I send it out through a channel (“proxy”) constructed by channel factory.
Having a reply-enabled server-side, we can now get to the juicy part: the client. Since we now need to listen for replies, the client has to expose a reply-endpoint and therefore also act as a server. (That is the reason why “endpoint” is preferred in service-land rather than the “client”/”server” nomenclature). Therefore, I define a IGenericReplyEndpoint contract (no surprises there) and implement that in GenericReplyEndpoint. To make the example a bit more fun, the constructor of that service class takes two arguments: The client argument refers to an instance of the Client application class and epName gives the service instance (!) a name. The client reference is used to let the client application know how many messages were already received so that it can shut down, once the expected replies for all sent messages have come back. The notification about received messages is done inside the ReceiveReply method, which otherwise just writes the message body to the console.
Unlike the previous example, this service implementation isn’t used directly. Instead, I derive two subclasses from it: ReplyEndpointA and ReplyEndpointB. These two classes each implement a constructor that passes “A” and “B”, respectively, for the epName argument to the base-class and pass-through the client argument. In case you wonder how the ServiceHost could possibly construct instances of these service classes, not knowing the appropriate parameters to pass to them: Instances of these two classes are pre-constructed and fed into the service host as singletons as you will see below.
The Client application class is a bit more intricate than the previous version, but there is no rocket science there. I have a counter for the number of messages received and a ManualResetEvent that is getting signaled whenever the number of received messages matches (or exceeds) the number of sent messages. That happens in the MessageReceived method, which is called by the service singletons. The class also has a UniqueIDGenerator, which is an Indigo-supplied class that lets me generate values for the MessageID header that is required alongside using ReplyTo.
In the SendLoop method, I now create two service host instances that shall receive the replies to messages I send; one of type ServiceHost<ReplyEndpointA> and one of type ServiceHost<ReplyEndpointB>. Each of these hosts receives an instance of its service type as a construction argument. Doing so causes the service host to operate in singleton mode, meaning that it will not create new service instances out of and by itself, but rather use only the exact instance supplied here. In the actual send loop, I alternate (i%2==0) between those two service hosts and invoke SendMessage passing the channel factory (not the channel as in the previous example) and the chosen ServiceHost instance.
In SendMessage, I do a few simple things and only one not-so-obvious thing. A new message is constructed as the first step and loaded with an action and the body content. Then I grab the destination address from the channel factory, which sits in the channel factory’s Description.Endpoint.Address property and assign that to the message’s To header. The MessageID is set to a new unique identifier created using the messageIdGenerator. All that is pretty straightforward. Not immediately clear might be what I am doing with the ReplyTo header:
Once a service host is Open, it’s bound to set of endpoints and is actively listening on those endpoints using “endpoint listeners”. I am writing “set of endpoints”, because a service might have several. Each service can expose as many endpoints as it likes; each with a separate binding (transport/behavior/address) and each with a separate contract. There are puzzling special cases, of which you’ll see at least one in this series, where a service listens and properly responds to a contract type that is nowhere to be seen on the actual service implementation. The active endpoints sit on the EndpointListeners collection.
For simplicity (again, this is a bit naïve, but serves the purpose for the time being) and to obtain a ReplyTo address to pass to the service I am sending the message to, I reach into that collection and grab the first available endpoint listener’s address. What I should be doing here is to check whether that listener is indeed the one for the IGenericReplyEndpoint contract and whether I can find one with a binding that is mostly compatible with the one the outbound channel uses. The latter selection would be done to make sure that if I send out via “net.tcp” and I expose a “net.tcp” endpoint myself, I would preferably pass that endpoint instead of a possible “http” endpoint I might be listening on at the same time. Once ReplyTo is set, I send the message out.
continuation from above …
What’s left is the matching configuration for this. The mechanics of how the configuration maps to the classes and instances are largely the same as in the simple messaging example. A small difference is that the replyChannel client configuration has no target address attribute, because that one is always supplied via ReplyTo (refer to the GenericMessageEndpoint’s Receive method above to see how that is wired up). Oh, yes, and I switched it all to http transport in case you don’t notice. TCP would work just as well, but I felt like I needed a little change. The assumed assembly name for this sample is “SimpleAddressing”, of course.
<?xml version="1.0" encoding="utf-8" ?>
The output of the sample is predictable, isn’t it? The replies come back in sequence, alternating between the two reply services “A” and “B”.