none
Message Inspector causes InvalidOperationException - missing WebBodyFormatMessageProperty after the MessageBuffer class is used to return a copy of the message

    Question

  • I'm using an implementation of a IDispatchMessageInspector to log service request payloads to a database. In the message inspector the MessageBuffer class is used to copy the message into a buffer to enable the request to be read multiple times. WCF has the restriction where the message can only be read once, hence the need for the buffer.

    Below is the code for the message inspector:-

    public

    object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext){

    MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);

    //  Set request for downstream processing (request is ref)
    request = buffer.CreateMessage();

    //  Log message to database
    LogMessage("Message", buffer.CreateNavigator());

    buffer.Close();

    }

    The service being called is an ADO.NET data service which uses a WebHttpBinding. The binding is created based on the WebServiceHostFactory class in System.ServiceModel.Web.

    After processing is completed by the inspector the follow error occurs:-

    Incoming message for operation 'ProcessRequestForMessage' (contract 'IRequestHandler' with namespace 'http://tempuri.org/') does not contain a WebBodyFormatMessageProperty. This can be because a WebContentTypeMapper or a WebMessageEncodingBindingElement has not been configured on the binding. See the documentation of WebContentTypeMapper and WebMessageEncodingBindingElement for more details.

    The error still occurs when our logging code is removed. When the code using the MessageBuffer is commented out so the request object is left untouched the call works.

    Another observation I made is when a copy of the message is created from the buffer, the resulting message instance type is a StreamedMessage whereas when the message which first arrives in the inspector has an instance type of NullMessage.

    The stack trace for the error is:-

    System.ServiceModel.Dispatcher.HttpStreamFormatter.GetStreamFromMessage(Message message, Boolean isRequest)
    System.ServiceModel.Dispatcher.HttpStreamFormatter.DeserializeRequest(Message message, Object[] parameters)
    System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter.DeserializeRequest(Message message, Object[] parameters)
    System.ServiceModel.Dispatcher.CompositeDispatchFormatter.DeserializeRequest(Message message, Object[] parameters)
    System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
    System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
    System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
    System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
    System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
    System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
    System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
    System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
    System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Dispatch(MessageRpc& rpc, Boolean isOperationContextSet)
    System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
    System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
    System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
    System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(IAsyncResult result)
    System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
    System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
    System.ServiceModel.Channels.InputQueue`1.AsyncQueueReader.Set(Item item)
    System.ServiceModel.Channels.InputQueue`1.EnqueueAndDispatch(Item item, Boolean canDispatchOnThisThread)
    System.ServiceModel.Channels.InputQueue`1.EnqueueAndDispatch(T item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)
    System.ServiceModel.Channels.InputQueueChannel`1.EnqueueAndDispatch(TDisposable item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)
    System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)
    System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(HttpRequestContext context, ItemDequeuedCallback callback)
    System.ServiceModel.Activation.HostedHttpTransportManager.HttpContextReceived(HostedHttpRequestAsyncResult result)
    System.ServiceModel.Activation.HostedHttpRequestAsyncResult.HandleRequest()
    System.ServiceModel.Activation.HostedHttpRequestAsyncResult.BeginRequest()
    System.ServiceModel.Activation.HostedHttpRequestAsyncResult.OnBeginRequest(Object state)
    System.ServiceModel.PartialTrustHelpers.PartialTrustInvoke(ContextCallback callback, Object state)
    System.ServiceModel.Activation.HostedHttpRequestAsyncResult.OnBeginRequestWithFlow(Object state)
    System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2()
    System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke()
    System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks()
    System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(Object state)
    System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
    System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(UInt32 error, UInt32 bytesRead, NativeOverlapped* nativeOverlapped)
    System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)


    Can anyone help me resolve this problem. The message inspector works prefectly with other types of services. 

    Regards
    Wilko31
    • Edited by Wilko31 Wednesday, June 17, 2009 12:34 PM Stack trace missing 1st line..
    Wednesday, June 17, 2009 12:18 PM

