Serializace JSON a XML ve webovém rozhraní API ASP.NET

Tento článek popisuje formátovací moduly JSON a XML v ASP.NET Web API.

V ASP.NET webovém rozhraní API je formátovací modul typu média objekt, který může:

  • Čtení objektů CLR z textu zprávy HTTP
  • Zápis objektů CLR do textu zprávy HTTP

Webové rozhraní API poskytuje formátovací moduly typu multimédií pro JSON i XML. Architektura ve výchozím nastavení vloží tyto formátovací moduly do kanálu. Klienti můžou v hlavičce Accept požadavku HTTP požádat o JSON nebo XML.

Obsah

Formátovací Media-Type JSON

Formátování JSON poskytuje třída JsonMediaTypeFormatter . JsonMediaTypeFormatter ve výchozím nastavení používá k serializaci knihovnu Json.NET. Json.NET je projekt open source třetí strany.

Pokud chcete, můžete nakonfigurovat JsonMediaTypeFormatter Třídy pro použití DataContractJsonSerializer místo Json.NET. Uděláte to tak, že vlastnost UseDataContractJsonSerializer nastavíte na hodnotu true:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;

Serializace JSON

Tato část popisuje některé konkrétní chování formátovače JSON pomocí výchozího Json.NET serializátoru. Nejedná se o komplexní dokumentaci knihovny Json.NET; Další informace najdete v dokumentaci k Json.NET.

Co se serializuje?

Ve výchozím nastavení jsou všechny veřejné vlastnosti a pole zahrnuté v serializovaném formátu JSON. Pokud chcete vynechat vlastnost nebo pole, ozdobte je atributem JsonIgnore .

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    [JsonIgnore]
    public int ProductCode { get; set; } // omitted
}

Pokud dáváte přednost přístupu "opt-in", ozdobte třídu atributem DataContract . Pokud je tento atribut přítomen, členové jsou ignorováni, pokud nemají DataMember. DataMember můžete také použít k serializaci soukromých členů.

[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 vlastnosti

Vlastnosti jen pro čtení jsou ve výchozím nastavení serializovány.

Kalendářní data

Ve výchozím nastavení Json.NET zapisuje data ve formátu ISO 8601 . Data ve standardu UTC (koordinovaný světový čas) se zapisují s příponou "Z". Kalendářní data v místním čase zahrnují posun časového pásma. Příklad:

2012-07-27T18:51:45.53403Z         // UTC
2012-07-27T11:51:45.53403-07:00    // Local

Ve výchozím nastavení Json.NET zachovává časové pásmo. Tuto možnost můžete přepsat nastavením vlastnosti DateTimeZoneHandling:

// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;

Pokud dáváte přednost použití formátu data Microsoft JSON ("\/Date(ticks)\/") místo ISO 8601, nastavte vlastnost DateFormatHandling v nastavení serializátoru:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling 
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;

Odsazení

Pokud chcete napsat odsazený JSON, nastavte formátování na Formatting.Indented:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

Velbloudí pouzdro

Pokud chcete psát názvy vlastností JSON s velkými písmeny beze změny datového modelu, nastavte camelCasePropertyNamesContractResolver na serializátoru:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

Anonymní a Weakly-Typed objekty

Metoda akce může vrátit anonymní objekt a serializovat ho do formátu JSON. Příklad:

public object Get()
{
    return new { 
        Name = "Alice", 
        Age = 23, 
        Pets = new List<string> { "Fido", "Polly", "Spot" } 
    };
}

Text zprávy odpovědi bude obsahovat následující kód JSON:

{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}

Pokud vaše webové rozhraní API přijímá od klientů volně strukturované objekty JSON, můžete text požadavku deserializovat na typ Newtonsoft.Json.Linq.JObject .

public void Post(JObject person)
{
    string name = person["Name"].ToString();
    int age = person["Age"].ToObject<int>();
}

Obvykle je ale lepší použít datové objekty se silnými typy. Pak nemusíte analyzovat data sami a získáte výhody ověření modelu.

Serializátor XML nepodporuje anonymní typy nebo instance JObject . Pokud tyto funkce používáte pro data JSON, měli byste z kanálu odebrat formátovací modul XML, jak je popsáno dále v tomto článku.

Formátovač Media-Type XML

Formátování XML je poskytováno XmlMediaTypeFormatter třída. Ve výchozím nastavení XmlMediaTypeFormatter používá DataContractSerializer třídy k provádění serializace.

Pokud chcete, můžete nakonfigurovat XmlMediaTypeFormatter na použití XmlSerializer místo DataContractSerializer. Uděláte to tak, že vlastnost UseXmlSerializer nastavíte na hodnotu true:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;

Třída XmlSerializer podporuje užší sadu typů než DataContractSerializer, ale poskytuje větší kontrolu nad výsledným XML. Pokud potřebujete spárovat existující schéma XML, zvažte použití XmlSerializeru .

Serializace XML

Tato část popisuje některé konkrétní chování formátovače XML pomocí výchozí DataContractSerializer.

Ve výchozím nastavení se DataContractSerializer chová takto:

  • Všechny veřejné vlastnosti a pole pro čtení a zápis jsou serializovány. Pokud chcete vynechat vlastnost nebo pole, přidejte k němu atribut IgnoreDataMember .
  • Soukromé a chráněné členy nejsou serializovány.
  • Vlastnosti jen pro čtení nejsou serializovány. (Obsah vlastnosti kolekce jen pro čtení je však serializován.)
  • Názvy tříd a členů jsou zapsány v XML přesně tak, jak jsou uvedeny v deklaraci třídy.
  • Použije se výchozí obor názvů XML.

Pokud potřebujete větší kontrolu nad serializací, můžete třídu ozdobit atributem DataContract . Při přítomnosti tohoto atributu je třída serializována následujícím způsobem:

  • Přístup "Přihlásit se": Vlastnosti a pole nejsou ve výchozím nastavení serializovány. Chcete-li serializovat vlastnost nebo pole, ozdobte ho atributem DataMember .
  • Chcete-li serializovat soukromý nebo chráněný člen, ozdobte ho atributem DataMember .
  • Vlastnosti jen pro čtení nejsou serializovány.
  • Chcete-li změnit, jak se název třídy zobrazí v XML, nastavte parametr Name v atributu DataContract .
  • Pokud chcete změnit způsob zobrazení názvu člena v XML, nastavte parametr Name v atributu DataMember .
  • Chcete-li změnit obor názvů XML, nastavte parametr Obor názvů ve třídě DataContract .

Read-Only vlastnosti

Vlastnosti jen pro čtení nejsou serializovány. Pokud má vlastnost jen pro čtení záložní privátní pole, můžete ho označit atributem DataMember . Tento přístup vyžaduje atribut DataContract ve třídě.

[DataContract]
public class Product
{
    [DataMember]
    private int pcode;  // serialized

    // Not serialized (read-only)
    public int ProductCode { get { return pcode; } }
}

Kalendářní data

Data jsou zapsána ve formátu ISO 8601. Například "2012-05-23T20:21:37.9116538Z".

Odsazení

Pokud chcete zapsat odsazený KÓD XML, nastavte vlastnost Odsazení na hodnotu true:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;

Nastavení serializátorů XML Per-Type

Můžete nastavit různé serializátory XML pro různé typy CLR. Například můžete mít konkrétní datový objekt, který vyžaduje XmlSerializer pro zpětnou kompatibilitu. Můžete použít XmlSerializer pro tento objekt a nadále používat DataContractSerializer pro jiné typy.

Chcete-li nastavit serializátor XML pro konkrétní typ, zavolejte SetSerializer.

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));

