views:

2614

answers:

3

When serializing/de-serializing certain classes I've come across the need to flag or mark certain properties as CDATA elements (due to their content). I am currently handling this like so:

    <XmlElement("MessageText")> _
    Public Property XmlContentLeft() As XmlCDataSection
        Get
            Dim doc As New XmlDataDocument()
            Dim cd As XmlCDataSection = doc.CreateCDataSection(Me.MessageText)
            Return cd
        End Get
        Set(ByVal value As XmlCDataSection)
            Me.MessageText = value.Value
        End Set
    End Property

    <XmlIgnore()> _
    Public Property MessageText() As String
        Get
            Return _messageText
        End Get
        Set(ByVal value As String)
            _messageText= value
        End Set
    End Property

Now while this works great it has drawbacks -- I now have duplicate properties for anything I want to be a CDATA element and I have to write extra code for these properties.

So my question is whether or not there is a better way to do this? I don't want to have to write custom schemas or serialization routines for each class. In an ideal scenario I'd be able to add an attribute to these properties so they are automatically treated as CDATA elements.

+2  A: 

Two things:

1) You can simplify your existing CDATA code like this:

<XmlElement("MessageText")> _
Public Property XmlContentLeft() As XmlCDataSection
    Get
        return GetCData(Me.MessageText)
    End Get
    Set(ByVal value As XmlCDataSection)
        Me.MessageText = value.Value
    End Set
End Property

' this method is re-usable by any property that needs CData
Private Function GetCData(ByVal value As String) As XmlCDataSection
    Static doc As New XmlDataDocument() 
    return doc.CreateCDataSection(value)
End Function

<XmlIgnore()> _
Public Property MessageText() As String
    Get
        Return _messageText
    End Get
    Set(ByVal value As String)
        _messageText= value
    End Set
End Property

Of course that's actually more code than you posted, but note that your CData property is now much simpler. Also, my use of Static isn't a typo or C# mistake. VB.Net has a little-known Static keyword for creating members in functions. This way the XmlDataDocument is only created once for the entire class and doesn't pollute your class's private namespace.

2) The serializer should already properly escapes character data. Do you really need it to be CData? Anything you serialize should be properly deserialized to match the original, even things like code strings. The only case I can thing where you might need a CDATA section is if you have to conform to a schema that's expecting it.

Joel Coehoorn
KISS -- gotta remember that! Did I really need CDATA? Doesn't appear to be so. I must have assumed at some point that because of content that would normally invalidate the XML would need to be CDATA. Didn't know about the auto-escaping. Cheers!
Sean Gough
A: 

Hi there, It does resolve the problem for serilization but its generating exception when i'm deserializing it.

My "MessageText" element is empty, it does not contains any text node or CdataSection node.

Below is the exception generated.

"Unable to cast object of type 'System.Xml.XmlElement' to type 'System.Xml.XmlCDataSection'."}

Any Idea?

If you have a follow up question you should post it asa new question, not as an answer to this old question.More people will read it since old questions are not frequented very much.In the top right is an "Ask Question" button to do so.You of course can always link back to this page for reference if you want to.
sth
A: 

I had a similar problem - a client needed XML from a webservice with strings wrapped in horrible CData tags, this is totally unneccessary but they didn't want to recode their client so I had to make my XML conform.

The way I worked it was to retype the (string) properties I wanted to appear as CDATA to my own custom type "XmlCDataString".

I then implemented IXmlSerializable on that type and shoved a couple of Narrowing and Widening conversion operator overloads in so that the properties still work as much like Strings as possible.

Imports System.Xml.Serialization
Imports System.Xml
<Serializable()> _
Public Class XmlCDataString
  Implements IXmlSerializable

  Private _strValue As String = Nothing

  Public Sub New()

  End Sub

  Public Sub New(ByVal strValue As String)
    _strValue = strValue
  End Sub

  Public Property StringValue() As String
    Get
      Return _strValue
    End Get
    Set(ByVal value As String)
      _strValue = value
    End Set
  End Property

  Public Shared Widening Operator CType(ByVal strValue As String) As XmlCDataString
    Return New XmlCDataString(strValue)
  End Operator

  Public Shared Narrowing Operator CType(ByVal cdata As XmlCDataString) As String
    Return cdata.StringValue
  End Operator

  Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
    Throw New NotImplementedException
  End Function

  Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml
    ' TODO
  End Sub

  Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
    Dim doc As XmlDocument
    Dim xmlCData As XmlCDataSection
    Dim serializer As XmlSerializer

    doc = New XmlDataDocument()
    xmlCData = doc.CreateCDataSection(_strValue)
    serializer = New XmlSerializer(GetType(XmlCDataSection))
    serializer.Serialize(writer, xmlCData)

  End Sub
End Class

The properties I am then serializing to CData I have to retype as follows (using your example):

Private _messageText As XmlCDataString  

Public Property MessageText() As XmlCDataString 
    Get 
        Return _messageText 
    End Get 
    Set(ByVal value As XmlCDataString) 
        _messageText= value 
    End Set 
End Property 

That worked for me!

James Close