Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo descrive i formattatori JSON e XML in ASP.NET API Web.
In ASP.NET API Web, 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 per JSON e 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 di tipo di media JSON
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 open source di terze parti.
Se si preferisce, è possibile configurare la classe JsonMediaTypeFormatter per 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 Json.NET.
Che cosa viene serializzato?
Per impostazione predefinita, tutte le proprietà e i campi pubblici 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 "opt-in", decorare la classe con l'attributo DataContract . Se questo attributo è presente, i membri vengono ignorati a meno che non abbiano 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à di Sola Lettura
Le proprietà di sola lettura vengono serializzate per impostazione predefinita.
Date
Per impostazione predefinita, Json.NET scrive 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. Per 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 di questa impostazione 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;
Rientro
Per scrivere JSON con rientro, impostare l'impostazione Formattazione su Formatting.Indented:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
Camel Casing
Per scrivere nomi di proprietà JSON in notazione camel case, senza modificare il modello di dati, impostare CamelCasePropertyNamesContractResolver sul serializzatore:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
Oggetti anonimi e debolmente tipizzati
Un metodo di azione può restituire un oggetto anonimo e serializzarlo in JSON. Per 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 libero 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 Tipo di Media XML
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. È consigliabile usare XmlSerializer se è necessario trovare una corrispondenza con uno schema XML esistente.
Serializzazione XML
In questa sezione vengono descritti 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. I contenuti di una proprietà di raccolta in sola lettura, tuttavia, vengono serializzati.
- I nomi di classe e membro vengono scritti nel codice XML esattamente come vengono visualizzati nella dichiarazione di classe.
- Viene utilizzato 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": Proprietà e 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à di Sola Lettura
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 vengono scritte in formato ISO 8601. Ad esempio, "2012-05-23T20:21:37.9116538Z".
Rientro
Per scrivere codice XML con rientro, impostare la proprietà Indent su true:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;
Impostazione dei serializzatori XML per tipo
È 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 MIME. 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.
Nel codice seguente viene illustrato come rimuovere i formattatori predefiniti. Chiamare questa operazione 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 l'oggetto grafico 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"}.
Annotazioni
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 meglio rimuovere i cicli dal grafico. Ad esempio, il collegamento dal dipendente al reparto 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);
}