Můžete zadat XmlSerializer nebo jakýkoli objekt, který je odvozen z XmlObjectSerializer.

Odebrání formátu JSON nebo XML

Pokud je nechcete použít, můžete formátovací modul JSON nebo XML odebrat ze seznamu formátovacích prostředků. Hlavní důvody, proč to udělat, jsou:

  • Omezení odpovědí webového rozhraní API na konkrétní typ média Můžete se například rozhodnout, že budete podporovat pouze odpovědi JSON, a odebrat formátovací modul XML.
  • Chcete-li nahradit výchozí formátovací prvek vlastním formátovačem. Formátovací kód JSON můžete například nahradit vlastní implementací formátu JSON.

Následující kód ukazuje, jak odebrat výchozí formátovací moduly. Volejte tuto metodu z Application_Start definované v souboru Global.asax.

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

Zpracování cyklických odkazů na objekty

Ve výchozím nastavení zapisují formátovací moduly JSON a XML všechny objekty jako hodnoty. Pokud dvě vlastnosti odkazují na stejný objekt nebo pokud se stejný objekt vyskytuje dvakrát v kolekci, formátovací modul serializuje objekt dvakrát. Jedná se o konkrétní problém, pokud graf objektů obsahuje cykly, protože serializátor vyvolá výjimku, když zjistí smyčku v grafu.

Zvažte následující objektové modely a kontroler.

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

Vyvolání této akce způsobí, že formatter vyvolá výjimku, která se přeloží na stavový kód 500 (Vnitřní chyba serveru) pro klienta.

Pokud chcete zachovat odkazy na objekty ve formátu JSON, přidejte do metody Application_Start v souboru Global.asax následující kód:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.All;

Akce kontroleru teď vrátí JSON, který vypadá takto:

{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}

Všimněte si, že serializátor přidá vlastnost "$id" do obou objektů. Zjistí také, že vlastnost Employee.Department vytvoří smyčku, takže nahradí hodnotu odkazem na objekt: {"$ref":"1"}.

Poznámka

Odkazy na objekty nejsou ve formátu JSON standardní. Před použitím této funkce zvažte, jestli vaši klienti budou moct analyzovat výsledky. Možná bude lepší jednoduše odebrat cykly z grafu. V tomto příkladu například není ve skutečnosti potřeba propojení mezi zaměstnancem a oddělením.

Chcete-li zachovat odkazy na objekty v xml, máte dvě možnosti. Jednodušší možností je přidat [DataContract(IsReference=true)] do třídy modelu. IsReference Parametr povoluje odkazy na objekty. Mějte na paměti, že DataContract umožňuje serializaci, takže budete také muset přidat atributy DataMember do vlastností:

[DataContract(IsReference=true)]
public class Department
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public Employee Manager { get; set; }
}

Formátovací nástroj teď vytvoří KÓD XML podobný následujícímu:

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

Pokud se chcete vyhnout atributům ve třídě modelu, existuje další možnost: Vytvořte novou instanci DataContractSerializer specifického typu a nastavte preserveObjectReferences na hodnotu true v konstruktoru. Pak nastavte tuto instanci jako serializátor pro jednotlivé typy na formátovacím modulu média XML. Následující kód ukazuje, jak to udělat:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);

Testování serializace objektů

Při návrhu webového rozhraní API je užitečné otestovat, jak se budou serializovat datové objekty. Můžete to udělat bez vytvoření kontroleru nebo vyvolání akce kontroleru.

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