Condividi tramite


Serializzazione JSON e XML in API Web ASP.NET

Questo articolo descrive i formattatori JSON e XML in API Web ASP.NET.

In API Web ASP.NET un formattatore di tipo multimediale è un oggetto che può:

  • Leggere oggetti CLR da un corpo del messaggio HTTP
  • Scrivere oggetti CLR in un corpo del messaggio HTTP

L'API Web fornisce formattatori di tipo multimediale sia per JSON che per XML. Il framework inserisce questi formattatori nella pipeline per impostazione predefinita. I client possono richiedere JSON o XML nell'intestazione Accept della richiesta HTTP.

Contenuto

Formattatore JSON Media-Type

La formattazione JSON viene fornita dalla classe JsonMediaTypeFormatter . Per impostazione predefinita, JsonMediaTypeFormatter usa la libreria Json.NET per eseguire la serializzazione. Json.NET è un progetto di open source di terze parti.

Se si preferisce, è possibile configurare la classe JsonMediaTypeFormatter in modo da usare DataContractJsonSerializer anziché Json.NET. A tale scopo, impostare la proprietà UseDataContractJsonSerializer su true:

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

Serializzazione JSON

Questa sezione descrive alcuni comportamenti specifici del formattatore JSON, usando il serializzatore Json.NET predefinito. Non si tratta di una documentazione completa della libreria Json.NET; per altre informazioni, vedere la documentazione di Json.NET.

Che cosa viene serializzato?

Per impostazione predefinita, tutti i campi e le proprietà pubbliche sono inclusi nel codice JSON serializzato. Per omettere una proprietà o un campo, decorarlo con l'attributo JsonIgnore .

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

Se si preferisce un approccio di consenso esplicito, decorare la classe con l'attributo DataContract . Se questo attributo è presente, i membri vengono ignorati a meno che non dispongano di DataMember. È anche possibile usare DataMember per serializzare i membri privati.

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

Proprietà Read-Only

Le proprietà di sola lettura vengono serializzate per impostazione predefinita.

Date

Per impostazione predefinita, Json.NET scrive le date in formato ISO 8601 . Le date in formato UTC (Coordinated Universal Time) vengono scritte con un suffisso "Z". Le date nell'ora locale includono una differenza di fuso orario. Ad esempio:

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

Per impostazione predefinita, Json.NET mantiene il fuso orario. È possibile eseguire l'override impostando la proprietà DateTimeZoneHandling:

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

Se si preferisce usare il formato data JSON Microsoft ("\/Date(ticks)\/") anziché ISO 8601, impostare la proprietà DateFormatHandling nelle impostazioni del serializzatore:

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

Stili rientri

Per scrivere codice JSON rientrato, impostare l'impostazione Formattazione su Formatting.Indented:

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

Maiuscole e minuscole camelie

Per scrivere nomi di proprietà JSON con maiuscole e minuscole camel, senza modificare il modello di dati, impostare CamelCasePropertyNamesContractResolver nel serializzatore:

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

Oggetti anonimi e Weakly-Typed

Un metodo di azione può restituire un oggetto anonimo e serializzarlo in JSON. Ad esempio:

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

Il corpo del messaggio di risposta conterrà il codice JSON seguente:

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

Se l'API Web riceve oggetti JSON strutturati in modo debole dai client, è possibile deserializzare il corpo della richiesta in un tipo Newtonsoft.Json.Linq.JObject .

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

Tuttavia, in genere è preferibile usare oggetti dati fortemente tipizzati. Non è quindi necessario analizzare i dati manualmente e ottenere i vantaggi della convalida del modello.

Il serializzatore XML non supporta tipi anonimi o istanze JObject . Se si usano queste funzionalità per i dati JSON, è necessario rimuovere il formattatore XML dalla pipeline, come descritto più avanti in questo articolo.

Formattatore xml Media-Type

La formattazione XML viene fornita dalla classe XmlMediaTypeFormatter . Per impostazione predefinita, XmlMediaTypeFormatter usa la classe DataContractSerializer per eseguire la serializzazione.

Se si preferisce, è possibile configurare XmlMediaTypeFormatter in modo che usi XmlSerializer anziché DataContractSerializer. A tale scopo, impostare la proprietà UseXmlSerializer su true:

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

La classe XmlSerializer supporta un set di tipi più ristretto rispetto a DataContractSerializer, ma offre un maggiore controllo sul codice XML risultante. Se è necessario trovare una corrispondenza con un XML Schema esistente, è consigliabile usare XmlSerializer .

Serializzazione XML

Questa sezione descrive alcuni comportamenti specifici del formattatore XML, usando il datacontractSerializer predefinito.

Per impostazione predefinita, DataContractSerializer si comporta come segue:

  • Tutte le proprietà e i campi di lettura/scrittura pubblici vengono serializzati. Per omettere una proprietà o un campo, decorarlo con l'attributo IgnoreDataMember .
  • I membri privati e protetti non vengono serializzati.
  • Le proprietà di sola lettura non vengono serializzate. Il contenuto di una proprietà di raccolta di sola lettura, tuttavia, viene serializzato.
  • I nomi di classe e membri vengono scritti nel codice XML esattamente come vengono visualizzati nella dichiarazione di classe.
  • Viene usato uno spazio dei nomi XML predefinito.

