Fancy Addressing in WCF

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
Filed under:

Comments

brad wrote re: Fancy Addressing in WCF
on 02-02-2006 5:40 AM
cool, but I don't get why the implementation of the Query operation wouldn't use the QueryCallbackContract...

ChannelFactory<QueryContract> factory = new ChannelFactory<QueryContract>(binding, replyTo);
Mike Vernal wrote re: Fancy Addressing in WCF
on 02-02-2006 8:54 AM
Broadly, I think it's if either (a) you want greater decoupling between the request and the reply or (b) you're doing a multicast request-reply operation.

For (a), you can imagine "async" scenarios where I want to be able to send you a message with some work that might take 4 weeks to process. Instead of holding open an HTTP request during that time, I might just want to send the message and then have the recipient call a service of mine at some point in the future.


In WCF, we support doing this to a certain extent with Duplex channels, but it's probably not the case that you would leave your client-side channel open for 4 weeks.

Instead, you'd probably define a WebHosted-service that would spin-up when an incoming request comes in, process the message, and then spin back down.

For (b), one common pattern that I think you see in multi-party interactions is that the query will go out over some multicast channel (e.g., NetPeerTcpBinding, or a theoretical UdpMulticastBinding) and that the response should come back over a point-to-point binding (e.g., NetTcpBinding, or UdpBinding).

Technically, I don't think this pattern is strictly request-reply as it's really single-request, multiple-responses. That said, I think it's still useful to leverage the addressing mechanisms to implement this scenario.

Cheers,
-mike
melack wrote re: Fancy Addressing in WCF
on 02-26-2006 9:37 AM
Hey, Mike!

Have a question. I tryed your sample but I cannot get the service callback channel to work. Here is my code example:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=249131&SiteID=1

Can you tell me what am I doing wrong?
Thanks.

[melack]
Mike Vernal wrote re: Fancy Addressing in WCF
on 03-21-2006 7:01 AM
Whoops, sorry. I had a typo in my code (I was creating a ChannelFactory parameterized by QueryContract, but I was then creating a QueryCallbackContract.

Looking at the thread, Michelle resolved the issue in the most common way (typically this pattern is used with [ServiceContract(CallbackContract ...))]. In this particular case, I was actually intentionally not using CallbackContract (there are two separate contracts, each linked in app-code and not in infrastructure-code).

Cheers,
-mike

Add a Comment

(required)  
(optional)
(required)  
Remember Me?