Я сошел с ума, пытаясь найти элегантное решение этой проблемы, так как кажется, что все по умолчанию использовали сериализатор Newtonsoft, чтобы обойти эту проблему.
Хотя Newtonsoft предоставляет больше возможностей, у него есть серьезные недостатки.
Перечислим несколько: необходимость в конструкторах без параметров, сумасшедшее поведение, если вы хотите сериализовать классы, которые реализуют IEnumerable, и он работает очень плохо, когда используются абстрактные типы (так как он не использует атрибут KnownTypes, а обходной путь генерирует подробный вывод, который раскрывает ваши внутренние пространства имен для вызывающих).
С другой стороны, есть несколько примеров того, как настроить DataContractJsonSerializer при использовании его в решении MVC4 WebApi.
Мне потребовалось некоторое время, чтобы найти решение, которое представляет перечисления в виде строк и решает известные проблемы с форматированием DateTime, которые поставляются с DataContractJsonSerializer.
ЧАСТЬ I - Поместите эти методы расширения в класс расширений
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~
#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);
}
ЧАСТЬ II. Создайте свой собственный форматер, инкапсулировав 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;
}
}
ЧАСТЬ III - отредактируйте файл Global.asax и используйте новый форматер 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;
}