WCF RESTサービス。 (HTTPステータス200 ???)。JSONシリアル化を自分で呼び出そうとすると、スローされる例外は次のとおりです。
SerializationException:DateTime.MaxValueより大きい、またはUTCに変換されたときに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はdefault(DateTime)なので、これを問題なくシリアル化できると期待しています。
RESTサービスを動作させる方法に関するヒントはありますか?DataContractを変更したくありません。
主な問題はDateTime.MinValue
はDateTimeKind.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
よりも1時間少なくなります。
このコンストラクタを使用して:
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; }
}
_
それはすべて、プロジェクトに必要な要件と制限に依存します。データ型を変更できない場合があります。その場合でも、DataMember
属性を利用して、データ型をそのまま保持できます。
上記の例では、サーバー側にnew Document() { Title = "Test Document" }
がある場合、JSONにシリアル化すると_{"Title": "Test Document"}
_が返されるため、JavaScriptまたは他のクライアントで簡単に処理できます。ワイヤーの。 JavaScriptでJSON.Parse()し、それを読み取ろうとすると、undefined
が返されます。型付き言語では、タイプに応じてそのプロパティのデフォルト値があります(通常、これは予想される動作です)。
_library.GetDocument(id).success(function(raw){
var document = JSON.Parse(raw);
var date = document.date; // date will be *undefined*
...
}
_
OnSerializing 属性とリフレクションを使用して、シリアル化中に修正できます。
[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());
}
}
}