0

I implemented a custom message inspector (via IDispatchMessageInspector) to intercept messages that are received on the server side of a WCF service so I can attempt to deserialize the message and apply some specific business logic. The problem I'm encountering is when I write the MessageBuffer's contents to a new MemoryStream and then try to deserialize, I get an error that says "The data at the root level is invalid. Line 1, position 1." I do know the data being passed in is valid as skipping over the inspector makes everything work fine.

Sample Code:

public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
    {
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();
        string msg = buffer.CreateMessage().ToString();

        var dc = new DataContractSerializer(typeof(Adder));

        using (var stream = new MemoryStream())
        {
            buffer.WriteMessage(stream);

            stream.Position = 0;

            //deserializing error occurs here
            var c = dc.ReadObject(stream);
        }

        return null;
    }

Here is the Adder class/interface:

    [DataContract(Name = "adder", Namespace = "http://test.com")]
public class Adder
{
    [DataMember(Name = "first")]
    public int First { get; set; }

    [DataMember(Name = "second")]
    public int Second { get; set; }
}

    [ServiceContract(Namespace = "http://test.com")]
public interface ITestSvc
{
    [OperationContract(Name = "add")]
    int Add(Adder adder);
}

Any suggestions or is there a better option for this? My main goal is to read the XML (in a deserialized object) on every WCF request that comes into my service.

Scott Salyer
  • 2,165
  • 7
  • 45
  • 82
  • Why are you trying to deserialize it yourself? Is there a particular problem you're trying to get around? – Mike Parkhill Oct 23 '12 at 23:47
  • the error message suggests it can be due to NameSpace – Johan Larsson Oct 24 '12 at 00:18
  • It's mostly born out of laziness. I am using a custom security principal that will be based on certain parts of data sent in every message. I'm hoping to intercept the message as it comes in, set the ThreadPrincipal and then all authorization can work automatically. This feels easier overall than setting it on every method call manually. – Scott Salyer Oct 24 '12 at 02:56
  • I've done something similar in the past, but I was always pulling the credentials out of the message headers, so haven't actually tried deserializing the payload in my message inspector. – Mike Parkhill Oct 24 '12 at 15:28
  • I'm not opposed to headers, but one major piece is making sure those same header values could be sent from other clients such as Android/iOS making calls. Assuming they can, I'll move to headers and be done with this! – Scott Salyer Oct 24 '12 at 17:13

3 Answers3

5

The request object contains the WCF message headers as well as the payload. You'll need to strip off the headers and then you should be able to deserialize the message body.

For example, a SOAP message would have:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>

</soap:Header>
<soap:Body>
  <!-- your payload -->
</soap:Body>

You could use XML navigation to get to the body element and then you'd deserialize that element alone.

EDIT: Actually just stumbled on this method which I think should do the trick for you:

Message.GetReaderAtBodyContents

Mike Parkhill
  • 5,511
  • 1
  • 28
  • 38
  • I tried looking at the message this way via the built-in XML viewer and where the body was supposed to be it said "... stream ..." which I hadn't seen before. I'm using wsHttpBinding so it should've posted the whole message at once, right? Ever seen this before? – Scott Salyer Oct 24 '12 at 03:01
  • Message.GetReaderAtBodyContents also gives me the same error, as does Message.GetBody. That's why I'm so confused. – Scott Salyer Oct 24 '12 at 13:20
  • Have you inspected the XML to make sure it looks how you expect it to? Could your client be sending in something unexpected? – Mike Parkhill Oct 24 '12 at 15:27
  • I have from the standpoint of using the "msg" variable (shown in my code above) and using the built-in XML viewer. That's where I see ... stream ..., but since it's doing regular wsHttpBinding I'd assume it's not a stream and should be the whole message, right? Ever seen that one before? – Scott Salyer Oct 24 '12 at 17:18
0

I just did this. Just paste all of the following code into your class, and call DeserializedResponse(). You'll need to change MyResponseObject to the name of whatever object you are trying to deserialize. Also, you will need to replace "_requestInspector.Response" with your own response xml string variable. The method GetSoapBodyInnerXml() will strip off the Soap Envelope, and only return your response xml which you wish to deserialize.

    private MyResponseObject DeserializedResponse()
    {
        var rootAttribute = new XmlRootAttribute("MyResponseObject ");
        rootAttribute.Namespace = @"http://www.company.com/MyResponseObjectNamespace";

        XmlSerializer serializer = new XmlSerializer(typeof(MyResponseObject ), rootAttribute);
        string responseSoapBodyInnerXml = GetSoapBodyInnerXml(_requestInspector.Response);
        AddXmlDeclaration(ref responseSoapBodyInnerXml);
        MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(responseSoapBodyInnerXml));
        MyResponseObject resultingResponse = (MyResponseObject )serializer.Deserialize(memStream);

        return resultingResponse;
    }

    private string GetSoapBodyInnerXml(string soapMessage)
    {
        XDocument xDoc = XDocument.Parse(soapMessage);
        XNamespace nsSoap = @"http://schemas.xmlsoap.org/soap/envelope/";
        return xDoc.Descendants(nsSoap + CONST_SoapBody).Descendants().First().ToString();
    }

    private void AddXmlDeclaration(ref string xmlString)
    {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(xmlString);

        //Create an XML declaration. 
        XmlDeclaration xmldecl;
        xmldecl = doc.CreateXmlDeclaration("1.0", "UTF-8", null);

        //Add the new node to the document.
        XmlElement root = doc.DocumentElement;
        doc.InsertBefore(xmldecl, root);

        //Return updated xmlString with XML Declaration
        xmlString = doc.InnerXml;
    }
  • Just a note... I only did this in order to test why WCF did not automatically deserialize a response xml string returned by a WebService reference into the response object. WCF returned null values into my response object, so I had to capture the response message from a request inspector in an attempt to deserialize myself. I was expecting to see errors as to why deserializing failed, but my manual deserializing worked, and fixed my issue. – user3182856 Jan 10 '14 at 17:35
-3

I ended up going the message header route, like Mike Parkhill suggested.

Scott Salyer
  • 2,165
  • 7
  • 45
  • 82