18

I have the same problem that is listed in the following thread.

WSDL first WCF server where client does not send SOAPAction

I performed the steps that are listed in the same thread (also shown below)

1) Download the Microsoft WCF examples. Add the following files to your project from WF_WCF_Samples\WCF\Extensibility\Interop\RouteByBody\CS\service

DispatchByBodyOperationSelector.cs

DispatchByBodyBehaviorAttribute.cs

2) Add the following attributes to your interface (next to your ServiceContract)

XmlSerializerFormat

DispatchByBodyBehavior

3) Add the following to your service interface

[OperationContract(Action = "")]

public void DoNothing()
{
}

4) For my service the WrapperName and Wrappernamespace are null for all messages. I had to go into DispatchByBodyBehaviorAttribute and edit ApplyDispatchBehavior() to add the following lines to check for this:

 if (qname.IsEmpty) {
     qname = new XmlQualifiedName(operationDescription.Messages[0].Body.Parts[0].Name, operationDescription.Messages[0].Body.Parts[0].Namespace);
 }

Now, I am getting an error message "This message cannot support the operation because it has been read". I turned the tracing on and captured the stack trace (below). If anyone has any idea on how this can be resolved, I appreciate if you could post some comments. Thanks for any help!

at System.ServiceModel.Channels.Message.GetReaderAtBodyContents()
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
at System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.Runtime.InputQueue`1.AsyncQueueReader.Set(Item item)
at System.Runtime.InputQueue`1.EnqueueAndDispatch(Item item, Boolean canDispatchOnThisThread)
at System.Runtime.InputQueue`1.EnqueueAndDispatch(T item, Action dequeuedCallback, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, Action dequeuedCallback, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(HttpRequestContext context, Action callback)
at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContextCore(IAsyncResult result)
at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContext(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
at System.Net.ListenerAsyncResult.WaitCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
</StackTrace>


class DispatchByBodyElementOperationSelector : IDispatchOperationSelector
{
    Dictionary<XmlQualifiedName, string> dispatchDictionary;

    public DispatchByBodyElementOperationSelector(Dictionary<XmlQualifiedName, string> dispatchDictionary)
    {
        this.dispatchDictionary = dispatchDictionary;            
    }

    #region IDispatchOperationSelector Members

    private Message CreateMessageCopy(Message message, XmlDictionaryReader body)
    {
        //Message copy = Message.CreateMessage(message.Version, message.Headers.Action, body);
        //copy.Headers.CopyHeaderFrom(message, 0);
        //copy.Properties.CopyProperties(message.Properties);
        //return copy;    

        MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue);
        Message copy = buffer.CreateMessage();
        buffer.Close();
        copy.Headers.CopyHeaderFrom(message, 0);
        copy.Properties.CopyProperties(message.Properties);

        return copy;
    }

    public string SelectOperation(ref System.ServiceModel.Channels.Message message)
    {
        XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();

        XmlQualifiedName lookupQName = new XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
        message = CreateMessageCopy(message,bodyReader);
        if (dispatchDictionary.ContainsKey(lookupQName))
        {
            return dispatchDictionary[lookupQName];
        }
        else
        {
            return null;
        }
    }

    #endregion
}

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
sealed class DispatchByBodyBehaviorAttribute : Attribute, IContractBehavior
{
    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
        // no binding parameters need to be set here
        return;
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        // this is a dispatch-side behavior which doesn't require
        // any action on the client
        return;
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
    {
        // We iterate over the operation descriptions in the contract and
        // record the QName of the request body child element and corresponding operation name
        // to the dictionary to be used for dispatch 
        Dictionary<XmlQualifiedName,string> dispatchDictionary = new Dictionary<XmlQualifiedName,string>();
        foreach( OperationDescription operationDescription in contractDescription.Operations )
        {
            XmlQualifiedName qname =
                new XmlQualifiedName(operationDescription.Messages[0].Body.WrapperName, operationDescription.Messages[0].Body.WrapperNamespace);




            if (qname.IsEmpty)
            {
                qname = new XmlQualifiedName(operationDescription.Messages[0].Body.Parts[0].Name, operationDescription.Messages[0].Body.Parts[0].Namespace);
            }

            dispatchDictionary.Add(qname, operationDescription.Name);                
        }

        // Lastly, we create and assign and instance of our operation selector that
        // gets the dispatch dictionary we've just created.
        dispatchRuntime.OperationSelector = 
            new DispatchByBodyElementOperationSelector(dispatchDictionary);
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
        // 
    }

    #endregion
}
Community
  • 1
  • 1
Jyina
  • 2,530
  • 9
  • 42
  • 80

3 Answers3

35

You should use MessageBuffer.CreateMessage:

The body of a Message instance can only be consumed or written once. If you wish to consume a Message instance more than once, you should use the MessageBuffer class to completely store an entire Message instance into memory.

http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.messagebuffer.aspx

Code from my current project:

public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
    reply = buffer.CreateMessage();
    Message m = buffer.CreateMessage();
    LogMessage(m, " Response => ");
}

Add ref for Message param and return new message.

 private Message CreateMessageCopy(ref Message message, XmlDictionaryReader body)
{
...
   message = buffer.CreateMessage();
Dmitry Harnitski
  • 5,838
  • 1
  • 28
  • 43
  • Thank you for posting the code. In my scenario, I am reusing the classes from WCF Samples download. I tried to put the MessageBuffer code in the CreateMessageCopy method as below but it did not work for me. – Jyina Jun 14 '12 at 19:40
  • I have edited my original question and posted the two classes that I am reusing from WCF samples. I commented out the existing code in CreateMessageCopy() and tried creating the message using MessageBuffer. When I run my client, it is failing at the line. MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue); – Jyina Jun 14 '12 at 20:08
  • It looks like the message has already been read before it hits this line. I am not sure where should I put the MessageBuffer code. Any ideas? Thanks! – Jyina Jun 14 '12 at 20:09
  • I tried ref as you advised and still getting the same error. I also tried to put a breakpoint at SelectOperation() and noticed it is firing the error when it hits the line MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue) for the first time. I am not sure why it says that the message was already read even though it is hitting this line first time. – Jyina Jun 14 '12 at 20:34
  • You read message here copy.Headers.CopyHeaderFrom(message, 0); copy.Properties.CopyProperties(message.Properties); – Dmitry Harnitski Jun 14 '12 at 20:38
  • I just tried to comment out those two lines but makes no difference. It is firing the error when it hits MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue); – Jyina Jun 14 '12 at 20:50
  • I just wanted to share this. I downloaded the WCF samples and tried to use the classes from C:\WF_WCF_Samples\WCF\Extensibility\Interop\AdvancedDispatchByBody. Now I don't get the error “This message cannot support the operation because it has been read”. Thanks for all your help! – Jyina Jun 15 '12 at 20:03
  • I'm having a similar problem. What is the difference in the code? I'm having problems spotting it. – Pablo Romeo Jun 23 '12 at 00:25
6

I had a very similar issue, using code from WCF Samples (RouteByBody to be precise) as well, and was able to solve it in a different way so I'll post it here in case it helps anybody.

Situation: The client application (consumer) would work in Release, however, when the debugger was attached it would always fail with the error "This message cannot support the operation because it has been read".

After much tracing and logging WCF messages, the only solution that worked for me turned out to be so simple:

My Service was hosted on IIS, and with debug="true" in the <compilation> section of the web.config.

Changing it to debug="false" on the service fixed all my problems.

Pablo Romeo
  • 11,298
  • 2
  • 30
  • 58
  • But then How we will be able to debug? – Faizan Mubasher Feb 07 '14 at 07:03
  • This has worked for us. Detaching the debugger from the process allows the messages to come through. Having the debugger attached would result in the InvalidOperationException "The message cannot support this operation because it has been read". Furthermore, the error only seems to occur if `DispatchByBodyBehavior` is used for the service. – knittl Nov 04 '14 at 10:30
6

Dmitry Harnitski's answer does not work if you are debugging the service (It will give you a "This message cannot support the operation because it has been copied." error.)

This works even in debug mode:

XmlDictionaryReader GetReader(ref Message message)
{
    MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue);
    message = buffer.CreateMessage();
    newMessage = buffer.CreateMessage();
    XmlDictionaryReader rv = buffer.CreateMessage().GetReaderAtBodyContents();
    buffer.Close();
    return rv;
}

static System.ServiceModel.Channels.Message newMessage = null;
static System.ServiceModel.Channels.Message lastMessage = null;

public string SelectOperation(ref System.ServiceModel.Channels.Message message)
{
    try
    {
        if(message == lastMessage)
            message = newMessage;

        XmlDictionaryReader bodyReader = GetReader(ref message);

        lastMessage = message;

        XmlQualifiedName lookupQName = new XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
        if (dispatchDictionary.ContainsKey(lookupQName))
        {
            return dispatchDictionary[lookupQName];
        }
        else
        {
            return null;
        }
    }
    catch(Exception ex)
    {
        throw ex;
    }
}
kovacs lorand
  • 741
  • 7
  • 23