DCSIMG
Serializing a string as CData in WCF - Doron's .NET Space

Serializing a string as CData in WCF

We’ve hit an annoying issue a while ago. We are publishing a WCF web service which is consumed by several clients, and one of them failed to see leading and trailing spaces when reading the message. In order to solve this, without having to change and redistribute the client, we wanted to return some of the strings wrapped by a CData element. WCF doesn’t have a builtin mechanism for this, but we’ve found a suggestion to use the CDataWrapper class to acheive this.

This works, but it also changes the schema. In fact, .NET clients will see the CDataWrapper class as a DataSet in the generated proxy. In order to make a CDataWrapper property appear as a string in the WSDL, we use the XmlSchemaProvider attribute. Here is the entire CDataWrapper class we used:

   1: [XmlSchemaProvider("GetSchema")]
   2: public sealed class CDataWrapper : IXmlSerializable
   3: {
   4:  
   5:     public static XmlQualifiedName GetSchema(XmlSchemaSet xs)
   6:     {           
   7:         return XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName;
   8:     }
   9:  
  10:     // implicit to/from string
  11:     public static implicit operator string(CDataWrapper value)
  12:     {
  13:         return value == null ? null : value.Value;
  14:     }
  15:  
  16:     public static implicit operator CDataWrapper(string value)
  17:     {
  18:         return value == null
  19:                    ? null
  20:                    : new CDataWrapper
  21:                          {
  22:                              Value =
  23:                                  value
  24:                          };
  25:     }
  26:  
  27:     public System.Xml.Schema.XmlSchema GetSchema()
  28:     {            
  29:         return null;
  30:     }
  31:  
  32:     // "" => <Node/>
  33:     // "Foo" => <Node><![CDATA[Foo]]></Node>
  34:     public void WriteXml(XmlWriter writer)
  35:     {
  36:         if (!string.IsNullOrEmpty(Value))
  37:         {
  38:             writer.WriteCData(Value);
  39:         }
  40:     }
  41:  
  42:     // <Node/> => ""
  43:     // <Node></Node> => ""
  44:     // <Node>Foo</Node> => "Foo"
  45:     // <Node><![CDATA[Foo]]></Node> => "Foo"
  46:     public void ReadXml(XmlReader reader)
  47:     {
  48:         if (reader.IsEmptyElement)
  49:         {
  50:             Value = "";
  51:         }
  52:         else
  53:         {
  54:             reader.Read();
  55:             switch (reader.NodeType)
  56:             {
  57:                 case XmlNodeType.EndElement:
  58:                     Value = ""; // empty after all...
  59:                     break;
  60:                 case XmlNodeType.Text:
  61:                 case XmlNodeType.CDATA:
  62:                     Value = reader.ReadContentAsString();
  63:                     break;
  64:                 default:
  65:                     throw new InvalidOperationException("Expected text or CData but was: "+ reader.NodeType);
  66:  
  67:             }
  68:         }
  69:     }
  70:  
  71:     // underlying value
  72:     public string Value { get; set; }
  73:  
  74:     public override string ToString()
  75:     {
  76:         return Value;
  77:     }
  78: }

In order to use this in an object you want to send over the wire, you will write something like this (again, taken entirely from here):

   1: // example usage
   2: [DataContract(Namespace="http://myobjects/")]
   3: public sealed class MyType
   4: {
   5:     public string SomeValue { get; set; }
   6:     [DataMember(Name = "SomeValue", EmitDefaultValue = false)]
   7:     private CDataWrapper SomeValueCData
   8:     { 
   9:       get { return SomeValue; }
  10:       set { SomeValue = value; }
  11:      }
  12: }

And that’s all there is to it.

Published Saturday, January 29, 2011 5:41 PM by dorony

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above: