Stefan
Stefan

Reputation: 11

How to deserialize WCF message using OperationContract

I succeeded in building a WCF client generated by svcutil.exe from the WSDL. Using the generated client proxy class I can call the web service of an external service supplier. I also succeeded in coding a message inspector, as I need to log both raw XML request and response as full SOAP message to the database.

For an emergency scenario I also need to be able to "import" a raw XML response. I found many hints on using XMLSerializer or deserializing WCF messages based on the message contract.

But how can I deserialize a raw XML response based on an operation contract? For a first test I use one of the logged raw responses, save it to a file and now try to deserialize it to the response type as generated in the client proxy. Somehow I must succeed in calling DeserializeReply() from class ClientOperation. But how to get there?

I happily accept any help as I'm quite new to WCF... TIA, Stefan

This is what I tried after Marc's answer:

  public static RatingResult DeserializeResponseFromFile(string path)
  {
     var xmlReader = XmlReader.Create(path);
     var message = Message.CreateMessage(xmlReader, int.MaxValue, MessageVersion.Soap11);
     var readerAtBodyContents = message.GetReaderAtBodyContents();
     var dcs = new DataContractSerializer(typeof(RatingResult), "RatingResponse", "http://rating.webservice.xxx.de");

     // Error in line 6 position 7. 'EndElement' 'RatingResponse' from namespace
     // 'http://rating.webservice.xxx.de' is not expected.
     // Expecting element 'commonDataField'.
     var wsResult = (RatingResult)dcs.ReadObject(readerAtBodyContents);

     return wsResult;
  }

This is part of the logged XML response file, that I'm trying to deserialize to type RatingResponse:

<soapenv:Envelope xmlns:soapenv="..." xmlns:soapenc="..." xmlns:xsd="..." xmlns:xsi="...">
  <soapenv:Header soapenv:encodingStyle="..." />
  <soapenv:Body soapenv:encodingStyle="...">
    <p933:RatingResponse xmlns:p933="http://rating.webservice.xxx.de">
      <RatingReturn href="#id0" />
    </p933:RatingResponse>
    <multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="..." xsi:type="p878:RatingResult" xmlns:p878="http://output.rating.webservice.xxx.de">
      <commonData href="#id1" />
      <acctData href="#id2" />
      <resultData href="#id3" />
    </multiRef>
    <multiRef id="id1" soapenc:root="0" soapenv:encodingStyle="..." xsi:type="p719:RatingCommonData" xmlns:p719="http://input.rating.webservice.xxx.de">
      <requestdate xsi:type="xsd:dateTime">2010-12-24T09:45:09.531Z</requestdate>
      ...

I guess that the data contract serializer has problems deserializing the href's. Please note that the message I try to deserialize "by hand" was captured using my injected message inspector. In a "normal" call of the web service this message get deserialized without problems.

Upvotes: 1

Views: 6090

Answers (2)

Jon Barker
Jon Barker

Reputation: 1829

For anyone in the future doing this. I had to manually read a WCF message out of the MSMSQ, and get the request object out of the MSMQ/WCF message envelope. Here's how:

Root code:

var q = new MessageQueue(@".\Private$\VishalQ;poison");

var allMessages = q.GetAllMessages().ToList();
var wcfRequests = allMessages.Select(ConvertToWcfRequest<ObjectChangedRequest>);

My contract:

[ServiceContract]
public interface IWish
{
    [OperationContract(IsOneWay = true)]
    void ObjectChanged(ObjectChangedRequest request);
}

My Data Contract:

[DataContract(Namespace = "http://x.namespaces.x-x.com/")]
public class ObjectChangedRequest
{
    [DataMember]
    public OperationType OperationType { get; set; }
}

My message deserialization code:

    /// <summary>
    /// Converts a WCF MSMQ message to a WCF request object.
    /// </summary>
    public static T ConvertToWcfRequest<T>(Message msmqMessage)
    {
        var buffer = new byte[msmqMessage.BodyStream.Length];
        msmqMessage.BodyStream.Read(buffer, 0, (int)msmqMessage.BodyStream.Length);

        var envelopeStart = FindEnvelopeStart(buffer);

        using var msmqStream = new MemoryStream(buffer, envelopeStart, buffer.Length - envelopeStart);
        var encodingElement  = new BinaryMessageEncodingBindingElement();
        var wcfMessage       = encodingElement.CreateMessageEncoderFactory().Encoder.ReadMessage(msmqStream, int.MaxValue);
        var document         = new XmlDocument();

        document.Load(wcfMessage.GetReaderAtBodyContents());

        var realRoot        = document.FirstChild.FirstChild;
        using var wcfStream = new MemoryStream();
        using var xmlWriter = XmlWriter.Create(wcfStream);

        realRoot.WriteTo(xmlWriter);
        xmlWriter.Flush();
        wcfStream.Seek(0, SeekOrigin.Begin);

        var wcfSerializer = new DataContractSerializer(typeof(T), realRoot.Name, "http://tempuri.org/"); //No idea why this has to be temp uri and not our namespace...

        return (T)wcfSerializer.ReadObject(wcfStream);
    }

    /// <summary>
    /// Locates the start of a WCF message within a MSMQ message.
    /// </summary>
    private static int FindEnvelopeStart(byte[] stream)
    {
        var position = 0;
        var previousByte = stream[position];

        for (position = 0; position < stream.Length; position++)
        {
            var currentByte = stream[position];

            //Some magic numbers that define the start of the WCF message envelope
            if (currentByte == 0x02 && previousByte == 0x56)
                break;

            previousByte = currentByte;
        }

        return position - 1;
    }

Upvotes: 1

marc_s
marc_s

Reputation: 755321

I don't really understand what you're trying to ask and to do.... based on an operation contract ?? The operation contract is just an attribute you put on an operation / method call to mark it as a service method .... the operation contract doesn't do anything even remotely involved with serialization or deserialization..... do you mean how to deserialize an XML message using the DataContractSerializer which is the WCF default serializer??

Assuming you do really mean HOWTO: deserialize a WCF message using the DataContractSerializer, then try this: if you have the response XML from a service call that used the default WCF DataContractSerializer, you should be able to deserialize it like this (assuming you have your XML serialized response in a xmlResponse variable):

using(MemoryStream memStm = new MemoryStream())
using(StreamWriter stw = new StreamWriter(memStm))
{
   // write your response to the memory stream
   stw.Write(xmlResponse);
   stw.Flush();

   // "reset" memory stream
   memStm.Seek(0, SeekOrigin.Begin);

   // setup DataContractSerializer     
   DataContractSerializer dcs = new DataContractSerializer(typeof(YourDataType));

   // deserialize result XML into an instance of "YourDataType"
   var result = dcs.ReadObject(memStm);
}

Upvotes: 3

Related Questions