JSON- und XML-Serialisierung in ASP.NET-Web-API
In diesem Artikel werden die JSON- und XML-Formatierer in ASP.NET-Web-API beschrieben.
In ASP.NET-Web-API ist ein Medienformatierer ein Objekt, das Folgendes ausführen kann:
- Lesen von CLR-Objekten aus einem HTTP-Nachrichtentext
- Schreiben von CLR-Objekten in einen HTTP-Nachrichtentext
Die Web-API stellt Medientypformatierer für JSON und XML bereit. Das Framework fügt diese Formatierer standardmäßig in die Pipeline ein. Clients können entweder JSON oder XML im Accept-Header der HTTP-Anforderung anfordern.
Inhalte
JSON-Media-Type-Formatierer
DIE JSON-Formatierung wird von der JsonMediaTypeFormatter-Klasse bereitgestellt. Standardmäßig verwendet JsonMediaTypeFormatter die Json.NET-Bibliothek , um die Serialisierung durchzuführen. Json.NET ist ein Open Source Projekt eines Drittanbieters.
Wenn Sie möchten, können Sie die JsonMediaTypeFormatter-Klasse so konfigurieren, dass der DataContractJsonSerializer anstelle von Json.NET verwendet wird. Legen Sie dazu die UseDataContractJsonSerializer-Eigenschaft auf true fest:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
JSON-Serialisierung
In diesem Abschnitt werden einige spezifische Verhaltensweisen des JSON-Formatierungsprogramms unter Verwendung des Standard-Json.NET Serialisierungsprogramms beschrieben. Hierbei handelt es sich nicht um eine umfassende Dokumentation der Json.NET Bibliothek; Weitere Informationen finden Sie in der Json.NET-Dokumentation.
Was wird serialisiert?
Standardmäßig sind alle öffentlichen Eigenschaften und Felder im serialisierten JSON enthalten. Um eine Eigenschaft oder ein Feld wegzulassen, dekorieren Sie sie mit dem JsonIgnore-Attribut .
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
[JsonIgnore]
public int ProductCode { get; set; } // omitted
}
Wenn Sie einen "Opt-In"-Ansatz bevorzugen, dekorieren Sie die Klasse mit dem DataContract-Attribut . Wenn dieses Attribut vorhanden ist, werden Member ignoriert, es sei denn, sie verfügen über dataMember. Sie können auch DataMember verwenden, um private Member zu serialisieren.
[DataContract]
public class Product
{
[DataMember]
public string Name { get; set; }
[DataMember]
public decimal Price { get; set; }
public int ProductCode { get; set; } // omitted by default
}
Read-Only Eigenschaften
Schreibgeschützte Eigenschaften werden standardmäßig serialisiert.
Datumsangaben
Standardmäßig schreibt Json.NET Datumsangaben im ISO 8601-Format . Datumsangaben in UTC (Koordinierte Weltzeit) werden mit dem Suffix "Z" geschrieben. Datumsangaben zur Ortszeit enthalten einen Zeitzonenoffset. Zum Beispiel:
2012-07-27T18:51:45.53403Z // UTC
2012-07-27T11:51:45.53403-07:00 // Local
Standardmäßig behält Json.NET die Zeitzone bei. Sie können dies überschreiben, indem Sie die DateTimeZoneHandling-Eigenschaft festlegen:
// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
Wenn Sie lieber das Microsoft JSON-Datumsformat ("\/Date(ticks)\/"
) anstelle von ISO 8601 verwenden möchten, legen Sie die DateFormatHandling-Eigenschaft für die Serialisierungseinstellungen fest:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
Einzug
Legen Sie zum Schreiben eingezogener JSON-Dateien die Formatierungseinstellung auf Formatierung.Einzug fest:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
Kamelgehäuse
Um JSON-Eigenschaftsnamen mit Camel-Großschreibung zu schreiben, ohne Das Datenmodell zu ändern, legen Sie camelCasePropertyNamesContractResolver für den Serialisierer fest:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
Anonyme und Weakly-Typed Objekte
Eine Aktionsmethode kann ein anonymes Objekt zurückgeben und in JSON serialisieren. Zum Beispiel:
public object Get()
{
return new {
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
}
Der Text der Antwortnachricht enthält den folgenden JSON-Code:
{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}
Wenn Ihre Web-API lose strukturierte JSON-Objekte von Clients empfängt, können Sie den Anforderungstext in einen Newtonsoft.Json.Linq.JObject-Typ deserialisieren.
public void Post(JObject person)
{
string name = person["Name"].ToString();
int age = person["Age"].ToObject<int>();
}
Es ist jedoch in der Regel besser, stark typisierte Datenobjekte zu verwenden. Dann müssen Sie die Daten nicht selbst analysieren, und Sie erhalten die Vorteile der Modellvalidierung.
Das XML-Serialisierungsprogramm unterstützt keine anonymen Typen oder JObject-Instanzen . Wenn Sie diese Features für Ihre JSON-Daten verwenden, sollten Sie den XML-Formatierer aus der Pipeline entfernen, wie weiter unten in diesem Artikel beschrieben.
XML Media-Type Formatierer
DIE XML-Formatierung wird von der XmlMediaTypeFormatter-Klasse bereitgestellt. Standardmäßig verwendet XmlMediaTypeFormatter die DataContractSerializer-Klasse , um die Serialisierung durchzuführen.
Wenn Sie möchten, können Sie xmlMediaTypeFormatter so konfigurieren, dass der XmlSerializer anstelle von DataContractSerializer verwendet wird. Legen Sie hierzu die UseXmlSerializer-Eigenschaft auf true fest:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
Die XmlSerializer-Klasse unterstützt einen engeren Satz von Typen als DataContractSerializer, bietet jedoch mehr Kontrolle über das resultierende XML. Erwägen Sie die Verwendung von XmlSerializer , wenn Sie einem vorhandenen XML-Schema entsprechen müssen.
XML-Serialisierung
In diesem Abschnitt werden einige spezifische Verhaltensweisen des XML-Formatierungsprogramms beschrieben, die den DataContractSerializer-Standard verwenden.
Der DataContractSerializer verhält sich standardmäßig wie folgt:
- Alle öffentlichen Lese-/Schreibeigenschaften und Felder werden serialisiert. Um eine Eigenschaft oder ein Feld wegzulassen, schmücken Sie es mit dem IgnoreDataMember-Attribut .
- Private und geschützte Member werden nicht serialisiert.
- Schreibgeschützte Eigenschaften werden nicht serialisiert. (Der Inhalt einer schreibgeschützten Auflistungseigenschaft wird jedoch serialisiert.)
- Klassen- und Membernamen werden in den XML-Code genau so geschrieben, wie sie in der Klassendeklaration angezeigt werden.
- Es wird ein XML-Standardnamespace verwendet.
Wenn Sie mehr Kontrolle über die Serialisierung benötigen, können Sie die Klasse mit dem DataContract-Attribut dekorieren. Wenn dieses Attribut vorhanden ist, wird die Klasse wie folgt serialisiert:
- Ansatz "Anmelden": Eigenschaften und Felder werden standardmäßig nicht serialisiert. Um eine Eigenschaft oder ein Feld zu serialisieren, dekorieren Sie es mit dem DataMember-Attribut .
- Um ein privates oder geschütztes Element zu serialisieren, dekorieren Sie es mit dem DataMember-Attribut .
- Schreibgeschützte Eigenschaften werden nicht serialisiert.
- Um die Darstellung des Klassennamens im XML-Code zu ändern, legen Sie den Parameter Name im DataContract-Attribut fest.
- Um die Darstellung eines Membernamens im XML-Code zu ändern, legen Sie den Parameter Name im DataMember-Attribut fest.
- Um den XML-Namespace zu ändern, legen Sie den Namespace-Parameter in der DataContract-Klasse fest.
Read-Only Eigenschaften
Schreibgeschützte Eigenschaften werden nicht serialisiert. Wenn eine schreibgeschützte Eigenschaft über ein privates Hintergrundfeld verfügt, können Sie das private Feld mit dem DataMember-Attribut markieren. Für diesen Ansatz ist das DataContract-Attribut für die -Klasse erforderlich.
[DataContract]
public class Product
{
[DataMember]
private int pcode; // serialized
// Not serialized (read-only)
public int ProductCode { get { return pcode; } }
}
Datumsangaben
Datumsangaben werden im ISO 8601-Format geschrieben. Beispiel: "2012-05-23T20:21:37.9116538Z".
Einzug
Legen Sie zum Schreiben eingezogener XML-Code die Eigenschaft Einzug auf true fest:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;
Festlegen Per-Type XML-Serialisierer
Sie können verschiedene XML-Serialisierer für verschiedene CLR-Typen festlegen. Beispielsweise verfügen Sie möglicherweise über ein bestimmtes Datenobjekt, für das XmlSerializer aus Gründen der Abwärtskompatibilität erforderlich ist. Sie können XmlSerializer für dieses Objekt und weiterhin DataContractSerializer für andere Typen verwenden.
Um ein XML-Serialisierungsprogramm für einen bestimmten Typ festzulegen, rufen Sie SetSerializer auf.
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));
Sie können einen XmlSerializer oder ein beliebiges Objekt angeben, das von XmlObjectSerializer abgeleitet wird.
Entfernen des JSON- oder XML-Formatters
Sie können den JSON-Formatierer oder den XML-Formatierer aus der Liste der Formatierer entfernen, wenn Sie sie nicht verwenden möchten. Die Standard Gründe hierfür sind:
- So beschränken Sie Ihre Web-API-Antworten auf einen bestimmten Medientyp. Beispielsweise können Sie sich entscheiden, nur JSON-Antworten zu unterstützen und den XML-Formatierer zu entfernen.
- So ersetzen Sie den Standardformatierer durch einen benutzerdefinierten Formatierer. Beispielsweise können Sie den JSON-Formatierer durch Ihre eigene benutzerdefinierte Implementierung eines JSON-Formatierers ersetzen.
Der folgende Code zeigt, wie Sie die Standardformatierer entfernen. Rufen Sie dies von Ihrer in Global.asax definierten Application_Start-Methode auf.
void ConfigureApi(HttpConfiguration config)
{
// Remove the JSON formatter
config.Formatters.Remove(config.Formatters.JsonFormatter);
// or
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
Behandeln von Zirkelobjektverweise
Standardmäßig schreiben die JSON- und XML-Formatierer alle Objekte als Werte. Wenn zwei Eigenschaften auf dasselbe Objekt verweisen oder dasselbe Objekt zweimal in einer Auflistung angezeigt wird, serialisiert der Formatierer das Objekt zweimal. Dies ist ein besonderes Problem, wenn Ihr Objektdiagramm Zyklen enthält, da das Serialisierungsprogramm eine Ausnahme auslöst, wenn eine Schleife im Graphen erkannt wird.
Betrachten Sie die folgenden Objektmodelle und controller.
public class Employee
{
public string Name { get; set; }
public Department Department { get; set; }
}
public class Department
{
public string Name { get; set; }
public Employee Manager { get; set; }
}
public class DepartmentsController : ApiController
{
public Department Get(int id)
{
Department sales = new Department() { Name = "Sales" };
Employee alice = new Employee() { Name = "Alice", Department = sales };
sales.Manager = alice;
return sales;
}
}
Wenn Sie diese Aktion aufrufen, löst der Formatierer eine Ausnahme aus, die sich in eine antwort vom status Code 500 (Interner Serverfehler) an den Client übersetzt.
Um Objektverweise in JSON beizubehalten, fügen Sie den folgenden Code Application_Start - Methode in der Datei Global.asax hinzu:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
Die Controlleraktion gibt nun JSON zurück, das wie folgt aussieht:
{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}
Beachten Sie, dass das Serialisierungsprogramm beiden Objekten eine Eigenschaft "$id" hinzufügt. Außerdem wird erkannt, dass die Employee.Department-Eigenschaft eine Schleife erstellt, sodass der Wert durch einen Objektverweis ersetzt wird: {"$ref":"1"}.
Hinweis
Objektverweise sind in JSON nicht standard. Bevor Sie dieses Feature verwenden, sollten Sie überlegen, ob Ihre Clients die Ergebnisse analysieren können. Es ist möglicherweise besser, einfach Zyklen aus dem Diagramm zu entfernen. Der Link vom Mitarbeiter zurück zur Abteilung wird in diesem Beispiel beispielsweise nicht wirklich benötigt.
Um Objektverweise in XML beizubehalten, haben Sie zwei Optionen. Die einfachere Option besteht darin, Ihrer Modellklasse hinzuzufügen [DataContract(IsReference=true)]
. Der IsReference-Parameter aktiviert Objektverweise. Denken Sie daran, dass DataContract die Serialisierung aktiviert, sodass Sie den Eigenschaften auch DataMember-Attribute hinzufügen müssen:
[DataContract(IsReference=true)]
public class Department
{
[DataMember]
public string Name { get; set; }
[DataMember]
public Employee Manager { get; set; }
}
Der Formatierer erzeugt nun XML ähnlich dem folgenden:
<Department xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="i1"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.datacontract.org/2004/07/Models">
<Manager>
<Department z:Ref="i1" />
<Name>Alice</Name>
</Manager>
<Name>Sales</Name>
</Department>
Wenn Sie Attribute für Ihre Modellklasse vermeiden möchten, gibt es eine weitere Option: Erstellen Sie ein neues typspezifisches DataContractSerializer-instance, und legen Sie preserveObjectReferences im Konstruktor auf true fest. Legen Sie dann diesen instance als Serialisierungsprogramm pro Typ auf dem XML-Medientypformatierer fest. Der folgende Code zeigt, wie Sie dies tun:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue,
false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);
Testen der Objektserialisierung
Beim Entwerfen Der Web-API ist es hilfreich, zu testen, wie Ihre Datenobjekte serialisiert werden. Sie können dies tun, ohne einen Controller zu erstellen oder eine Controlleraktion zu aufrufen.
string Serialize<T>(MediaTypeFormatter formatter, T value)
{
// Create a dummy HTTP Content.
Stream stream = new MemoryStream();
var content = new StreamContent(stream);
/// Serialize the object.
formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
// Read the serialized string.
stream.Position = 0;
return content.ReadAsStringAsync().Result;
}
T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
{
// Write the serialized string to a memory stream.
Stream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(str);
writer.Flush();
stream.Position = 0;
// Deserialize to an object of type T
return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
}
// Example of use
void TestSerialization()
{
var value = new Person() { Name = "Alice", Age = 23 };
var xml = new XmlMediaTypeFormatter();
string str = Serialize(xml, value);
var json = new JsonMediaTypeFormatter();
str = Serialize(json, value);
// Round trip
Person person2 = Deserialize<Person>(json, str);
}