Se è necessario un maggiore controllo sulla serializzazione, è possibile decorare la classe con l'attributo DataContract . Quando questo attributo è presente, la classe viene serializzata nel modo seguente:

  • Approccio "Opt in": le proprietà e i campi non vengono serializzati per impostazione predefinita. Per serializzare una proprietà o un campo, decorarlo con l'attributo DataMember .
  • Per serializzare un membro privato o protetto, decorarlo con l'attributo DataMember .
  • Le proprietà di sola lettura non vengono serializzate.
  • Per modificare la modalità di visualizzazione del nome della classe nel codice XML, impostare il parametro Name nell'attributo DataContract .
  • Per modificare la modalità di visualizzazione di un nome membro nel codice XML, impostare il parametro Name nell'attributo DataMember .
  • Per modificare lo spazio dei nomi XML, impostare il parametro Namespace nella classe DataContract .

Proprietà Read-Only

Le proprietà di sola lettura non vengono serializzate. Se una proprietà di sola lettura ha un campo privato sottostante, è possibile contrassegnare il campo privato con l'attributo DataMember . Questo approccio richiede l'attributo DataContract nella classe .

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

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

Date

Le date sono scritte in formato ISO 8601. Ad esempio, "2012-05-23T20:21:37.9116538Z".

Stili rientri

Per scrivere codice XML rientrato, impostare la proprietà Rientro su true:

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

Impostazione Per-Type serializzatori XML

È possibile impostare serializzatori XML diversi per tipi CLR diversi. Ad esempio, potrebbe essere disponibile un oggetto dati specifico che richiede XmlSerializer per la compatibilità con le versioni precedenti. È possibile usare XmlSerializer per questo oggetto e continuare a usare DataContractSerializer per altri tipi.

Per impostare un serializzatore XML per un particolare tipo, chiamare SetSerializer.

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

È possibile specificare un XmlSerializer o qualsiasi oggetto che deriva da XmlObjectSerializer.

Rimozione del formattatore JSON o XML

È possibile rimuovere il formattatore JSON o il formattatore XML dall'elenco dei formattatori, se non si vuole usarli. I motivi principali per eseguire questa operazione sono:

  • Per limitare le risposte dell'API Web a un particolare tipo di supporto. Ad esempio, è possibile decidere di supportare solo le risposte JSON e rimuovere il formattatore XML.
  • Per sostituire il formattatore predefinito con un formattatore personalizzato. Ad esempio, è possibile sostituire il formattatore JSON con la propria implementazione personalizzata di un formattatore JSON.

Il codice seguente illustra come rimuovere i formattatori predefiniti. Chiamarlo dal metodo Application_Start definito in 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);
}

Gestione di riferimenti a oggetti circolari

Per impostazione predefinita, i formattatori JSON e XML scrivono tutti gli oggetti come valori. Se due proprietà fanno riferimento allo stesso oggetto o se lo stesso oggetto viene visualizzato due volte in un insieme, il formattatore serializzerà l'oggetto due volte. Si tratta di un problema particolare se il grafico degli oggetti contiene cicli, perché il serializzatore genererà un'eccezione quando rileva un ciclo nel grafico.

Si considerino i modelli a oggetti e il controller seguenti.

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

Se si richiama questa azione, il formattatore genererà un'eccezione, che si traduce in una risposta di codice di stato 500 (errore interno del server) al client.

Per mantenere i riferimenti agli oggetti in JSON, aggiungere il codice seguente al metodo Application_Start nel file Global.asax:

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

Ora l'azione del controller restituirà JSON simile al seguente:

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

Si noti che il serializzatore aggiunge una proprietà "$id" a entrambi gli oggetti. Rileva inoltre che la proprietà Employee.Department crea un ciclo, quindi sostituisce il valore con un riferimento all'oggetto: {"$ref":"1"}.

Nota

I riferimenti agli oggetti non sono standard in JSON. Prima di usare questa funzionalità, valutare se i client saranno in grado di analizzare i risultati. Potrebbe essere preferibile rimuovere i cicli dal grafico. Ad esempio, il collegamento da Employee a Department non è effettivamente necessario in questo esempio.

Per mantenere i riferimenti agli oggetti in XML, sono disponibili due opzioni. L'opzione più semplice consiste nell'aggiungere [DataContract(IsReference=true)] alla classe del modello. Il parametro IsReference abilita i riferimenti agli oggetti. Tenere presente che DataContract effettua il consenso esplicito per la serializzazione, quindi sarà necessario aggiungere anche gli attributi DataMember alle proprietà:

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

Ora il formattatore produrrà codice XML simile al seguente:

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

Per evitare attributi nella classe del modello, è disponibile un'altra opzione: Creare una nuova istanza dataContractSerializer specifica del tipo e impostare preserveObjectReferences su true nel costruttore. Impostare quindi questa istanza come serializzatore per tipo nel formattatore di tipo multimediale XML. Il codice seguente illustra come eseguire questa operazione:

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

Test della serializzazione degli oggetti

Durante la progettazione dell'API Web, è utile testare il modo in cui gli oggetti dati verranno serializzati. È possibile eseguire questa operazione senza creare un controller o richiamare un'azione del controller.

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