Je suis allé fou en essayant de trouver une solution élégante à ce problème car il semble que tout le monde fait défaut au sérialiseur Newtonsoft pour contourner ce problème. Bien que Newtonsoft offre plus de fonctionnalités, il présente de sérieux inconvénients. Pour en citer quelques-uns: le besoin de constructeurs sans paramètres, un comportement fou si vous souhaitez sérialiser des classes qui implémentent IEnumerable, et il fonctionne très mal lorsque des types abstraits sont utilisés (car il n'utilise pas l'attribut KnownTypes une sortie verbeuse qui expose vos espaces de noms internes aux appelants). D'autre part, il existe quelques exemples sur la façon de personnaliser le DataContractJsonSerializer lors de son utilisation sur une solution WebCpi MVC 4.
Il m'a fallu un certain temps pour trouver une solution qui représente les énumérations en tant que chaînes et qui corrige les problèmes de formatage DateTime connus qui accompagnent DataContractJsonSerializer.
PARTIE I - Mettre ces méthodes d'extension dans une classe d'extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
#region JSon
/// <summary>Serializes an object to JSon.</summary>
/// <param name="obj">The object to serialize.</param>
/// <returns>Returns a byte array with the serialized object.</returns>
/// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
[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();
}
}
/// <summary>Serializes an object to JSon.</summary>
/// <param name="obj">The object to serialize.</param>
/// <param name="stream">The stream to write to.</param>
/// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
[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<object>().ToArray()
: obj;
if (enumerationValue != null && (!type.IsGenericType || (type.IsGenericType || type.GetGenericArguments()[0].IsInterface)))
{
var firstMember = (fixedValue as System.Collections.IEnumerable).OfType<object>().FirstOrDefault();
if (firstMember != null)
fixedValue = enumerationValue.ToArray(firstMember.GetType());
}
var fixedType = obj == null
? type
: fixedValue.GetType();
var jsonSer = new DataContractJsonSerializer(fixedType, settings);
jsonSer.WriteObject(stream, fixedValue);
}
/// <summary>
/// Deserializes an object.
/// </summary>
/// <typeparam name="T">The output type of the object.</typeparam>
/// <param name="data">The serialized contents.</param>
/// <returns>Returns the typed deserialized object.</returns>
/// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
public static T DeserializeJSon<T>(this byte[] data)
{
using (MemoryStream b = new MemoryStream(data))
return DeserializeJSon<T>(b);
}
/// <summary>Deserializes a JSon object.</summary>
/// <typeparam name="T">The output type of the object.</typeparam>
/// <param name="stream">The stream to read from.</param>
/// <returns>Returns the typed object.</returns>
/// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
public static T DeserializeJSon<T>(this 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 jsonSer = new DataContractJsonSerializer(typeof(T), settings);
return (T)jsonSer.ReadObject(stream);
}
/// <summary>Deserializes a JSon object.</summary>
/// <param name="data">The serialized contents.</param>
/// <param name="targetType">The target type.</param>
/// <returns>Returns the typed deserialized object.</returns>
/// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
public static object DeserializeJSon(this byte[] data, Type targetType)
{
using (MemoryStream b = new MemoryStream(data))
{
return DeserializeJSon(b, targetType);
}
}
/// <summary>Deserializes a JSon object.</summary>
/// <param name="data">The serialized contents.</param>
/// <param name="targetType">The target type.</param>
/// <returns>Returns the typed deserialized object.</returns>
/// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
public static object DeserializeJSon(this Stream data, Type targetType)
{
var settings = new DataContractJsonSerializerSettings();
settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();
var jsonSer = new DataContractJsonSerializer(targetType, settings);
return jsonSer.ReadObject(data);
}
/// <summary>Enumerator contract surrogate.</summary>
internal class EnumToStringDataContractSurrogate : IDataContractSurrogate
{
Type IDataContractSurrogate.GetDataContractType(Type type)
{
return type == typeof(Enum) ? typeof(string) : type;
}
object IDataContractSurrogate.GetDeserializedObject(object obj, Type targetType)
{
if (targetType.IsEnum)
{
return obj == null
? System.Enum.GetValues(targetType).OfType<int>().FirstOrDefault()
: System.Enum.Parse(targetType, obj.ToString());
}
return obj;
}
object IDataContractSurrogate.GetObjectToSerialize(object obj, Type targetType)
{
if (obj is Enum)
{
var pair = Enum.GetName(obj.GetType(), obj);
return pair;
}
return obj;
}
object IDataContractSurrogate.GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
object IDataContractSurrogate.GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
void IDataContractSurrogate.GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
{
throw new NotImplementedException();
}
Type IDataContractSurrogate.GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
throw new NotImplementedException();
}
System.CodeDom.CodeTypeDeclaration IDataContractSurrogate.ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
}
#endregion
/// <summary>Creates an array from a non generic source.</summary>
/// <param name="source">The source.</param>
/// <param name="type">The target type of the array.</param>
/// <returns>Returns a typed array.</returns>
public static Array ToArray(this IEnumerable source, Type type)
{
var param = Expression.Parameter(typeof(IEnumerable), "source");
var cast = Expression.Call(typeof(Enumerable), "Cast", new[] { type }, param);
var toArray = Expression.Call(typeof(Enumerable), "ToArray", new[] { type }, cast);
var lambda = Expression.Lambda<Func<IEnumerable, Array>>(toArray, param).Compile();
return lambda(source);
}
PARTIE II - Créez votre propre formatter en encapsulant le DataContractJsonSerializer ~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// <summary>Custom implementation of DataContract formatter.</summary>
public class DataContractJsonFormatter : MediaTypeFormatter
{
/// <summary>Creates a new instance.</summary>
public DataContractJsonFormatter()
{
SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
}
/// <summary>Gets if the formatter the write a given type.</summary>
/// <param name="type">The type to handle.</param>
/// <returns>Returns if the formatter the write a given type.</returns>
public override bool CanWriteType(Type type)
{
return true;
}
/// <summary>Gets if the formatter the read a given type.</summary>
/// <param name="type">The type to handle.</param>
/// <returns>Returns if the formatter the read a given type.</returns>
public override bool CanReadType(Type type)
{
return true;
}
/// <summary>Deserializes an object.</summary>
/// <param name="type">The target type.</param>
/// <param name="readStream">The stream to read from.</param>
/// <param name="content">The http content.</param>
/// <param name="formatterLogger">A logger.</param>
/// <returns>Returns the deserialized object.</returns>
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
{
var task = Task<object>.Factory.StartNew(() =>
{
return readStream.DeserializeJSon(type);
});
return task;
}
/// <summary>Serializes an object.</summary>
/// <param name="type">The target type.</param>
/// <param name="value">The object to serialize.</param>
/// <param name="writeStream">The stream to write to.</param>
/// <param name="content">The http content.</param>
/// <param name="transportContext">The context.</param>
/// <returns>Returns the deserialized object.</returns>
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;
}
}
PARTIE III - Modifiez votre fichier Global.asax et consommer votre nouveau formatter JSon ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
/// <summary>Event handlers of when the application starts.</summary>
[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;
}
Cette n'est pas trop surprenant, puisque enums est par défaut de type int. – Powerlord
Dans la sérialisation XML, il est également recommandé d'éviter les énumérations d'exposition dans les contrats de données wcf, car ils créent des problèmes subtiles rétrocompatibles. Voir http://stackoverflow.com/questions/326339/do-you-use-enum-types-in-your-wcf-web-services –