JSON- och XML-serialisering i ASP.NET webb-API

I den här artikeln beskrivs JSON- och XML-formateringarna i ASP.NET Webb-API.

I ASP.NET webb-API är en formatering av medietyp ett objekt som kan:

  • Läsa CLR-objekt från en HTTP-meddelandetext
  • Skriva CLR-objekt till en HTTP-meddelandetext

Webb-API:et tillhandahåller formaterare av medietyp för både JSON och XML. Ramverket infogar dessa formaterare i pipelinen som standard. Klienter kan begära antingen JSON eller XML i accepthuvudet för HTTP-begäran.

Innehåll

JSON-Media-Type formatering

JSON-formatering tillhandahålls av klassen JsonMediaTypeFormatter . Som standard använder JsonMediaTypeFormatterJson.NET-biblioteket för att utföra serialisering. Json.NET är ett projekt med öppen källkod från tredje part.

Om du vill kan du konfigurera klassen JsonMediaTypeFormatter så att den använder DataContractJsonSerializer i stället för Json.NET. Om du vill göra det anger du egenskapen UseDataContractJsonSerializer till true:

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

JSON-serialisering

I det här avsnittet beskrivs några specifika beteenden för JSON-formatören med hjälp av standard-Json.NET serialiserare. Detta är inte avsett att vara omfattande dokumentation om Json.NET biblioteket; Mer information finns i dokumentationen om Json.NET.

Vad serialiseras?

Som standard inkluderas alla offentliga egenskaper och fält i den serialiserade JSON-filen. Om du vill utelämna en egenskap eller ett fält dekorerar du det med attributet JsonIgnore .

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

Om du föredrar en "opt-in"-metod kan du dekorera klassen med attributet DataContract . Om det här attributet finns ignoreras medlemmar om de inte har DataMember. Du kan också använda DataMember för att serialisera privata medlemmar.

[DataContract]
public class Product
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public decimal Price { get; set; }
    public int ProductCode { get; set; }  // omitted by default
}

Skrivskyddade egenskaper

Skrivskyddade egenskaper serialiseras som standard.

Datum

Som standard skriver Json.NET datum i ISO 8601-format . Datum i UTC (Coordinated Universal Time) skrivs med suffixet "Z". Datum i lokal tid inkluderar en tidszonsförskjutning. Som exempel:

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

Som standard bevarar Json.NET tidszonen. Du kan åsidosätta detta genom att ange egenskapen DateTimeZoneHandling:

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

Om du föredrar att använda Microsoft JSON-datumformat ("\/Date(ticks)\/") i stället för ISO 8601 anger du egenskapen DateFormatHandling på serialiserarinställningarna:

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

Indrag

Om du vill skriva indraget JSON anger du formateringsinställningen till Formatering.Indrag:

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

Kamelhölje

Om du vill skriva JSON-egenskapsnamn med kamelhölje, utan att ändra datamodellen, anger du CamelCasePropertyNamesContractResolver på serialiseraren:

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

Anonyma och svagt typade objekt

En åtgärdsmetod kan returnera ett anonymt objekt och serialisera det till JSON. Som exempel:

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

Brödtexten för svarsmeddelandet innehåller följande JSON:

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

Om ditt webb-API tar emot löst strukturerade JSON-objekt från klienter kan du deserialisera begärandetexten till en Newtonsoft.Json.Linq.JObject-typ .

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

Det är dock vanligtvis bättre att använda starkt skrivna dataobjekt. Sedan behöver du inte parsa data själv och du får fördelarna med modellvalidering.

XML-serialiseraren stöder inte anonyma typer eller JObject-instanser . Om du använder de här funktionerna för dina JSON-data bör du ta bort XML-formatören från pipelinen enligt beskrivningen senare i den här artikeln.

XML-Media-Type formatter

XML-formatering tillhandahålls av klassen XmlMediaTypeFormatter . Som standard använder XmlMediaTypeFormatter klassen DataContractSerializer för att utföra serialisering.

Om du vill kan du konfigurera XmlMediaTypeFormatter att använda XmlSerializer i stället för DataContractSerializer. Om du vill göra det anger du egenskapen UseXmlSerializer till true:

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

Klassen XmlSerializer stöder en smalare uppsättning typer än DataContractSerializer, men ger mer kontroll över den resulterande XML-koden. Överväg att använda XmlSerializer om du behöver matcha ett befintligt XML-schema.

XML-serialisering

I det här avsnittet beskrivs vissa specifika beteenden hos XML-tolkaren med hjälp av standard DataContractSerializer.

DataContractSerializer fungerar som standard på följande sätt:

  • Alla offentliga läs-/skrivegenskaper och fält serialiseras. Om du vill utelämna en egenskap eller ett fält dekorerar du det med attributet IgnoreDataMember .
  • Privata och skyddade medlemmar serialiseras inte.
  • Skrivskyddade egenskaper serialiseras inte. Innehållet i en skrivskyddad samlingsegenskap serialiseras dock.
  • Klass- och medlemsnamn skrivs i XML precis som de visas i klassdeklarationen.
  • Ett XML-standardnamnområde används.

Om du behöver mer kontroll över serialiseringen kan du dekorera klassen med attributet DataContract . När det här attributet finns serialiseras klassen på följande sätt:

  • Metoden "Anmäl dig": Egenskaper och fält serialiseras inte som standard. Om du vill serialisera en egenskap eller ett fält dekorerar du den med attributet DataMember .
  • Om du vill serialisera en privat eller skyddad medlem kan du dekorera den med attributet DataMember .
  • Skrivskyddade egenskaper serialiseras inte.
  • Om du vill ändra hur klassnamnet visas i XML anger du parametern Namn i attributet DataContract .
  • Om du vill ändra hur ett medlemsnamn visas i XML anger du parametern Namn i attributet DataMember .
  • Om du vill ändra XML-namnområdet anger du parametern Namnområde i klassen DataContract .

Skrivskyddade egenskaper

Skrivskyddade egenskaper serialiseras inte. Om en skrivskyddad egenskap har ett bakomliggande privat fält kan du markera det privata fältet med attributet DataMember. Den här metoden kräver attributet DataContract för klassen.

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

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

Datum

Datum skrivs i ISO 8601-format. Till exempel "2012-05-23T20:21:37.9116538Z".

Indrag

Om du vill skriva indraget XML anger du egenskapen Indent till true:

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

Ange Per-Type XML-serialiserare

Du kan ange olika XML-serialiserare för olika CLR-typer. Du kan till exempel ha ett visst dataobjekt som kräver XmlSerializer för bakåtkompatibilitet. Du kan använda XmlSerializer för det här objektet och fortsätta att använda DataContractSerializer för andra typer.

Om du vill ange en XML-serialiserare för en viss typ anropar du SetSerializer.

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

Du kan ange en XmlSerializer eller ett objekt som härleds från XmlObjectSerializer.

Ta bort JSON- eller XML-formatering

Du kan ta bort JSON-formaterare eller XML-formaterare från listan över formatare om du inte vill använda dem. De främsta orsakerna till detta är:

  • Begränsa dina webb-API-svar till en viss medietyp. Du kan till exempel välja att endast stödja JSON-svar och ta bort XML-formatering.
  • Ersätt standardformaterare med en anpassad formaterare. Du kan till exempel ersätta JSON-formatören med din egen anpassade implementering av en JSON-formaterare.

Följande kod visar hur du tar bort standardformatrarna. Anropa detta från din Application_Start-metod , definierad i 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);
}

Hantering av cirkulära objektreferenser

Som standard skriver JSON- och XML-formatrarna alla objekt som värden. Om två egenskaper refererar till samma objekt, eller om samma objekt visas två gånger i en samling, serialiserar formateren objektet två gånger. Det här är ett särskilt problem om objektdiagrammet innehåller cykler, eftersom serialiseraren utlöser ett undantag när den identifierar en loop i diagrammet.

Tänk på följande objektmodeller och styrenhet.

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

Om du anropar den här åtgärden kommer ett undantag att utlösas, vilket resulterar i ett statuskod 500 (Internt Serverfel) svar till klienten.

Om du vill bevara objektreferenser i JSON lägger du till följande kod i Application_Start-metoden i filen Global.asax:

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

Nu returnerar kontrollantåtgärden JSON som ser ut så här:

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

Observera att serialiseraren lägger till egenskapen "$id" i båda objekten. Dessutom identifieras att egenskapen Employee.Department skapar en loop, så den ersätter värdet med en objektreferens: {"$ref":"1"}.

Anmärkning

Objektreferenser är inte standard i JSON. Innan du använder den här funktionen bör du överväga om dina klienter kommer att kunna parsa resultatet. Det kan vara bättre att helt enkelt ta bort cykler från diagrammet. Till exempel behövs inte länken från Medarbetare tillbaka till Avdelningen i det här exemplet.

Om du vill bevara objektreferenser i XML har du två alternativ. Det enklare alternativet är att lägga [DataContract(IsReference=true)] till i modellklassen. Parametern IsReference aktiverar objektreferenser. Kom ihåg att DataContract gör serialiseringsanmälning, så du måste också lägga till DataMember-attribut i egenskaperna:

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

Nu kommer formateren att producera XML som liknar följande:

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

Om du vill undvika attribut i modellklassen finns det ett annat alternativ: Skapa en ny typspecifik DataContractSerializer-instans och ange preserveObjectReferences till true i konstruktorn. Ange sedan den här instansen som serialiserare per typ på XML-medieformatören. Följande kod visar hur du gör detta:

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

Testa objektserialisering

När du utformar webb-API:et är det användbart att testa hur dina dataobjekt ska serialiseras. Du kan göra detta utan att skapa en kontrollant eller anropa en kontrollantåtgärd.

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