0

I have an ugly requirement, for all properties of a child class "job", to serialize to XML wrapped in CDATA tags. Here's the spec: http://www.indeed.com/intl/en/xmlinfo.html

All the answers I find suggest an approach that is suitable for one or two properties, not a whole class. For example: How do you serialize a string as CDATA using XmlSerializer?

One of the comments in that article suggests implementing IXmlSerializable on an individual class. Sounds like it may be a better approach, since every property on my job class needs CDATA wrappers, and I don't have this requirement anywhere else. However, it would be even better if there was a per-property member to override on the serializer that I could take advantage of when I do this.

Any ideas on how to get the CDATA implementation down from 200 lines of code to 10? It strikes me how simple this would be if not for the CDATA requirement:

public class job {
    public string title { get; set; }
    public DateTime date { get; set; }
    public int referencenumber { get; set; }
    public string url { get; set; }
    public string company { get; set; }
    public string city { get; set; }
    public string state { get; set; }
    public string country { get; set; }
    public string postalcode { get; set; }
    public string description { get; set; }
    public string salary { get; set; }
    public string category { get; set; }
    public string experience { get; set; }

    public job(DataModel.PostedJob postedJob, UrlHelper urlHelper) {
        country = "US";
        category = "contract, project, consulting";
        title = postedJob.Title;
        date = postedJob.PostedOn;
        referencenumber = postedJob.Id;
        url = urlHelper.Action(MVC.SearchJobs.Index(postedJob.Id));
        company = postedJob.Company;
        city = postedJob.City;
        state = postedJob.State;
        postalcode = postedJob.PostalCode;
        description = postedJob.Description;
        experience = postedJob.MinYears.ToString() + "+ years";
    }

    public job() { }
}

EDIT: Note that I'm currently serializing this using MvcContrib "XmlResult" code snippet shown here: Return XML from a controller's action in as an ActionResult? ... The non-traditional case on the property names themselves reflect the XML schema required by the specification linked above. I thought that since view-model properties are typically tightly-coupled to the consumer it was appropriate, but feel free to correct me.

Community
  • 1
  • 1
shannon
  • 8,664
  • 5
  • 44
  • 74
  • Side note: Since all the answers I find are 2-3 years old, I also wonder if there's a newer serializer that will magically solve this for me. – shannon Aug 02 '12 at 05:55
  • Are all your properties *always* "simple" types (`string`, `DateTime`, `int` etc)? (And are you able to change your names so that they follow .NET naming conventions?) – Jon Skeet Aug 02 '12 at 06:04
  • Hey Jon. Nice to hear from you. Yes re: simple types, the class you see is the actual view-model/DTO I've implemented, and since it's based on the external spec for a popular service I don't expect it to change soon. Also, I can certainly name the properties to follow the .NET conventions, but I thought it might be clearer to leave the serialization attributes off this class. I do see your point, and a developer has no reason to expect such a dependency anyway. – shannon Aug 02 '12 at 06:15
  • I edited above regarding the non-traditional case on the property names. – shannon Aug 02 '12 at 06:31

1 Answers1

1

If everything is simple, you can write your own serializer using LINQ to XML.

Here's a very unpolished version... you'd need to make it cover all the types you need, and also consider how to represent null references if necessary.

using System;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

class Foo
{
    public string Name { get; set; }
    public int Value { get; set; }
    public DateTime When { get; set; }
}

class Test
{
    static void Main()
    {
        Foo foo = new Foo { 
            Name = "Jon", 
            Value = 5,
            When = DateTime.Now
        };

        XElement element = Serialize("Foo", foo);
        Console.WriteLine(element);
        Foo bar = Deserialize<Foo>(element);
        Console.WriteLine("Deserialized values:");
        Console.WriteLine("{0}, {1}, {2}", 
                          bar.Name, bar.Value, bar.When);
    }

    static XElement Serialize(XName elementName,
                              object value)
    {
        return new XElement(elementName,
          value.GetType()
               .GetProperties()
               .Select(p => new XElement
                   (p.Name, ConvertValue(p.GetValue(value, null)))));

    }

    static XCData ConvertValue(object value)
    {
        // This is a bit odd. It takes advantage of LINQ
        // to XML being nice with XElement, but not for
        // XCData
        XElement tmp = new XElement("ignored", value);
        return new XCData(tmp.Value);
    }

    static T Deserialize<T>(XElement element) where T : new()
    {
        T ret = new T();
        foreach (var sub in element.Elements())
        {
            var property = typeof(T).GetProperty(sub.Name.LocalName);
            SetProperty(property, sub, ret);
        }
        return ret;
    }

    static void SetProperty(PropertyInfo property,
                            XElement element,
                            object target)
    {
        // This is somewhat annoying. There should be a better
        // way...
        var type = property.PropertyType;
        object value;
        if (type == typeof(string))
        {
            value = (string) element;
        } 
        else if (type == typeof(int))
        {
            value = (int) element;
        }
        else if (type == typeof(DateTime))
        {
            value = (DateTime) element;
        }
        // ...
        else
        {
            throw new Exception("Can't convert " + type);
        }
        property.SetValue(target, value, null);
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194