Answers

  • If the method takes a Stream then it's already using the Raw format. Methods with a Stream parameter when used with WebHttpBinding/Web[ScriptEnabling|Http]Behavior will receive any arbitrary input from the request (see more info at http://blogs.msdn.com/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-receiving-arbitrary-data.aspx). So setting WebBodyFormat.Raw shouldn't be a problem.

    The only problem will be if the request is empty (Content-Length: 0), then the Stream that is given to the method will be unusable. If that's still a problem, then another workaround for this bug would be to recreate a new empty message (see code below) by doing the check for the ContentLength == 0 at the AfterReceiveRequest method:

        public class Post_ecafdc19_5e95_4286_aef1_6cd3937c2363
        {
            [ServiceContract]
            public interface ITest
            {
                [OperationContract]
                Stream Echo(Stream input);
            }
            [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
            public class Service : ITest
            {
                public Stream Echo(Stream input)
                {
                    string str = new StreamReader(input).ReadToEnd();
                    return new MemoryStream(Encoding.UTF8.GetBytes(str));
                }
            }
            public class MyInspector : IDispatchMessageInspector, IEndpointBehavior
            {
                internal class MyEmptyMessage : Message
                {
                    MessageHeaders headers;
                    MessageProperties properties;
                    public MyEmptyMessage()
                    {
                        this.headers = new MessageHeaders(MessageVersion.None);
                        this.properties = new MessageProperties();
                    }
                    public override MessageHeaders Headers
                    {
                        get { return this.headers; }
                    }
                    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
                    {
                        writer.WriteElementString("Binary", "");
                    }
                    public override MessageProperties Properties
                    {
                        get { return this.properties; }
                    }
                    public override MessageVersion Version
                    {
                        get { return MessageVersion.None; }
                    }
                }
                public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
                {
                    Message requestToLog;
                    if (WebOperationContext.Current.IncomingRequest.ContentLength == 0)
                    {
                        requestToLog = request;
                        Message newMessage = new MyEmptyMessage();
                        newMessage.Properties.CopyProperties(request.Properties);
                        newMessage.Headers.CopyHeadersFrom(request.Headers);
                        request = newMessage;
                    }
                    else
                    {
                        MessageBuffer buffer = request.CreateBufferedCopy(int.MaxValue);
                        request = buffer.CreateMessage();
                        requestToLog = buffer.CreateMessage();
                    }
                    Console.WriteLine("Request to log: {0}", requestToLog);
                    if (!request.Properties.ContainsKey(WebBodyFormatMessageProperty.Name))
                    {
                        request.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
                    }
                    return null;
                }
                public void BeforeSendReply(ref Message reply, object correlationState)
                {
                }
                public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
    
                public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
    
                public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
                {
                    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
                }
                public void Validate(ServiceEndpoint endpoint) { }
            }
            public static void Test()
            {
                string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
                ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
                ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
                endpoint.Behaviors.Add(new WebHttpBehavior());
                endpoint.Behaviors.Add(new MyInspector());
                host.Open();
                Console.WriteLine("Host opened");
    
                ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WebHttpBinding(), new EndpointAddress(baseAddress));
                factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
                ITest proxy = factory.CreateChannel();
                Stream stream = proxy.Echo(new MemoryStream(Encoding.UTF8.GetBytes("Hello")));
                Console.WriteLine(new StreamReader(stream).ReadToEnd());
                stream = proxy.Echo(new MemoryStream(new byte[0]));
                Console.WriteLine(new StreamReader(stream).ReadToEnd());
                ((IClientChannel)proxy).Close();
                factory.Close();
    
                Console.WriteLine("Press ENTER to close");
                Console.ReadLine();
                host.Close();
            }
        }
    
    • Marked as answer by Wilko31 Monday, June 29, 2009 12:15 PM
    Wednesday, June 24, 2009 7:07 PM

