2014-11-21 7 views
7

Dieses Problem wirkt sich mein ASP.Net WebAPI Patch-Methode, die viel wie folgt aussieht:Json.Net DeserializeObject mit OData.Delta versagt - nur ganze Zahlen

public MyModel Patch(int id, [FromBody]Delta<MyModel> newRecord){/*stuff here*/} 

Aber es ist nicht WebAPI das ist das Problem - das Scheitern ist zwischen Json.Net und OData.Delta.

Das Problem ist JsonConvert.DeserializeObject keine Ganzzahlen von OData.Delta-Objekte und ich frage mich, ob es eine Problemumgehung oder eine Lösung gibt, die ich anwenden kann.

UPDATE: Haben geschriebenen Code (siehe unten rechts) in der Json.Net-Bibliothek, die das beheben wird. Nur müssen sie im nächsten Update enthalten sein

UPDATE 2 (wenn James Newton-King es erlaubt): Nach weiteren Tests habe ich die beste Vorgehensweise entschieden wird, mit OData.Delta zu stoppen und schreiben meine eigene (siehe Antwort)

Unit-Tests besteht das Problem

Test 1 (unter Verwendung von Aussagen für Klarheit bewegt unten) zu beweisen: schlägt mit einem int (Int32):

class TestObjWithInt 
{ 
    public int Int { get; set; } 
} 
[TestMethod] 
public void IsApplied_When_IntIsDeserializedToDelta() 
{ 
    string testData = "{\"Int\":1}"; 
    var deserializedDelta = JsonConvert.DeserializeObject<Delta<TestObjWithInt>>(testData); 
    var result = deserializedDelta.GetChangedPropertyNames().Contains("Int"); 
    Assert.IsTrue(result); 
} 

Te 2 st: Gelingt es mit einem langen (Int64)

class TestObjWithLong 
{ 
    public long Long { get; set; } 
} 
[TestMethod] 
public void IsApplied_When_LongIsDeserializedToDelta() 
{ 
    string testData = "{\"Long\":1}"; 
    var deserializedDelta = JsonConvert.DeserializeObject<Delta<TestObjWithLong>>(testData); 
    var result = deserializedDelta.GetChangedPropertyNames().Contains("Long"); 
    Assert.IsTrue(result); 
} 

Und nur um sicher zu sein, dass die Deserialisierung mit, diese beiden Tests zu beginnen, funktioniert sowohl passieren.

[TestMethod] 
public void IsApplied_When_LongIsDeserializedToTestObject() 
{ 
    string testData = "{\"Long\":1}"; 
    var deserializedObject = JsonConvert.DeserializeObject<TestObjWithLong>(testData); 
    var result = deserializedObject.Long == 1; 
    Assert.IsTrue(result); 
} 
[TestMethod] 
public void IsApplied_When_IntIsDeserializedToTestObject() 
{ 
    string testData = "{\"Int\":1}"; 
    var deserializedObject = JsonConvert.DeserializeObject<TestObjWithInt>(testData); 
    var result = deserializedObject.Int == 1; 
    Assert.IsTrue(result); 
} 

fand ich this Bericht OData Fehler, wie ein ähnliches Problem klingt, aber seine alten und geschlossen, so wahrscheinlich nicht.

Jede Hilfe wäre großartig.

Mit Aussagen (von oben der Testdatei):

using System; 
using System.Linq; 
using System.Web.Http.OData; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using Newtonsoft.Json; 

Lösung, wenn sie von James Newton-King akzeptierte - Änderung 6.0.6 zu veröffentlichen. ersetzen JsonSerializerInternalReader.cs Linie 1581:

contract.TrySetMember(newObject, memberName, value); 

mit:

bool done = false; 
while (!(done = done || contract.TrySetMember(newObject, memberName, value))) 
{ 
    switch (reader.TokenType) 
    { 
     case JsonToken.Integer: 
      if (value is long && ((long)value) <= Int32.MaxValue && ((long)value) >= Int32.MinValue) 
       value = Convert.ToInt32(value); 
      //Add else if (...) to cast to other data types here (none additional required to date). 
      else 
       done = true; 
      break; 
     default: 
      done = true; 
      break; 
    } 
} 
+0

