views:

219

answers:

2

I am writing an ASP.NET MVC 2 site with REST endpoints. I want to consume this service with ActiveResource in a Rails web application.

So far, I've managed to get routes setup in ASP.NET MVC that match the ActiveResource conventions. But where I'm having problems is with de-serializing the data that ActiveResource sends to the REST service.

It seems that Rails does xml serialization very different than .NET (but I haven't been able to find these conventions documented anywhere). For example, with a .NET class like this:

public class Person
{
  public string first_name { get; set; }
  public string last_name { get; set; }
  public bool active { get; set; }
}

Here are some of the differences:

  1. .NET serializes the root as <Person> while rails does <person>
  2. .NET adds a namespace while rails does not
  3. .NET serializes the properties like <first_name> while rails does <first-name>
  4. .NET serializes the bool as <active> while rails does <active type="boolean">

Number 4 doesn't seem to cause any problems, but the other 3 cause .NET to not de-serialize the message. You can solve these problems by decorating the object with XmlRoot and XmlElement attributes. But this is error prone and tedious.

Does anyone know a better way to de-serialize XML in .NET which has been serialized by Rails' ActiveResource?

A: 

yes.

[XmlType("person",   // issue #1
     Namespace="")]  // issue #2
public class Person 
{ 
  [XmlElement("first-name")]  // issue #3
  public string first_name { get; set; } 

  [XmlElement("last-name")]  // issue #3
  public string last_name { get; set; } 

  public bool active { get; set; } 
} 
Cheeso
I pointed out in the question that this approach works, but is error prone and tedious. I'm looking for a better way that doesn't require every object to be decorated. Any ideas? Thanks.
Kevin Berridge
You can use a special XmlSerializer instantiated with an XmlAttributeOverrides object. You'd have to dynamically build the XmlAttributeOverrides to handle the underscore-to-dash convention, and the downcase convention. It would be a single serializer class for any type of object.
Cheeso
+1  A: 

This can be achieved using XmlAttributeOverrides.

Following is a basic example, GetOverrides() being the important part:

public class RailsXmlSerializer
{
  private Type type;

  public RailsXmlSerializer(Type type)
  {
    this.type = type;
  }

  public void Serialize(Stream stream, object o)
  {
    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
    ns.Add("", "");

    XmlSerializer xmlSerializer = new XmlSerializer(type, GetOverrides(type));
    xmlSerializer.Serialize(stream, o, ns);
  }

  public object Deserialize(Stream stream)
  {
    XmlSerializer xmlSerializer = new XmlSerializer(type, GetOverrides(type));
    return xmlSerializer.Deserialize(stream);
  }

  private XmlAttributeOverrides GetOverrides(Type type)
  {
    XmlAttributeOverrides overrides = new XmlAttributeOverrides();

    XmlAttributes classAttributes = new XmlAttributes();
    classAttributes.XmlType = new XmlTypeAttribute(type.Name.ToLower());
    overrides.Add(type, classAttributes);

    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(type))
    {
      XmlAttributes propertyAttributes = new XmlAttributes();
      propertyAttributes.XmlElements.Add(new XmlElementAttribute(property.Name.ToLower().Replace('_', '-')));
      overrides.Add(type, property.Name, propertyAttributes);
    }

    return overrides;
  }
}

Obviously, this needs to be a more robust implementation, possibly as a decorator for XmlSerializer, with error and type checking. Of course, GetOverrides() can also be customized to provide the specific output you desire. That being said, here's the resulting output given a sample Person object:

<?xml version="1.0" encoding="utf-16"?>
<person>
  <first-name>Jason</first-name>
  <last-name>Bourne</last-name>
  <active>true</active>
</person>

And, given this input, deserialization is also performed correctly to provide the proper Person object.

Tarsier