I went crazy trying to find an elegant solution to this problem as it seems that everyone defaulted to Newtonsoft's serializer to workaround this issue.
Though Newtonsoft provides more features, it does have some severe drawbacks.
To enumerate a few: the need for parameterless constructors, crazy behaviour if you wish to serialize classes that implement IEnumerable, and it performs very badly when abstract types are used (as it does not make use of the KnownTypes attribute, and the workaround generates a verbose output that exposes your internal namespaces to callers).
On the other hand, there are little examples on how to customize the DataContractJsonSerializer when using it on an MVC4 WebApi solution.
It took me a while to find a solution that represents enums as strings and addresses the known DateTime formatting issues that comes with the DataContractJsonSerializer.
PART I - Put these extension methods in an extensions class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#region JSon
/// Serializes an object to JSon.
/// The object to serialize.
/// Returns a byte array with the serialized object.
/// This implementation outputs dates in the correct format and enums as string.
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
public static byte[] SerializeJson(this object obj)
{
using (MemoryStream b = new MemoryStream())
{
SerializeJson(obj, b);
return b.ToArray();
}
}
/// Serializes an object to JSon.
/// The object to serialize.
/// The stream to write to.
/// This implementation outputs dates in the correct format and enums as string.
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
public static void SerializeJson(this object obj, Stream stream)
{
var settings = new DataContractJsonSerializerSettings();
settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();
var type = obj == null ? typeof(object) : obj.GetType();
var enumerationValue = obj as System.Collections.IEnumerable;
var fixedValue = enumerationValue != null
? type.IsGenericType && !type.GetGenericArguments()[0].IsInterface
? enumerationValue.ToArray(type.GetGenericArguments()[0])
: enumerationValue.OfType
PART II - Create your own formatter by encapsulating the DataContractJsonSerializer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Custom implementation of DataContract formatter.
public class DataContractJsonFormatter : MediaTypeFormatter
{
/// Creates a new instance.
public DataContractJsonFormatter()
{
SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
}
/// Gets if the formatter the write a given type.
/// The type to handle.
/// Returns if the formatter the write a given type.
public override bool CanWriteType(Type type)
{
return true;
}
/// Gets if the formatter the read a given type.
/// The type to handle.
/// Returns if the formatter the read a given type.
public override bool CanReadType(Type type)
{
return true;
}
/// Deserializes an object.
/// The target type.
/// The stream to read from.
/// The http content.
/// A logger.
/// Returns the deserialized object.
public override Task ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
{
var task = Task.Factory.StartNew(() =>
{
return readStream.DeserializeJSon(type);
});
return task;
}
/// Serializes an object.
/// The target type.
/// The object to serialize.
/// The stream to write to.
/// The http content.
/// The context.
/// Returns the deserialized object.
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
var task = Task.Factory.StartNew(() =>
{
value.SerializeJson(writeStream);
});
return task;
}
}
PART III - Edit your Global.asax file and consume your new JSon formatter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Event handlers of when the application starts.
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
protected void Application_Start()
{
//Register my custom DataContract JSon serializer
GlobalConfiguration.Configuration.Formatters.Insert(0, new DataContractJsonFormatter());
//Register areas
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
// BundleConfig.RegisterBundles(BundleTable.Bundles);
//JSON serialization config
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = false;
}