Falls es jemand hilft, gibt es einen Thread mit einer sehr ähnlichen Frage, wo ich habe eine einfachere, ‚hacken‘ fix. [Hier ist die Antwort.] (Http://StackOverflow.com/a/41749206/2283050) –

Antwort

3

OData.Delta <T> nicht mit Json.Net für eine beliebige Anzahl anderer Typen als Int64 arbeiten. Der einfachste Ansatz besteht darin, einen Ersatz für OData zu schreiben.Delta <T> (die ich auf Unternehmen Zeit gemacht habe, so kann ich es nicht in seiner Gesamtheit schreiben sorry) enthält Methoden wie folgt aus:

private bool TrySetInt32(object value, PropertyInfo propertyInfo, bool isNullable) 
{ 
    var done = false; 
    if (value is Int32) 
    { 
     propertyInfo.SetValue(_obj, value); 
     done = true; 
    } 
    else if (value == null) 
    { 
     if (isNullable) 
     { 
      propertyInfo.SetValue(_obj, value); 
      done = true; 
     } 
    } 
    else if (value is Int64) //Json.Net - fallback for numbers is an Int64 
    { 
     var val = (Int64)value; 
     if (val <= Int32.MaxValue && val >= Int32.MinValue) 
     { 
      done = true; 
      propertyInfo.SetValue(_obj, Convert.ToInt32(val)); 
     } 
    } 
    else 
    { 
     Int32 val; 
     done = Int32.TryParse(value.ToString(), out val); 
     if (done) 
      propertyInfo.SetValue(_obj, val); 
    } 
    return done; 
} 

Die Klasse kann eine dynamische generic so aussehen:

public sealed class Patchable<T> : DynamicObject where T : class, new() 

Mit einer Arbeitsvariable wie folgt aus:

T _obj = new T(); 

In der überschriebenen TrySetMember Methode müssen wir den zugrunde liegenden Typ der zu überprüfenden Eigenschaft mit Reflexion und rufen Sie die entsprechende TrySet ... Methode wie folgt:

if (underlyingType == typeof(Int16)) 
    done = TrySetInt16(value, propertyInfo, isNullable); 
else if (underlyingType == typeof(Int32)) 
    done = TrySetInt32(value, propertyInfo, isNullable); 

Wenn der Wert erfolgreich festgelegt ist, dass wir die Eigenschaftsnamen zu einer Liste hinzufügen können, die wir dann für das Patchen des ursprünglichen Datensatz wie folgt verwendet werden:

if (done) 
    _changedPropertyNames.Add(propertyInfo.Name); 

public void Patch(T objectToPatch) 
{ 
    foreach (var propertyName in _changedPropertyNames) 
    { 
     var propertyInfo = _obj.GetType().GetProperty(propertyName); 
     propertyInfo.SetValue(objectToPatch, propertyInfo.GetValue(_obj)); 
    } 
} 

68 Unit Tests später, scheint alles ziemlich gut zu funktionieren. Hier ein Beispiel:

class TestObjWithInt32 
{ 
    public Int32 Int32 { get; set; } 
    public Int32? SetNullable { get; set; } 
    public Int32? UnsetNullable { get; set; } 
} 
[TestMethod] 
public void IsApplied_When_Int32IsDeserializedToPatchable() 
{ 
    string testData = "{\"Int32\":1,\"SetNullable\":1}"; 
    var deserializedPatchable = JsonConvert.DeserializeObject<Patchable<TestObjWithInt32>>(testData); 
    var result = deserializedPatchable.ChangedPropertyNames.Contains("Int32"); 
    Assert.IsTrue(result); 
    var patchedObject = new TestObjWithInt32(); 
    Assert.AreEqual<Int32>(0, patchedObject.Int32); 
    deserializedPatchable.Patch(patchedObject); 
    Assert.AreEqual<Int32>(1, patchedObject.Int32); 
    Assert.IsNull(patchedObject.UnsetNullable); 
    Assert.IsNotNull(patchedObject.SetNullable); 
} 
1

Dies ist meine Implementierung für dieses Problem anhand von Rob Lösung:

public sealed class Patchable<T> : DynamicObject where T : class { 
    private readonly IDictionary<PropertyInfo, object> changedProperties = new Dictionary<PropertyInfo, object>(); 
    public override bool TrySetMember(SetMemberBinder binder, object value) { 
     var pro = typeof (T).GetProperty(binder.Name); 
     if (pro != null) 
      changedProperties.Add(pro, value); 
     return base.TrySetMember(binder, value); 
    } 
    public void Patch(T delta) { 
     foreach (var t in changedProperties) 
      t.Key.SetValue(
       delta, 
       t.Key.PropertyType.IsEnum ? Enum.Parse(t.Key.PropertyType, t.Value.ToString()) : Convert.ChangeType(t.Value, t.Key.PropertyType)); 
    } 
} 

ich die erforderlichen einem leeren Konstruktor in generischen Typparametern mit dem Wörterbuch anstelle einem zeitlichen Objekt entfernt .

Dank Rob;)

+0

Guter Versuch der Code-Minimierung, aber leider fehlgeschlagen 17 Unit-Tests einschließlich der in meiner Antwort. Das Problem ist, dass statt Json.Net eine InvalidCastException unterdrückt wurde, wurde es in die Patch-Methode verschoben. Meine Antwort (die zu einer langen Klasse führt) vermeidet Ausnahmen so weit wie möglich. – Rob

Verwandte Themen