Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using CDATA with WCF REST starter kits

Tags:

rest

c#

wcf

I have a service built using Preview 2 of the WCF REST starter kit, but I've run into an issue with passing around XML-styled data in the calls. Here's my request object:

[DataContract(Namespace = "")]
public class ServiceRequest
{
    [DataMember]
    public string ContentText { get; set; }
    [DataMember]
    public string ApiKey { get; set; }

}

Everything works fine until you throw '' in there. Is there a to encapsulate the ContentText property in a CDATA or something similar?

like image 711
Joel.Cogley Avatar asked Sep 03 '09 15:09

Joel.Cogley


3 Answers

Marc Gravell has a solution here for serializing CDATA sections.

I have copied the code here for posterity.

updated: the previous example did not generate a valid schema, the XmlSchemaProviderAttribute and accompanying method will generate "xs:string" which works [more...]

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.ComponentModel;

[XmlSchemaProvider("GenerateSchema")]
public sealed class CDataWrapper : IXmlSerializable
{
  // implicit to/from string
  public static implicit operator string(CDataWrapper value)
  {
    return value == null ? null : value.Value;
  }

  public static implicit operator CDataWrapper(string value)
  {
    return value == null ? null : new CDataWrapper { Value = value };
  }

  public System.Xml.Schema.XmlSchema GetSchema()
  {
    return null;
  }

  // return "xs:string" as the type in scheme generation
  public static XmlQualifiedName GenerateSchema(XmlSchemaSet xs)
  {
      return XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName;
  }

  // "" => <Node/>
  // "Foo" => <Node><![CDATA[Foo]]></Node>
  public void WriteXml(XmlWriter writer)
  {
    if (!string.IsNullOrEmpty(Value))
    {
      writer.WriteCData(Value);
    }
  }

  // <Node/> => ""
  // <Node></Node> => ""
  // <Node>Foo</Node> => "Foo"
  // <Node><![CDATA[Foo]]></Node> => "Foo"
  public void ReadXml(XmlReader reader)
  {
    if (reader.IsEmptyElement)
    {
      Value = "";
    }
    else
    {
      reader.Read();

      switch (reader.NodeType)
      {
        case XmlNodeType.EndElement:
          Value = ""; // empty after all...
          break;
        case XmlNodeType.Text:
        case XmlNodeType.CDATA:
          Value = reader.ReadContentAsString();
          break;
        default:
          throw new InvalidOperationException("Expected text/cdata");
      }
    }
  }

  // underlying value
  public string Value { get; set; }
  public override string ToString()
  {
    return Value;
  }
}

// example usage
[DataContract(Namespace="http://myobjects/")]
public sealed class MyType
{
  public string SomeValue { get; set; }
  [DataMember(Name = "SomeValue", EmitDefaultValue = false)]
  private CDataWrapper SomeValueCData
  {
    get { return SomeValue; }
    set { SomeValue = value; }
  }

  public string EmptyTest { get; set; }
  [DataMember(Name = "EmptyTest", EmitDefaultValue = false)]
  private CDataWrapper EmptyTestCData
  {
    get { return EmptyTest; }
    set { EmptyTest = value; }
  }

  public string NullTest { get; set; }
  [DataMember(Name = "NullTest", EmitDefaultValue = false)]
  private CDataWrapper NullTestCData
  {
    get { return NullTest ; }
    set { NullTest = value; }
  }
}

// test rig
static class Program
{
  static void Main()
  {
    DataContractSerializer dcs = new DataContractSerializer(typeof(MyType));

    StringWriter writer = new StringWriter();
    using (XmlWriter xw = XmlWriter.Create(writer))
    {
      MyType foo = new MyType
      {
        SomeValue = @"&<t\d",
        NullTest = null,
        EmptyTest = ""
      };

      ShowObject("Original", foo);

      dcs.WriteObject(xw, foo);
      xw.Close();
    }

    string xml = writer.ToString();
    ShowObject("Xml", xml);

    StringReader reader = new StringReader(xml);
    using (XmlReader xr = XmlReader.Create(reader))
    {
      MyType bar = (MyType) dcs.ReadObject(xr);
      ShowObject("Recreated", bar);
    }
  }

  static void ShowObject(string caption, object obj)
  {
    Console.WriteLine();
    Console.WriteLine("** {0} **", caption );
    Console.WriteLine();

    if (obj == null)
    {
      Console.WriteLine("(null)");
    }
    else if (obj is string)
    {
      Console.WriteLine((string)obj);
    }
    else
    {
      foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(obj))
      {
        Console.WriteLine("{0}:\t{1}", prop.Name, prop.GetValue(obj) ?? "(null)");
      }
    }
  }
}
like image 129
Darrel Miller Avatar answered Oct 21 '22 04:10

Darrel Miller


VB conversion of the CDataWrapper in the accepted answer:

Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Xml
Imports System.Xml.Schema
Imports System.Xml.Serialization
Imports System.ComponentModel

Public Class CDataWrapper
    Implements IXmlSerializable

    'underlying value
    Public Property Value As String

    'Implicit to/from string
    Public Shared Widening Operator CType(ByVal value As CDataWrapper) As String
        If value Is Nothing Then
            Return Nothing
        Else
            Return value.Value
        End If
    End Operator

    Public Shared Widening Operator CType(value As String) As CDataWrapper
        If value Is Nothing Then
            Return Nothing
        Else
            Return New CDataWrapper() With {.Value = value}
        End If
    End Operator


    Public Function GetSchema() As XmlSchema Implements IXmlSerializable.GetSchema
        Return Nothing
    End Function

    ' <Node/> => ""
    ' <Node></Node> => ""
    ' <Node>Foo</Node> => "Foo"
    ' <Node><![CDATA[Foo]]></Node> => "Foo"
    Public Sub ReadXml(reader As XmlReader) Implements IXmlSerializable.ReadXml
        If reader.IsEmptyElement Then
            Me.Value = ""
        Else
            reader.Read()

            Select Case reader.NodeType
                Case XmlNodeType.EndElement
                    Me.Value = "" ' empty after all...
                Case XmlNodeType.Text, XmlNodeType.CDATA
                    Me.Value = reader.ReadContentAsString()
                Case Else
                    Throw New InvalidOperationException("Expected text/cdata")
            End Select
        End If
    End Sub

    ' "" => <Node/>
    ' "Foo" => <Node><![CDATA[Foo]]></Node>
    Public Sub WriteXml(writer As XmlWriter) Implements IXmlSerializable.WriteXml
        If Not String.IsNullOrEmpty(Me.Value) Then
            writer.WriteCData(Me.Value)
        End If
    End Sub

    Public Overrides Function ToString() As String
        Return Me.Value
    End Function
End Class
like image 3
Hc5kphUXpR Avatar answered Oct 21 '22 03:10

Hc5kphUXpR


Above code is missing the fact that you must go past the content after you have read it. So this class as it stands won't work with a collection.

Change it to the following, and you can now hold Collections of CDataWrapper.

Value = reader.ReadContentAsString(); 
reader.Read();
like image 3
AnubhavK Avatar answered Oct 21 '22 04:10

AnubhavK