2010-10-26 7 views
48

Występują problemy z usługą REST WCF. Obiekt wire, który próbuję zwrócić, nie ma określonych właściwości, co powoduje, że DateTime.MinValue dla właściwości typu DateTime. Usługa zwraca pusty dokument (ze statusem HTTP 200 ???). Kiedy próbuję zadzwonić JSON serializacji się wyjątek, który jest generowany jest:Dlaczego funkcja DateTime.MinValue może nie być serializowana w strefach czasowych przed UTC?

SerializationException: wartości DateTime, które są większe niż parametr DateTime.MaxValue lub mniejszy niż DateTime.MinValue po przeliczeniu na UTC nie mogą być szeregowane do JSON.

To może być powielana uruchamiając następujący kod w app konsoli:

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

Dlaczego jest to zachowanie? Myślę, że jest to związane z moją strefą czasową (GMT + 1). Ponieważ wartość DateTime.MinValue jest domyślna (DateTime), spodziewam się, że może to być serializowane bez problemów.

Wszelkie wskazówki dotyczące sposobu, aby moje usługi REST zachowywać? Nie chcę zmienić mojej DataContract.

+0

Czy można ustawić wartość DateTime jako zerową i użyć wartości NULL jako wartości domyślnej? – Gabe

+0

@Gabe: Chyba mogłem. Czuję się głupio, aby zmienić mój typ, aby obejść szczegóły serializacji. Ale to chyba najbardziej pragmatyczna droga. –

Odpowiedz

61

Głównym problemem jest DateTime.MinValue ma DateTimeKind.Unspecified rodzaju. Jest on zdefiniowany jako:

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

Ale to nie jest prawdziwy problem, ta definicja prowadzi do problemu podczas serializacji. JSON DateTime serializacji odbywa się poprzez:

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

niestety jest zdefiniowany jako:

... 

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

... 

Więc to nie uwzględnia Unspecified i traktuje ją jako Local. Aby uniknąć takiej sytuacji można zdefiniować własne Constant:

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

lub

MinValueUtc = DateTime.MinValue.ToUniversalTime(); 

To wygląda dziwnie, oczywiście, ale to pomaga.

+0

To miłe wyjaśnienie. To wyjaśnia również, dlaczego serializacja XML działa, a JSON nie. Mój problem polega na tym, że MinValue istnieje, ponieważ nie jest ustawiona. Zamiast dodawać prosty atrybut, będę musiał ustawić tę niestandardową MinValueUtc na wszystkie właściwości datetime. –

+0

Możesz rozważyć pracę (lub po prostu przechowywać) z datetimes zawsze w UTC konwersji do LocalTime tuż przed ich wyświetleniem. Więc po init albo w konstruktorze, albo po prostu w odpowiednim ustawiaczu, przekonwertuj "przychodzącą" wartość datetime na UTC przez .ToUniversalTime(). Pomaga rozwiązać problem z domyślną wartością. –

+0

to nie działa dla mnie, ustawienie wartości na nowy DateTime (0L, DateTimeKind.Utc); również powoduje awarię serializacji – Matus

6

Jeśli Twoja strefa czasowa to GMT + 1, wówczas wartość UTC wynosząca DateTime.MinValue w Twojej strefie czasowej będzie o godzinę niższa niż DateTime.MinValue.

+7

Tak, pomyślałem tyle. Ale co robić? Czy to nie dziwne, że domyślna wartość bardzo popularnej klasy z frameworka nie może być serializowana w połowie świata? –

5

użyciu tego konstruktora:

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

przykładowy kod:

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 
    } 
+0

Więc gdzie powinienem użyć tego konstruktora? Właśnie dodałem WebMessageFormat.Json do atrybutu WebInvoke. Jakieś wskazówki, jak połączyć tę technikę w stylu deklaratywnym? –

13

Spróbuj dodać to na każdym DateTime Członek

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

Większość tych erros dzieje, ponieważ wartość domyślna datetime jest DateTime.MinValue, który jest z roku 1 i JSON se rializacja pochodzi z 1970 roku.

0

Można ustalić, że podczas serializacji poprzez atrybut OnSerializing i trochę refleksji:

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

wierzę bardziej eleganckim sposobem jest poinstruowanie serializatora nie emitują wartość domyślną dla pól DateTime. Pozwoli to zaoszczędzić bajt podczas transferu i trochę przetwarzania podczas serializacji dla pól, które nie mają dla nich żadnej wartości. Przykład:

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

lub możesz użyć Nullables. Przykład:

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

Wszystko zależy od wymagań i ograniczeń, które możesz mieć w swoim projekcie. Czasami nie można po prostu zmienić typów danych. W takim przypadku możesz nadal korzystać z atrybutu DataMember i zachowywać nienaruszone typy danych.

W powyższym przykładzie, jeśli masz new Document() { Title = "Test Document" } po stronie serwera, po serializacji do JSON da ci to {"Title": "Test Document"}, więc łatwiej będzie sobie z nim poradzić w JavaScript lub innym kliencie po drugiej stronie drutu. W JavaScript, jeśli JSON.Parse() to, i spróbuj go przeczytać, dostaniesz z powrotem undefined. W językach pisanych na klawiaturze będziesz miał domyślną wartość dla tej właściwości w zależności od typu (zwykle jest to oczekiwane zachowanie).

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

Dzięki. EmitDefaultValue pracował dla mnie. – Johan

+0

Nie ma za co.Poprawiłem odpowiedź, aby porozmawiać o stronie klienta. Ktoś może uznać to za przydatne pewnego dnia;) – Reza

Powiązane problemy