Click here to Skip to main content
Click here to Skip to main content
Go to top

Custom serialization for ExpandoObjects in ASP.NET WebAPI

, 5 Dec 2012
Rate this:
Please Sign up or sign in to vote.
Using dynamic or ExpandoObject is a breeze to dynamic data WebAPIs, but when serializing to XML, it can cause a brain damage sometimes :)

Introduction 

ASP.NET WebAPI came with their well deserved shine and look and breeze of coding. Also came with, were a lot of features that lazy coders like me would like. In this article I intend to demonstrate a situation where I plan to use and keep my WebAPI type independent and why not. Slowly we are moving to loosely typed days and thus it becomes unfriendly if we continue to delve into strongly typed models.

Background 

A generic WebAPI over a database using dynamic objects would be an idea to build generic data interfaces for common use across multiple client communities. With automatic content negotiation and support for precanned/custom serialiers it has become more and more programmer friendly to build and maintain such frameworks with little coding efforts. This article demonstrates a sample application in such a context. In the subsequent sections I will explain exactly how. But please follow me in the basic explanation of some of the interesting aspects of the code attached.

Disclaimer: The code is built using Visual Studio 2010 and MVC4 extensions. These are required to be pre-installed on your system in case you wish to build the solution. The code would execute with minor changes in hosting and configurations (e.g.: connection string). 

Automatic content negotiation with dynamic objects

The standard approach to do content negotiation is to leave it default and return the dynamic object by default. But by doing that we do not have control over the HttpRespnseMessage's StatusCode and any other parameters (unless of course we become groovy and handle globally in a DelegatingHandler). However in this approach I have chosen to return a HttpResponseMessage and set the object content accordingly. Since we are dealing with dynamic objects, here it is important that we respectfully do the null checks. 

[HttpGet]

public HttpResponseMessage Register([ModelBinder(typeof(DataContextModelBinder))] DataContext context)  
{ 
  var account = new DataWrapper().GetAccountInfo(context.AccountId, context.LocationId);
  var formatter = new DefaultContentNegotiator().Negotiate(account.GetType(), 
      Request, GlobalConfiguration.Configuration.Formatters).Formatter;
  var response = Request.CreateResponse(account != null ? HttpStatusCode.OK : HttpStatusCode.NotFound);
  response.Content = account != null ? new ObjectContent(account.GetType(), account, formatter) : null; 
return response;}

Expando to XML and CustomMediaTypeFormatter

For JSON, it is all so sweet. Json.NET's default serialization takes care of everything, but since the clients expect putting Accept verbs (Accept: application/xml) and to just get results out of the box, it is a bit more involving to manage XML serialization. The default DataContractSerializer would serialize out dynamic objects into a lousy <key> <value> pair dictionary type serialization. There is no object like look and feel. So I came up with this class to do the object like look-and-feel XML serialization from dynamic objects.

public class ExpandoXmlMediaTypeFormatter : MediaTypeFormatter
{ 
    public ExpandoXmlMediaTypeFormatter()
    {
     SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/xml"));
     SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("text/xml"));
    } 

   public override bool CanWriteType(Type type)
   {
     return type == typeof(ExpandoObject);
   }
   public override bool CanReadType(Type type)
   {
      return type == typeof(ExpandoObject);
   }

  public override Task<object> ReadFromStreamAsync(Type type, 
         Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
   {
      var ser = new XmlSerializer(type); 
      return ser.Deserialize(readStream);
   }); 
   return task;
}

public override Task WriteToStreamAsync(Type type, object value, 
       Stream writeStream, HttpContent content, TransportContext transportContext)
{
      var task = Task.Factory.StartNew(() =>
      {
            var xml = JsonConvert.DeserializeXNode("{\"root\":" + 
                     JsonConvert.SerializeObject(value) + "}").ToString();
            using (var writer = XmlTextWriter.Create(writeStream, 
                   new XmlWriterSettings() { Indent = true }))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement("Response");
                writer.WriteRaw(xml.Replace("<root>", 
                   string.Empty).Replace("</root>", string.Empty));
                writer.WriteEndElement();
                writer.WriteEndDocument();
            }
            writeStream.Flush();
      });
       return task;
    }
}

Using the Code 

To use the code, simply open the solution in Visual Studio 2010 or later, and host it in IIS or Cassini.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

tumbledDown2earth
Software Developer
India India
is a poor software developer and thinker. Presently working on a theory of "complementary perception". It's a work in progress.

Comments and Discussions

 
QuestionRe Not working PinmemberAndyFensham31-Mar-14 22:07 
QuestionExample with WebAPI ODATA PinmemberMember 7542341-Sep-13 10:10 
AnswerRe: Example with WebAPI ODATA PinmembertumbledDown2earth2-Sep-13 18:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 5 Dec 2012
Article Copyright 2012 by tumbledDown2earth
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid