Posting Xml and Json MVC 2 Controller Actions

01.19.12

Problem:

You need to submit Xml or Json via Post/Put to your ASP.net MVC 2 Action. The request body must also bind to your models and validate.

Solution:

Write a custom value provider, this is a factory that handles the mapping of the request to forms dictionary. You just inherit from ValueProviderFactory and handle the request if it is of type “application/json” or “application/xml.”

More Info:

Phil Haack

MSDN

StackOverflow.com

CODE:

Add your custom value providers in Global.asax.cs in the method OnApplicationStarted().

protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();

RegisterRoutes(RouteTable.Routes);

ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
ValueProviderFactories.Factories.Add(new XmlValueProviderFactory());
}

JsonValueProviderFactory.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Web.Mvc;
using System.Web.Script.Serialization;

public class JsonValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
var deserializedJson = GetDeserializedJson(controllerContext);

if (deserializedJson == null) return null;

var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

AddToBackingStore(backingStore, string.Empty, deserializedJson);

return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}

private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
{
var dictionary = value as IDictionary<string, object>;
if (dictionary != null)
{
foreach (KeyValuePair<string, object> keyValuePair in dictionary)
AddToBackingStore(backingStore, MakePropertyKey(prefix, keyValuePair.Key), keyValuePair.Value);
}
else
{
var list = value as IList;
if (list != null)
{
for (var index = 0; index < list.Count; ++index)
AddToBackingStore(backingStore, MakeArrayKey(prefix, index), list[index]);
}
else backingStore[prefix] = value;
}
}

private static object GetDeserializedJson(ControllerContext controllerContext)
{
var contentType = controllerContext.HttpContext.Request.ContentType;
if (!contentType.StartsWith("text/json", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;

var input = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();

if (string.IsNullOrEmpty(input)) return null;

return new JavaScriptSerializer().DeserializeObject(input);
}

private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}

private static string MakePropertyKey(string prefix, string propertyName)
{
if (!string.IsNullOrEmpty(prefix))
return prefix + "." + propertyName;
return propertyName;
}
}

XmlValueProviderFactory.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Mvc;
using System.Xml;
using System.Xml.Linq;

public class XmlValueProviderFactory : ValueProviderFactory
{

public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
var deserializedXml = GetDeserializedXml(controllerContext);

if (deserializedXml == null) return null;

var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

AddToBackingStore(backingStore, string.Empty, deserializedXml.Root);

return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);

}

private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, XElement xmlDoc)
{
// Check the keys to see if this is an array or an object
var uniqueElements = new List<String>();
var totalElments = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (!uniqueElements.Contains(element.Name.LocalName))
uniqueElements.Add(element.Name.LocalName);
totalElments++;
}

var isArray = (uniqueElements.Count == 1 && totalElments > 1);


// Add the elements to the backing store
var elementCount = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (element.HasElements)
{
if (isArray)
AddToBackingStore(backingStore, MakeArrayKey(prefix, elementCount), element);
else
AddToBackingStore(backingStore, MakePropertyKey(prefix, element.Name.LocalName), element);
}
else
{
backingStore.Add(MakePropertyKey(prefix, element.Name.LocalName), element.Value);
}
elementCount++;
}
}


private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}

private static string MakePropertyKey(string prefix, string propertyName)
{
if (!string.IsNullOrEmpty(prefix))
return prefix + "." + propertyName;
return propertyName;
}

private XDocument GetDeserializedXml(ControllerContext controllerContext)
{
var contentType = controllerContext.HttpContext.Request.ContentType;
if (!contentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase))
return null;

XDocument xml;
try
{
var xmlReader = new XmlTextReader(controllerContext.HttpContext.Request.InputStream);
xml = XDocument.Load(xmlReader);
}
catch (Exception)
{
return null;
}

if (xml.FirstNode == null)//no xml.
return null;

return xml;
}
}