为什么DateTime.MinValue不能在UTC之前的时区中序列化?

我遇到了WCF REST服务的问题。 我尝试返回的线对象没有设置某些属性,导致DateTime.MinValuetypes为DateTimetypes的属性。 该服务返回一个空文档(HTTP状态200 ???)。 当我尝试自己调用JSON序列化时,引发的exception是:

SerializationException:在转换为UTC时,大于DateTime.MaxValue或小于DateTime.MinValue的DateTime值无法序列化为JSON。

这可以通过在控制台应用程序中运行以下代码来进行重现:

DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(DateTime)); MemoryStream m = new MemoryStream(); DateTime dt = DateTime.MinValue; // throws SerializationException in my timezone ser.WriteObject(m, dt); string json = Encoding.ASCII.GetString(m.GetBuffer()); Console.WriteLine(json); 

这是为什么? 我认为这与我的时区(GMT + 1)有关。 由于DateTime.MinValue是默认的(DateTime),我希望这可以序列化没有问题。

有关如何使我的REST服务的行为的任何提示? 我不想改变我的DataContract。

主要的问题是DateTime.MinValueDateTimeKind.Unspecified种类。 它被定义为:

 MinValue = new DateTime(0L, DateTimeKind.Unspecified); 

但是这不是一个真正的问题,这个定义导致序列化过程中的问题。 JSON DateTime序列化通过以下方式完成:

 System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(DateTime value) 

不幸的是它被定义为:

 ... if (value.Kind != DateTimeKind.Utc) { long num = value.Ticks - TimeZone.CurrentTimeZone.GetUtcOffset(value).Ticks; if ((num > DateTime.MaxValue.Ticks) || (num < DateTime.MinValue.Ticks)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString("JsonDateTimeOutOfRange"), new ArgumentOutOfRangeException("value"))); } } ... 

所以它不考虑Unspecified并将其视为Local 。 为了避免这种情况,你可以定义你自己的常量:

 MinValueUtc = new DateTime(0L, DateTimeKind.Utc); 

要么

 MinValueUtc = DateTime.MinValue.ToUniversalTime(); 

它看起来很奇怪,但它有帮助。

尝试添加此任何DateTime成员

 [DataMember(IsRequired = false, EmitDefaultValue = false)] 

大多数这些错误发生是因为datetime的默认值是DateTime.MinValue ,从1年开始,JSON序列化从1970年开始。

如果您的时区是GMT + 1,那么您的时区中的DateTime.MinValue的UTC值将比DateTime.MinValue小一个小时。

使用这个构造函数:

 public DataContractJsonSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, IDataContractSurrogate dataContractSurrogate, bool alwaysEmitTypeInformation) 

示例代码:

 DataContractJsonSerializer serializer = new DataContractJsonSerializer(o.GetType(), null, int.MaxValue, false, new DateTimeSurrogate(), false); public class DateTimeSurrogate : IDataContractSurrogate { #region IDataContractSurrogate 成员public object GetCustomDataToExport(Type clrType, Type dataContractType) { return null; } public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType) { return null; } public Type GetDataContractType(Type type) { return type; } public object GetDeserializedObject(object obj, Type targetType) { return obj; } public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes) { } public object GetObjectToSerialize(object obj, Type targetType) { if (obj.GetType() == typeof(DateTime)) { DateTime dt = (DateTime)obj; if (dt == DateTime.MinValue) { dt = DateTime.MinValue.ToUniversalTime(); return dt; } return dt; } if (obj == null) { return null; } var q = from p in obj.GetType().GetProperties() where (p.PropertyType == typeof(DateTime)) && (DateTime)p.GetValue(obj, null) == DateTime.MinValue select p; q.ToList().ForEach(p => { p.SetValue(obj, DateTime.MinValue.ToUniversalTime(), null); }); return obj; } public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) { return null; } public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit) { return typeDeclaration; } #endregion } 

我相信一个更优雅的方法是指示序列化程序不发出DateTime字段的默认值。 这将在传输过程中保存一些字节,并在序列化您没有任何值的字段时进行一些处理。 例:

 [DataContract] public class Document { [DataMember] public string Title { get; set; } [DataMember(IsRequired = false, EmitDefaultValue = false)] public DateTime Modified { get; set; } } 

或者你可以使用Nullables。 例:

 [DataContract] public class Document { [DataMember] public string Title { get; set; } [DataMember] public DateTime? Modified { get; set; } } 

这完全取决于您在项目中的要求和限制。 有时你不能只改变数据types。 在这种情况下,您仍然可以利用DataMember属性并保持数据types不变。

在上面的例子中,如果你在服务器端有了new Document() { Title = "Test Document" } ,当序列化为JSON的时候会给你{"Title": "Test Document"}所以处理起来更容易在JavaScript或任何其他客户端在电线的另一端。 在JavaScript中,如果你使用JSON.Parse(),并尝试读取它,你将返回undefined 。 在types化语言中,根据types(这通常是预期的行为),将具有该属性的默认值。

 library.GetDocument(id).success(function(raw){ var document = JSON.Parse(raw); var date = document.date; // date will be *undefined* ... } 

您可以通过OnSerializing属性和一些reflection来修复序列化过程中的问题:

 [OnSerializing] public void OnSerializing(StreamingContext context) { var properties = this.GetType().GetProperties(); foreach (PropertyInfo property in properties) { if (property.PropertyType == typeof(DateTime) && property.GetValue(this).Equals(DateTime.MinValue)) { property.SetValue(this, DateTime.MinValue.ToUniversalTime()); } } }