Ich sehe ein JSON-Deserialisierungsproblem, das ich nicht erklären oder beheben kann.
{'items':[{'sid':3157,'name':'a name','items':[{'sid':3158,'name':'child name','data':{'d1':2,'d2':null,'d3':2}}]}]}
Die gleiche JSON-Zeichenfolge wird, wenn sie an die Aktion Save
gesendet wird, nicht auf die gleiche Weise deserialisiert, speziell die Werte D1, D2 und D3 werden alle auf NULL gesetzt. Immer.
Was passiert hier und wie kann ich es beheben?
Es klingt vielleicht kontraintuitiv, aber Sie sollten diese Doppelgänger als Zeichenfolgen im json senden:
%Vor%Hier ist mein vollständiger Testcode, der diese Controller-Aktion mit AJAX aufruft und die Bindung an jeden Wert des Modells ermöglicht:
%Vor%Um nur das Ausmaß des Problems zu veranschaulichen, wie numerische Typen aus JSON deserialisiert werden können, nehmen wir ein paar Beispiele:
public double? Foo { get; set; }
= & gt; Foo = null { foo: 2 }
= & gt; Foo = null { foo: 2.0 }
= & gt; Foo = null { foo: 2.5 }
= & gt; Foo = 2,5 { foo: '2.5' }
= & gt; Foo = null public float? Foo { get; set; }
= & gt; Foo = null { foo: 2 }
= & gt; Foo = null { foo: 2.0 }
= & gt; Foo = 2,5 { foo: 2.5 }
= & gt; Foo = null { foo: '2.5' }
= & gt; Foo = null public decimal? Foo { get; set; }
= & gt; Foo = 2,5 { foo: 2 }
= & gt; Foo = 2,5 Nun machen wir dasselbe mit nicht nullbaren Typen:
{ foo: 2.0 }
= & gt; Foo = 2,0 { foo: 2.5 }
= & gt; Foo = 2,0 { foo: '2.5' }
= & gt; Foo = 2,5 public double Foo { get; set; }
= & gt; Foo = 2,5 { foo: 2 }
= & gt; Foo = 2,0 { foo: 2.0 }
= & gt; Foo = 2,0 { foo: 2.5 }
= & gt; Foo = 2,5 { foo: '2.5' }
= & gt; Foo = 2,5 public float Foo { get; set; }
= & gt; Foo = 0 { foo: 2 }
= & gt; Foo = 0 { foo: 2.0 }
= & gt; Foo = 2,5 { foo: 2.5 }
= & gt; Foo = 2,5 Fazit: Das Deserialisieren numerischer Typen aus JSON ist ein großes Durcheinander. Verwenden Sie Zeichenfolgen in JSON. Und natürlich, wenn Sie Zeichenfolgen verwenden, seien Sie vorsichtig mit dem Dezimaltrennzeichen, da es kulturabhängig ist.
Ich wurde im Kommentarbereich gefragt, warum Unit-Tests bestanden werden, funktioniert aber nicht in ASP.NET MVC. Die Antwort ist einfach: Es ist, weil ASP.NET MVC viel mehr Dinge tut als einen einfachen Aufruf an eine { foo: '2.5' }
, was der Komponententest tut. Sie vergleichen im Grunde Äpfel mit Orangen.
Lassen Sie uns tiefer in das Geschehen eintauchen. In ASP.NET MVC 3 gibt es eine integrierte public decimal Foo { get; set; }
, die intern die Klasse { foo: 2 }
verwendet, um JSON zu deserialisieren. Dies funktioniert, wie Sie bereits gesehen haben, im Komponententest. Aber in ASP.NET MVC steckt noch viel mehr dahinter, da es auch einen Standardmodellbinder verwendet, der für die Instanziierung Ihrer Aktionsparameter zuständig ist.
Und wenn Sie sich den Quellcode von ASP.NET MVC 3 und genauer die Klasse DefaultModelBinder.cs ansehen, werden Sie die folgende Methode bemerken, die für jede Eigenschaft aufgerufen wird, für die ein Wert festgelegt werden soll:
%Vor%Konzentrieren wir uns genauer auf die folgende Zeile:
%Vor% Wenn Sie annehmen, dass Sie eine Eigenschaft vom Typ { foo: 2.0 }
hatten, sehen Sie, wie dies aussehen würde, wenn Sie Ihre Anwendung debuggen:
Keine Überraschungen hier. Unser Zieltyp ist { foo: 2.5 }
, weil wir das in unserem Ansichtsmodell verwendet haben.
Sehen Sie sich dann { foo: '2.5' }
:
Sehen Sie diese JavaScriptSerializer.Deserialize
Eigenschaft da draußen? Kannst du seinen Typ erraten?
Diese Methode löst also einfach eine Ausnahme aus, weil sie offensichtlich den JsonValueProviderFactory
Wert von JavaScriptDeserializer
nicht in einen Nullable<double>
konvertieren kann.
Bemerken Sie, welcher Wert in diesem Fall zurückgegeben wird? Aus diesem Grund haben Sie in Ihrem Modell double?
.
Das ist sehr einfach zu überprüfen. Überprüfen Sie einfach die Eigenschaft valueProviderResult
in Ihrer Controller-Aktion und Sie werden feststellen, dass es RawValue
ist. Und wenn Sie den Modellfehler untersuchen, der dem Modellstatus hinzugefügt wurde, sehen Sie Folgendes:
Die Parameterkonvertierung vom Typ 'System.Decimal' in type 'System.Nullable'1 [[System.Double, mscorlib, Version = 4.0.0.0, Kultur = neutral, PublicKeyToken = b77a5c561934e089]] 'gescheitert, weil nein Typwandler kann zwischen diesen Typen konvertieren.
Sie können jetzt fragen: "Aber warum ist die RawValue-Eigenschaft innerhalb des ValueProviderResult vom Typ dezimal?". Noch einmal liegt die Antwort im ASP.NET MVC 3-Quellcode (ja, Sie sollten es bereits heruntergeladen haben). Sehen wir uns die decimal
-Datei und genauer die 2.5
-Methode an:
Bemerken Sie die folgende Zeile:
%Vor%Können Sie raten, was das folgende Snippet auf Ihrer Konsole drucken wird?
%Vor% Ja, Sie haben richtig geraten, es ist ein double?
.
Sie können jetzt fragen: "Aber warum haben sie die serializer.DeserializeObject-Methode anstelle von serializer.Deserialize wie in meinem Komponententest verwendet?" Das liegt daran, dass das ASP.NET MVC-Team die Designentscheidung zur Implementierung der JSON-Anforderungsbindung mit Hilfe von null
getroffen hat, die den Typ Ihres Modells nicht kennt.
Sehen Sie jetzt, wie sich Ihr Komponententest vollständig von dem unterscheidet, was wirklich unter ASP.NET MVC 3 passiert? Was sollte normalerweise erklären, warum es passiert und warum die Controller-Aktion keinen korrekten Modellwert erhält?
Lösung 1: Übergeben Sie Daten, die als "application / x-www-form-urlencoded" gekennzeichnet sind. In diesem Fall wird Nullable<double>
korrekt deserialisiert.
Beispiel:
Lösung 2: doppelter Wechsel? zu dezimal? und sende Inhalte als "application / json". Danke an Darin Untersuchung
Tags und Links asp.net-mvc-3 json model-binding