One of the core specs that WCF implements is WS-Addressing.
At the highest level, WS-Addressing essentially defines three things.
First, WS-Addressing defines a core address construct called Endpoint Reference. An Endpoint Reference provides a mechanism for identifying a Web service endpoint. In the WCF object model, we call this thing an EndpointAddress.
Second, WS-Addressing defines a set of Message Addressing Headers. These headers -- To, ReplyTo, FaultTo, MessageID, RelatesTo, etc. -- are useful for defining (a) where an initial message is going, (b) how to send messages in reaction to that initial message and (c) how to tell that those reactionary messages are related to the initial message.
Third (though it's not usually called out separately), WS-Addressing defines an Action header. This essentially defines the intent of the Message. Typically, if you want to figure out how to process a Message, you use the action as your key.
We tend to think of the Message Addressing Headers primarily as plumbing. For instance, if you want to correlate a request message with a response message over TCP or UDP, you need the ReplyTo, MessageID, and RelatesTo mechanisms. If you want to figure out to what endpoint you should dispatch a message, you look at the To. Etc. Our runtime adds and inspects these headers to do things like request-reply correlation.
Sometimes, though, people end up wanting to do fancy things with addressing at the application layer.
For instance, one pattern is to send a one-way message to a lot of folks (e.g., using UDP multicast) and provide them with a mechanism for sending back one-way reply messages.
To do this in WCF, you just need to use OperationContext.
For instance, suppose I had the following two contracts:
[
ServiceContract]
interface
QueryContract
{
[
OperationContract(IsOneWay = true)]
void Query(string query);
}
[
ServiceContract]
interface
QueryCallbackContract
{
[
OperationContract(IsOneWay = true)]
void Response(string response);
}
The first contract (QueryContract) represents the one-way message that I want to send out. The second contract (QueryCallbackContract) represents the one-way message I expect in return.
Now, suppose you want to correlate a Query message with a Response message.
To do that, you'd first create up the appropriate channel / channel factory:
CustomBinding binding = new CustomBinding(new TextMessageEncodingBindingElement(), new HttpTransportBindingElement());
ChannelFactory<QueryContract> factory = new ChannelFactory<QueryContract>(binding, new EndpointAddress("http://destination/"));
QueryContract channel = factory.CreateChannel();
Next, you would wrap your call to Query() in an OperationContextScope. This essentially populates an OperationContext.Current.
Inside your using block, you could add the address of your local endpoint to the ReplyTo header as well as add a correlation ID:
using (new OperationContextScope((IContextChannel)channel))
{
OperationContext.Current.OutgoingMessageHeaders.ReplyTo = new EndpointAddress("http://callback/");
OperationContext.Current.OutgoingMessageHeaders.MessageId = new UniqueId();
channel.Query(
"foo");
}
After this, you would typically store off the Message ID you created, along with information about the request, in some thread-safe dictionary.
On the service-side, you might implement Query() as follows:
class
QueryService : QueryContract
{
public void Query(string query)
{
EndpointAddress replyTo = OperationContext.Current.IncomingMessageHeaders.ReplyTo;
UniqueId messageId = OperationContext.Current.IncomingMessageHeaders.MessageId;
CustomBinding binding = new CustomBinding(new TextMessageEncodingBindingElement(), new HttpTransportBindingElement());
ChannelFactory<QueryCallbackContract> factory = new ChannelFactory<QueryCallbackContract>(binding, replyTo);
QueryCallbackContract channel = factory.CreateChannel();
using (new OperationContextScope((IContextChannel)channel))
{
OperationContext.Current.OutgoingMessageHeaders.RelatesTo = messageId;
channel.Response(
"bar");
}
}
}
First, you would fish out the addressing headers that you set (ReplyTo, MessageId). Next, you would create a channel back to that ReplyTo. Next, you would create a new OperationContextScope and use that around the new channel, set the RelatesTo header to match the MessageId, and then send the response message.
(Note, you'd probably want to be slightly fancier than creating a new binding / channel factory for each operation.)
Lastly, back on the client-side, you would probably fish out the RelatesTo, go back to that dictionary you set on the initial request, and complete the outstanding operation.
class
QueryCallbackService : QueryCallbackContract
{
public void Response(string response)
{
UniqueId relatesTo = OperationContext.Current.IncomingMessageHeaders.RelatesTo;
// use relateTo to correlate to initial request
}
}
It should be noted that in a real application, you might want to be more sophisticated about deciding whether or not to trust the incoming ReplyTo before creating a channel to it.
So there you go.
Interestingly, I just realized that utilizing the ReplyTo header has a nice property over utilizing a custom header as you can use EndpointAddress directly (and we will pick up the right addressing version for free). If you were to define a MessageHeader via CreateMessageHeader(...), you would have to pick a serializable type of EndpointAddress (either EndpointAddressAugust2004 or EndpointAddress10) or write a custom sub-class of MessageHeader that grabbed the ambient Message Version and used that when reading / writing the EndpointAddress.
But I digress.
Edit (2006.03.21): Fixed Typo -- Changed QueryContract --> QueryCallbackContract.
Posted
Feb 01 2006, 01:08 PM
by
mike-vernal