All replies

  • This seems to be a bug in WCF - the NullMessage doesn't compose too well with the MessageBuffer.

    I was able to reproduce this issue using an [OperationContract] which takes / returns Stream parameters, if this is not what you had can you post your contract here?

    There is a (quite ugly) workaround for the issue I was able to reproduce, which has two parts:
    1. On the inspector, add a WebBodyFormatMessageProperty to the message before passing it along the stack
    2. This message will be transformed into a Stream, but this stream is unusable (it can't be read from); so catch an exception to notify that the incoming request was empty.

    The code below shows the workaround:

        public class Post_ecafdc19_5e95_4286_aef1_6cd3937c2363
        {
            [ServiceContract]
            public interface ITest
            {
                [OperationContract]
                Stream Echo(Stream input);
            }
            [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
            public class Service : ITest
            {
                public Stream Echo(Stream input)
                {
                    string str = "";
                    try
                    {
                        str = new StreamReader(input).ReadToEnd();
                    }
                    catch (IOException e)
                    {
                        if (e.InnerException is XmlException)
                        {
                            str = ""; // workaround for WCF bug
                        }
                    }
                    return new MemoryStream(Encoding.UTF8.GetBytes(str));
                }
            }
            public class MyInspector : IDispatchMessageInspector, IEndpointBehavior
            {
                public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
                {
                    MessageBuffer buffer = request.CreateBufferedCopy(int.MaxValue);
                    request = buffer.CreateMessage();
                    if (!request.Properties.ContainsKey(WebBodyFormatMessageProperty.Name))
                    {
                        request.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
                    }
                    Console.WriteLine("Request to log: {0}", buffer.CreateMessage());
                    return null;
                }
                public void BeforeSendReply(ref Message reply, object correlationState)
                {
                }
                public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
    
                public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
    
                public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
                {
                    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
                }
                public void Validate(ServiceEndpoint endpoint) { }
            }
            public static void Test()
            {
                string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
                ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
                ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
                endpoint.Behaviors.Add(new WebHttpBehavior());
                endpoint.Behaviors.Add(new MyInspector());
                host.Open();
                Console.WriteLine("Host opened");
    
                ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WebHttpBinding(), new EndpointAddress(baseAddress));
                factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
                ITest proxy = factory.CreateChannel();
                Stream stream = proxy.Echo(new MemoryStream(Encoding.UTF8.GetBytes("Hello")));
                Console.WriteLine(new StreamReader(stream).ReadToEnd());
                stream = proxy.Echo(new MemoryStream(new byte[0]));
                Console.WriteLine(new StreamReader(stream).ReadToEnd());
                ((IClientChannel)proxy).Close();
                factory.Close();
    
                Console.WriteLine("Press ENTER to close");
                Console.ReadLine();
                host.Close();
            }
        }
    
    Friday, June 19, 2009 9:23 PM
  • Or for a not-so-ugly workaround, you can also check the incoming Content-Length; if it's zero, then don't bother reading from the stream:

                public Stream Echo(Stream input)
                {
                    string str = "";
                    if (WebOperationContext.Current.IncomingRequest.ContentLength == 0)
                    {
                        str = ""; // don't try to read from stream, it won't work
                    }
                    else
                    {
                        str = new StreamReader(input).ReadToEnd();
                    }
                    return new MemoryStream(Encoding.UTF8.GetBytes(str));
                }
    
    Friday, June 19, 2009 9:24 PM
  • Thanks Carlos for taking the time to investigate.

    I managed to work around the issue by adding a"WebBodyFormatMessageProperty"  to the message.Properties collection as suggested in the message inspector. Since the application is calling an ADO.NET data service I don't have any control of the contract or implementation therefore I cannot make any further changes. Although looking in reflector at the service the actual method being called (named ProcessRequestForMessage) accepts a stream, which is similar to the example you gave.

    I'm still a little nervous about the workaround because I'm setting the WebBodyFormatMessageProperty to a value of WebBodyFormat.Raw which I suspect may not work with all REST ful services. What do you think?
    Wednesday, June 24, 2009 12:13 PM
  • If the method takes a Stream then it's already using the Raw format. Methods with a Stream parameter when used with WebHttpBinding/Web[ScriptEnabling|Http]Behavior will receive any arbitrary input from the request (see more info at http://blogs.msdn.com/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-receiving-arbitrary-data.aspx). So setting WebBodyFormat.Raw shouldn't be a problem.

    The only problem will be if the request is empty (Content-Length: 0), then the Stream that is given to the method will be unusable. If that's still a problem, then another workaround for this bug would be to recreate a new empty message (see code below) by doing the check for the ContentLength == 0 at the AfterReceiveRequest method:

        public class Post_ecafdc19_5e95_4286_aef1_6cd3937c2363
        {
            [ServiceContract]
            public interface ITest
            {
                [OperationContract]
                Stream Echo(Stream input);
            }
            [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
            public class Service : ITest
            {
                public Stream Echo(Stream input)
                {
                    string str = new StreamReader(input).ReadToEnd();
                    return new MemoryStream(Encoding.UTF8.GetBytes(str));
                }
            }
            public class MyInspector : IDispatchMessageInspector, IEndpointBehavior
            {
                internal class MyEmptyMessage : Message
                {
                    MessageHeaders headers;
                    MessageProperties properties;
                    public MyEmptyMessage()
                    {
                        this.headers = new MessageHeaders(MessageVersion.None);
                        this.properties = new MessageProperties();
                    }
                    public override MessageHeaders Headers
                    {
                        get { return this.headers; }
                    }
                    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
                    {
                        writer.WriteElementString("Binary", "");
                    }
                    public override MessageProperties Properties
                    {
                        get { return this.properties; }
                    }
                    public override MessageVersion Version
                    {
                        get { return MessageVersion.None; }
                    }
                }
                public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
                {
                    Message requestToLog;
                    if (WebOperationContext.Current.IncomingRequest.ContentLength == 0)
                    {
                        requestToLog = request;
                        Message newMessage = new MyEmptyMessage();
                        newMessage.Properties.CopyProperties(request.Properties);
                        newMessage.Headers.CopyHeadersFrom(request.Headers);
                        request = newMessage;
                    }
                    else
                    {
                        MessageBuffer buffer = request.CreateBufferedCopy(int.MaxValue);
                        request = buffer.CreateMessage();
                        requestToLog = buffer.CreateMessage();
                    }
                    Console.WriteLine("Request to log: {0}", requestToLog);
                    if (!request.Properties.ContainsKey(WebBodyFormatMessageProperty.Name))
                    {
                        request.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
                    }
                    return null;
                }
                public void BeforeSendReply(ref Message reply, object correlationState)
                {
                }
                public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
    
                public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
    
                public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
                {
                    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
                }
                public void Validate(ServiceEndpoint endpoint) { }
            }
            public static void Test()
            {
                string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
                ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
                ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
                endpoint.Behaviors.Add(new WebHttpBehavior());
                endpoint.Behaviors.Add(new MyInspector());
                host.Open();
                Console.WriteLine("Host opened");
    
                ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WebHttpBinding(), new EndpointAddress(baseAddress));
                factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
                ITest proxy = factory.CreateChannel();
                Stream stream = proxy.Echo(new MemoryStream(Encoding.UTF8.GetBytes("Hello")));
                Console.WriteLine(new StreamReader(stream).ReadToEnd());
                stream = proxy.Echo(new MemoryStream(new byte[0]));
                Console.WriteLine(new StreamReader(stream).ReadToEnd());
                ((IClientChannel)proxy).Close();
                factory.Close();
    
                Console.WriteLine("Press ENTER to close");
                Console.ReadLine();
                host.Close();
            }
        }
    
    • Marked as answer by Wilko31 Monday, June 29, 2009 12:15 PM
    Wednesday, June 24, 2009 7:07 PM
  • Another thing, you don't need to copy the headers / properties for the empty message (in AfterReceiveRequest), since it's an empty message after all (I tried editing the previous post but somehow it didn't work).
    Wednesday, June 24, 2009 7:09 PM