web-dev-qa-db-fra.com

Pourquoi DateTime.MinValue ne peut-il pas être sérialisé dans les fuseaux horaires avant UTC?

Je rencontre des problèmes avec un service WCF REST. Certaines propriétés non définies de l'objet fil que j'essaie de renvoyer, ce qui entraîne DateTime.MinValue pour les propriétés de type DateTime. Le service renvoie un document vide (avec le statut HTTP 200 ???). Lorsque j'essaie d'appeler moi-même la sérialisation JSON, l'exception levée est la suivante:

SerializationException: les valeurs DateTime supérieures à DateTime.MaxValue ou inférieures à DateTime.MinValue lors de la conversion en UTC ne peuvent pas être sérialisées en JSON.

Cela peut être reproduit en exécutant le code suivant dans une application console:

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

Pourquoi ce comportement? Je pense que cela est lié à mon fuseau horaire (GMT + 1). Comme DateTime.MinValue est la valeur par défaut (DateTime), je pense que cela peut être sérialisé sans problèmes.

Des conseils pour que mon service REST se comporte? Je ne veux pas changer mon DataContract.

49
Teun D

Le problème principal est que DateTime.MinValue a le type DateTimeKind.Unspecified. Il est défini comme:

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

Mais ce n’est pas un réel problème, cette définition pose problème lors de la sérialisation. Sérialisation JSON DateTime effectuée via:

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

Malheureusement, il est défini comme:

...

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

...

Donc, il ne prend pas en compte Unspecified et le traite comme Local. Pour éviter cette situation, vous pouvez définir votre propre constante:

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

ou 

MinValueUtc = DateTime.MinValue.ToUniversalTime();

Cela a l'air bizarre, bien sûr, mais ça aide.

64
Nick Martyshchenko

Essayez d'ajouter ceci à n'importe quel membre DateTime

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

La plupart de ces erreurs se produisent parce que la valeur par défaut de datetime est DateTime.MinValue, qui correspond à l'année 1 et que la sérialisation JSON correspond à l'année 1970.

13
user1505015

Si votre fuseau horaire est GMT + 1, la valeur UTC de DateTime.MinValue dans votre fuseau horaire sera une heure inférieure à DateTime.MinValue.

6
Adam Robinson

en utilisant ce constructeur:

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

exemple de code:

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

Je pense qu'une méthode plus élégante consiste à indiquer au sérialiseur de ne pas émettre la valeur par défaut pour les champs DateTime. Cela économisera quelques octets lors du transfert et du traitement lors de la sérialisation des champs pour lesquels vous n’avez aucune valeur pour eux . Exemple:

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

ou vous pouvez utiliser Nullables. Exemple:

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

Tout dépend des exigences et des restrictions que vous pourriez avoir dans votre projet. Parfois, vous ne pouvez pas simplement changer les types de données. Dans ce cas, vous pouvez toujours tirer parti de l'attribut DataMember et conserver les types de données intacts.

Dans l'exemple ci-dessus, si vous avez new Document() { Title = "Test Document" } du côté serveur, une fois sérialisé en JSON, vous obtiendrez {"Title": "Test Document"} afin qu'il soit plus facile de gérer en JavaScript ou tout autre client de l'autre côté du fil. En JavaScript, si vous JSON.Parse () et essayez de le lire, vous obtiendrez undefined. Dans les langages typés, vous aurez la valeur par défaut pour cette propriété en fonction du type (qui correspond généralement au comportement attendu).

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

Vous pouvez résoudre ce problème pendant la sérialisation via l'attribut OnSerializing et une réflexion:

[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