Hi, as I said in my previous post, I’m working on a kind of soa-like system, in the system we are exchanging some envelopes messages, something like this:

1 <Envelope> 2 <AuthData name="" pwd="" /> 3 <ContextData name="" /> 4 <Input> 5 <FirstNode /> 6 <FirstNode /> 7 <SecondNode /> 8 </Input> 9 <Output> 10 <ThirdNode /> 11 <FourthNode /> 12 <FourthNode /> 13 </Output> 14 </Envelope>

In my previous post Xml Default serialization I was talking about some serialization problems I found, especialy with serializing collections. I have to say that the Service, the remote service is an old-style ASMX Web Service, wich accept a string parameter and returns a string representing the output message. Well, while I was coding up a client to that service, I tried, first, to evolve the Client using a WCF style proxy, we know it’s truly possible to call an ASMX service from with a WCF client, but talking about the client proxy I made is out of the scope of this post.

Well, I made a Proxy library with a Proxy client and some Objects, well..Contracts, describing the solution and then using a DataContractSerializer in conjunction with an XmlSerializer to serialize-deserialize messages regarding the simple-and-raw message string representation which the Service expect..

So, the First problem was, How to create a DataContract object with a Input and Output DataMembers properties which could accept any kind of object? This because, clearly, I can send in the envelope any operation-request and expect the related operation-response..but how can I put them in to the evelope-DataContract-Object?? after some search I realized that the better thing was to accept in the Input and in the Output DataMember properties an xml String, so the things are:

  1. create the Message object
  2. create the Operation object
  3. serialize with an XmlSerializer the operation itself and put the result in the Input part of the Message previously created

But, when I tried to do as above, I fall into a DataContractSerializer behavior wich will "encode" the xml string, so the string instead of been passed for example as "<xml></xml>" is passed as "&lt;xml&gt;&lt;/xml&gt;", it’s clear that the encode xml string will not be accepted as a valid xml, so what could I try more?

A developer team I was working with, take the solution using an XmlDocument and let it be serialized-deserialized by the serializer, but I don’t really love this solution nor the XmlDocument itself for many reasons, then I took a solution from a forum-port I found on the net regarding a problem as mine, how to serialize a raw-xml string without involve in encoding-issue which is not an Issue but in my case it was..

so here is the solution, the brilliant one (I cannot find where I put the original forum-post-idea link), the solution is mixing the IXmlSerializable and the DataContractSerializer serialization. The DataContractSerializer we know will serialize DataContracts, DataMembers and more, even types marked as inheriting from IXmlSerializable, so using an object of that type as a DataMember of my Message-envelope will produce a well defined, not encoded xml string. The thing is made using implicit operators which will do the magic of translating an xml string to and from this kind of object.

I named the object XmlRawData because it represent a special place-holder object to represent an xml string inside a DataMember representation, but here is the code:

1 using System.Xml; 2 using System.Xml.Schema; 3 using System.Xml.Serialization; 4 5 namespace XGuy.Common.Serialization 6 { 7 /// <summary> 8 /// Rpresent a wrapper-placeholder for raw xml string 9 /// representation where is not possible to use direct 10 /// <b>string</b> data type 11 /// </summary> 12 public class XmlRawData : IXmlSerializable 13 { 14 /// <summary> 15 /// implicit operator from <b>XmlRawData</b> to <b>string</b> 16 /// </summary> 17 /// <param name="item"> 18 /// the XmlRawData instance to convert 19 /// </param> 20 /// <returns>the content string</returns> 21 public static implicit operator string(XmlRawData item) 22 { 23 return item == null ? null : item.Content; 24 } 25 26 /// <summary> 27 /// implicit operator from <b>string</b> to <b>XmlRawData</b> 28 /// </summary> 29 /// <param name="content">the xml raw string to convert 30 /// and wrap with an XmlRawData object</param> 31 /// <returns>the XmlRawData object</returns> 32 public static implicit operator XmlRawData(string content) 33 { 34 return content == null ? null : new XmlRawData(content); 35 } 36 37 /// <summary> 38 /// Gets or sets the string content of the object 39 /// </summary> 40 public string Content 41 { 42 get { return _content; } 43 set{ _content = value;} 44 } 45 46 /// <summary>internal content string placeholder</summary> 47 private string _content; 48 49 /// <summary> 50 /// Creates a new <b>XmlRawData</b> instance 51 /// </summary> 52 public XmlRawData() 53 {} 54 55 /// <summary> 56 /// Creates a new <b>XmlRawData</b> instance 57 /// </summary> 58 /// <param name="content">the raw xml string to wrap</param> 59 public XmlRawData(string content) 60 { 61 _content = content; 62 } 63 64 /// <summary> 65 /// Get the schema instance of the schema 66 /// related to the content 67 /// string 68 /// </summary> 69 /// <returns>a null reference</returns> 70 public XmlSchema GetSchema() 71 { 72 return null; 73 } 74 75 /// <summary> 76 /// Read from an xml reader the raw xml string content 77 /// previous serialized 78 /// </summary> 79 /// <param name="reader"></param> 80 /// <remarks> 81 /// Here is the first part of the magic of the object which 82 /// will take the inner xml of the containing xml and 83 /// reads it 84 /// into the inner content placeholder preventing the 85 /// encoding-decoding steps 86 /// </remarks> 87 public void ReadXml(XmlReader reader) 88 { 89 if (reader.IsEmptyElement) 90 { _content = string.Empty; } 91 else 92 { 93 switch (reader.NodeType) 94 { 95 case XmlNodeType.EndElement: 96 _content = string.Empty; break; 97 case XmlNodeType.Element: 98 _content = reader.ReadInnerXml(); 99 break; 100 } 101 } 102 } 103 104 /// <summary> 105 /// Writes the raw xml string into an <see cref="XmlWriter"/> 106 /// </summary> 107 /// <param name="writer"></param> 108 /// <remarks> 109 /// Here is the second part of the magic of the object which 110 /// will take the raw xml string and writes out with the 111 /// WriteRaw method this preventing the 112 /// encoding-decoding steps so the xml result clear 113 /// </remarks> 114 public void WriteXml(XmlWriter writer) 115 { 116 if(!string.IsNullOrEmpty(_content)) 117 writer.WriteRaw(_content); 118 } 119 120 /// <summary> 121 /// Returns the inner raw xml string 122 /// </summary> 123 /// <returns></returns> 124 public override string ToString() 125 { 126 return Content; 127 } 128 } 129 }

The above code shows how we can avoid the raw xml encoding step during the serialization, but let explaing and showing it with an usage example:

1 using System.ServiceModel; 2 using System.Runtime.Serialization; 3 4 namespace XGuy.Common.Serialization.Test 5 { 6 //first of all define a DataContract which will 7 //contains the xml raw DataMember property 8 9 [DataContract(Namespace = "http://xguy.serialization/2008/11")] 10 public class SimpleEnvelope 11 { 12 ///<summary> 13 /// Contains the envelope operation name 14 ///</summary> 15 [DataMember()] 16 public string Name { get; set; } 17 18 ///<summary> 19 /// Contains the envelope input data xml 20 ///</summary> 21 [DataMember()] 22 public XmlRawData Input { get; set; } 23 24 ///<summary> 25 /// Contains the envelope output data xml 26 ///</summary> 27 [DataMember(EmitDefaultValue = false)] 28 public XmlRawData Output { get; set; } 29 } 30 31 ///<summary> 32 /// Simple test class 33 ///</summary> 34 public class XmlRawDataTest() 35 { 36 public void Run() 37 { 38 //now I can do something like this 39 SimpleEnvelope env = new SimpleEvelope() 40 { 41 Name = "TestOperation", 42 Input = @"<Operation><Data>Test data 43 content</Data></Operation>" 44 }; 45 46 StringBuilder buffer 47 = new StringBuilder(); 48 49 XmlWriterSettings settings = new XmlWriterSettings(); 50 settings.OmitXmlDeclaration = true; 51 52 using (StringWriter writer = new StringWriter(buffer)) 53 using (XmlWriter xwriter = 54 XmlDictionaryWriter.Create(writer, settings)) 55 { 56 57 DataContractSerializer ser = 58 new DataContractSerializer(typeof(SimpleEnvelope)); 59 ser.WriteObject(xwriter, env); 60 xwriter.Flush(); 61 } 62 63 Console.WriteLine(buffer.ToString()); 64 } 65 } 66 }

So the result written out in the console will be:

1 <SimpleEnvelope 2 xmlns="http://xguy.serialization/2008/11"> 3 <Name>TestOperation</Name> 4 <Input> 5 <!-- this is the raw xml added part --> 6 <Operation><Data>Test data content</Data></Operation> 7 </Input> 8 <SimpleEnvelope/>

instead of the following without the XmlRawData object:

<SimpleEnvelope xmlns="http://xguy.serialization/2008/11"> <Name></Name> <Input> <!-- this is the encoded raw xml added part--> &lt;Operation&gt;&lt;Data&gt;Test data content&lt;/Data&gt;&lt;/Operation&gt; </Input> </SimpleEnvelope>

the difference it’s clear and no needs of more explain! Hope this Helps!

Ciao!

Advertisements