views:

434

answers:

2

I am trying to implement a very basic reliable message state handling system to provide improved reliability in my Silverlight to WCF server communications.

I have taken the route of introducing a custom message header into the SOAP request that contains an incrementing integer as a message ID. The idea is that when I receive a request at the WCF server, I want to check the message ID. If the ID is > the last ID, then I simply execute the request and AFTER the request, I cache a copy of the result along with the new ID.

If the ID is == the lastID, I assume the client never received my message and I want to simply return the cache'd response object instead of re-processing the request. I have written a MessageInspector Behavior object that I inject into the WCF Endpoint behaviors. This object implements the IDispatchMessageInspector, which has 2 methods:

object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
{
    // Get the incoming message headers and get the messageId
    var idx = request.Headers.FindHeader("ClientRequestId", "");
    if (idx >= 0)
    {
        var requestHeader = request.Headers[idx];
        int requestId = 0;
        if (Int32.TryParse(requestHeader.ToString(), out requestId))
        {
            int lastRequest = myCache.GetLastId();
            if (requestId <= lastRequest)
            {
                // @TODO - Send back the saved message
                var reply = myCache.GetLastResponse();
                if (reply != null)
                {
                    /// ERK -- > Woops, how do I override the service
                    /// and make the reply here?
                }
            }
            myCache.SetLastId(requestId);
        }
    }
    return null;
}

void IDispatchMessageInspector.BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    var requestId = myCache.GetLastId();
    if (requestId > 0)
    {
        myCache.SetLastResponse(reply);
    }
}

Well, the problem I ran into should be obvious... The AfterReceiveRequest return value (object) is the value passed to BeforeSendReply as "correlatedState". NOT, as I originally anticipated, the new return value for the request. The question is, can I stop the message from processing to the service at this location and return a 'cached' response?

Actually the better version of this question: is this the appropriate 'injection' location to handle the caching and response tracking? If not, where would be the "approved ms" or better way of accomplishing this request tracking and recovery?

thanks.

-Jeff

A: 

Hi Jeff, i think you're trying to re-invent the wheel slightly here. WCF has Reliable Messaging that is designed to ensure message delivery already. I'd try that, unless that is you don't want to use it for a specific reason?

Tanner
Yes, but to my knowledge, WCF reliable messaging is NOT available in silverlight. Silverlight only implements basicHttpBinding, which from what I've read, does not support reliable messaging.
Jeff
A: 

After much trial and tribulation, I found a solution to my problem that, if not elegant, is functional.

In order to bypass the call to the service endpoint when I detect a duplicate service request, I simply throw a FaultException. Then in the BeforeSendReply method, I check to see if replay.IsFault and then if the fault is the specific fault code I threw in AfterReceiveRequest. If so, i return the cached copy of the message response. The FaultException mechanism is what I need to use to bypass the the service call.

If anyone wants full working code for this solution, drop a note here and I'll post my final code after I complete final debug and unit test.

-Jeff

Jeff