it-swarm.com.de

Warum kann DateTime.MinValue nicht vor UTC in Zeitzonen serialisiert werden?

Ich habe Probleme mit einem WCF-Dienst REST. Das Drahtobjekt, das ich zurückgeben möchte, enthält bestimmte Eigenschaften, die nicht festgelegt wurden. Dies führt zu DateTime.MinValue für Eigenschaften des Typs DateTime. Der Service gibt ein leeres Dokument zurück (mit HTTP-Status 200 ???). Wenn ich versuche, JSON-Serialisierung selbst aufzurufen, wird die folgende Ausnahme ausgelöst:

SerializationException: DateTime-Werte, die bei der Konvertierung nach UTC größer als DateTime.MaxValue oder kleiner als DateTime.MinValue sind, können nicht in JSON serialisiert werden.

Dies kann durch Ausführen des folgenden Codes in einer Konsolenanwendung reproduziert werden:

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);

Warum ist dieses Verhalten? Ich denke, das hängt mit meiner Zeitzone (GMT + 1) zusammen. Da DateTime.MinValue voreingestellt ist (DateTime), würde ich erwarten, dass dies ohne Probleme serialisiert werden kann.

Tipps zum Verhalten meines REST - Dienstes? Ich möchte meinen DataContract nicht ändern.

49
Teun D

Das Hauptproblem ist DateTime.MinValue hat DateTimeKind.Unspecified-Art. Es ist definiert als:

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

Dies ist jedoch kein echtes Problem, diese Definition führt zu Problemen bei der Serialisierung. JSON DateTime-Serialisierung erfolgt durch:

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

Leider ist es definiert als:

...

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")));
    }
}

...

Es berücksichtigt also nicht Unspecified und behandelt es als Local. Um diese Situation zu vermeiden, können Sie Ihre eigene Konstante definieren:

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

oder 

MinValueUtc = DateTime.MinValue.ToUniversalTime();

Es sieht natürlich komisch aus, aber es hilft.

64

Versuchen Sie, dies auf einem DateTime-Mitglied hinzuzufügen

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

Die meisten dieser Fehler treten auf, weil der Standardwert von datetimeDateTime.MinValue ist, der aus dem Jahr 1 und die JSON-Serialisierung aus dem Jahr 1970 stammt.

13
user1505015

Wenn Ihre Zeitzone GMT + 1 ist, ist der UTC-Wert von DateTime.MinValue in Ihrer Zeitzone eine Stunde weniger als DateTime.MinValue.

6
Adam Robinson

mit diesem Konstruktor:

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

beispielcode:

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
    }
5
Daniel Cai

Ich glaube, eine elegantere Methode besteht darin, den Serializer anzuweisen, den Standardwert für DateTime-Felder nicht auszugeben. Dadurch werden während der Übertragung einige Bytes und einige Verarbeitungsschritte für die Felder gespeichert, für die Sie keinen Wert haben . Beispiel:

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

oder Sie können Nullables verwenden. Beispiel:

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

Es hängt alles von den Anforderungen und Einschränkungen Ihres Projekts ab. Manchmal können Sie die Datentypen nicht einfach ändern. In diesem Fall können Sie trotzdem das DataMember-Attribut nutzen und die Datentypen beibehalten.

Wenn Sie im obigen Beispiel new Document() { Title = "Test Document" } auf der Serverseite haben, erhalten Sie bei der Serialisierung zu JSON {"Title": "Test Document"}, sodass es einfacher ist, mit JavaScript oder einem anderen Client auf der anderen Seite der Verbindung umzugehen. Wenn Sie in JavaScript JSON.Parse () verwenden und versuchen, es zu lesen, erhalten Sie undefined zurück. In typisierten Sprachen haben Sie je nach Typ den Standardwert für diese Eigenschaft (normalerweise das erwartete Verhalten).

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

Sie können dies während der Serialisierung über das Attribut OnSerializing und einige Überlegungen beheben:

[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());
    }
  }
}
0
Sebastian