Serializacja JSON i XML w internetowym interfejsie API ASP.NET
W tym artykule opisano formatery JSON i XML w interfejsie API sieci Web ASP.NET.
W ASP.NET internetowym interfejsem API formater typu nośnika jest obiektem, który może:
- Odczytywanie obiektów CLR z treści komunikatu HTTP
- Zapisywanie obiektów CLR w treści komunikatu HTTP
Internetowy interfejs API udostępnia formatery typów multimediów zarówno dla formatu JSON, jak i XML. Struktura domyślnie wstawia te formatery do potoku. Klienci mogą zażądać kodu JSON lub XML w nagłówku Accept żądania HTTP.
Zawartość
Formater Media-Type JSON
Formatowanie JSON jest dostarczane przez klasę JsonMediaTypeFormatter . Domyślnie program JsonMediaTypeFormatter używa biblioteki Json.NET do wykonywania serializacji. Json.NET to projekt open source innej firmy.
Jeśli wolisz, możesz skonfigurować klasę JsonMediaTypeFormatter , aby zamiast Json.NET użyć klasy DataContractJsonSerializer . W tym celu ustaw właściwość UseDataContractJsonSerializer na true:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
Serializacja JSON
W tej sekcji opisano niektóre konkretne zachowania formatatora JSON przy użyciu domyślnego serializatora Json.NET . Nie jest to kompleksowa dokumentacja biblioteki Json.NET; Aby uzyskać więcej informacji, zobacz dokumentację Json.NET.
Co jest serializowane?
Domyślnie wszystkie publiczne właściwości i pola są uwzględniane w serializowanym formacie JSON. Aby pominąć właściwość lub pole, ozdobić go atrybutem JsonIgnore .
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
[JsonIgnore]
public int ProductCode { get; set; } // omitted
}
Jeśli wolisz podejście "opt-in", dekoruj klasę za pomocą atrybutu DataContract . Jeśli ten atrybut jest obecny, elementy członkowskie są ignorowane, chyba że mają element DataMember. Można również użyć elementu DataMember , aby serializować prywatne elementy członkowskie.
[DataContract]
public class Product
{
[DataMember]
public string Name { get; set; }
[DataMember]
public decimal Price { get; set; }
public int ProductCode { get; set; } // omitted by default
}
właściwości Read-Only
Właściwości tylko do odczytu są domyślnie serializowane.
Daty
Domyślnie Json.NET zapisuje daty w formacie ISO 8601 . Daty w formacie UTC (uniwersalny czas koordynowany) są zapisywane z sufiksem "Z". Daty w czasie lokalnym obejmują przesunięcie strefy czasowej. Na przykład:
2012-07-27T18:51:45.53403Z // UTC
2012-07-27T11:51:45.53403-07:00 // Local
Domyślnie Json.NET zachowuje strefę czasową. Można to przesłonić, ustawiając właściwość DateTimeZoneHandling:
// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
Jeśli wolisz używać formatu daty JSON firmy Microsoft ("\/Date(ticks)\/"
) zamiast ISO 8601, ustaw właściwość DateFormatHandling w ustawieniach serializatora:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
Wcięcia
Aby zapisać wcięcie JSON, ustaw ustawienie Formatowanie naFormatting.Indented:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
Camel Casing
Aby zapisać nazwy właściwości JSON z wielkością wielbłąda, bez zmieniania modelu danych ustaw właściwość CamelCasePropertyNamesContractResolver na serializatorze:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
Obiekty anonimowe i Weakly-Typed
Metoda akcji może zwrócić anonimowy obiekt i serializować go do formatu JSON. Na przykład:
public object Get()
{
return new {
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
}
Treść komunikatu odpowiedzi będzie zawierać następujący kod JSON:
{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}
Jeśli internetowy interfejs API odbiera luźno ustrukturyzowane obiekty JSON od klientów, można deserializować treść żądania do typu Newtonsoft.Json.Linq.JObject .
public void Post(JObject person)
{
string name = person["Name"].ToString();
int age = person["Age"].ToObject<int>();
}
Jednak zazwyczaj lepiej jest używać silnie typiowanych obiektów danych. Następnie nie musisz analizować danych samodzielnie i uzyskać korzyści z weryfikacji modelu.
Serializator XML nie obsługuje typów anonimowych ani wystąpień JObject . Jeśli używasz tych funkcji dla danych JSON, usuń formater XML z potoku, zgodnie z opisem w dalszej części tego artykułu.
XML Media-Type Formatter
Formatowanie XML jest dostarczane przez klasę XmlMediaTypeFormatter . Domyślnie klasa XmlMediaTypeFormatter używa klasy DataContractSerializer do wykonywania serializacji.
Jeśli wolisz, możesz skonfigurować element XmlMediaTypeFormatter tak, aby używał klasy XmlSerializer zamiast elementu DataContractSerializer. W tym celu ustaw właściwość UseXmlSerializer na true:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
Klasa XmlSerializer obsługuje węższy zestaw typów niż DataContractSerializer, ale zapewnia większą kontrolę nad wynikowym kodem XML. Rozważ użycie elementu XmlSerializer , jeśli musisz dopasować istniejący schemat XML.
Serializacji XML
W tej sekcji opisano niektóre konkretne zachowania formatującego XML przy użyciu domyślnego elementu DataContractSerializer.
Domyślnie element DataContractSerializer zachowuje się w następujący sposób:
- Wszystkie publiczne właściwości i pola odczytu/zapisu są serializowane. Aby pominąć właściwość lub pole, udekoruj ją atrybutem IgnoreDataMember .
- Prywatne i chronione elementy członkowskie nie są serializowane.
- Właściwości tylko do odczytu nie są serializowane. (Jednak zawartość właściwości kolekcji tylko do odczytu jest serializowana).
- Nazwy klas i składowych są zapisywane w kodzie XML dokładnie tak, jak są wyświetlane w deklaracji klasy.
- Używana jest domyślna przestrzeń nazw XML.
Jeśli potrzebujesz większej kontroli nad serializacji, możesz ozdobić klasę atrybutem DataContract . Gdy ten atrybut jest obecny, klasa jest serializowana w następujący sposób:
- Podejście "Opt in": Właściwości i pola nie są domyślnie serializowane. Aby serializować właściwość lub pole, udekoruj ją atrybutem DataMember .
- Aby serializować prywatny lub chroniony element członkowski, należy go ozdobić atrybutem DataMember .
- Właściwości tylko do odczytu nie są serializowane.
- Aby zmienić sposób wyświetlania nazwy klasy w pliku XML, ustaw parametr Name w atrybucie DataContract .
- Aby zmienić sposób wyświetlania nazwy elementu członkowskiego w pliku XML, ustaw parametr Name w atrybucie DataMember .
- Aby zmienić przestrzeń nazw XML, ustaw parametr przestrzeń nazw w klasie DataContract .
właściwości Read-Only
Właściwości tylko do odczytu nie są serializowane. Jeśli właściwość tylko do odczytu ma pole prywatne z kopią zapasową, możesz oznaczyć pole prywatne atrybutem DataMember . Takie podejście wymaga atrybutu DataContract w klasie .
[DataContract]
public class Product
{
[DataMember]
private int pcode; // serialized
// Not serialized (read-only)
public int ProductCode { get { return pcode; } }
}
Daty
Daty są zapisywane w formacie ISO 8601. Na przykład "2012-05-23T20:21:37.9116538Z".
Wcięcia
Aby zapisać wcięcie XML, ustaw właściwość Indent na true:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;
Ustawianie Per-Type serializatorów XML
Można ustawić różne serializatory XML dla różnych typów CLR. Na przykład może istnieć określony obiekt danych, który wymaga elementu XmlSerializer w celu zapewnienia zgodności z poprzednimi wersjami. Możesz użyć elementu XmlSerializer dla tego obiektu i nadal używać elementu DataContractSerializer dla innych typów.
Aby ustawić serializator XML dla określonego typu, wywołaj metodę SetSerializer.
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));
Można określić element XmlSerializer lub dowolny obiekt pochodzący z elementu XmlObjectSerializer.
Usuwanie formatu JSON lub XML
Jeśli nie chcesz ich używać, możesz usunąć z listy formaterów JSON lub formater XML. Główne powody, dla których należy to zrobić, to:
- Aby ograniczyć odpowiedzi internetowego interfejsu API do określonego typu nośnika. Na przykład możesz zdecydować się na obsługę tylko odpowiedzi JSON i usunąć formater XML.
- Aby zastąpić domyślny formater niestandardowym. Możesz na przykład zastąpić formater JSON własną niestandardową implementacją programu formatującego JSON.
Poniższy kod pokazuje, jak usunąć domyślne formatery. Wywołaj to z metody Application_Start zdefiniowanej w pliku 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);
}
Obsługa odwołań do obiektu cyklicznego
Domyślnie formatery JSON i XML zapisują wszystkie obiekty jako wartości. Jeśli dwie właściwości odwołują się do tego samego obiektu lub jeśli ten sam obiekt pojawia się dwa razy w kolekcji, formater będzie serializować obiekt dwa razy. Jest to konkretny problem, jeśli wykres obiektu zawiera cykle, ponieważ serializator zgłosi wyjątek podczas wykrywania pętli na grafie.
Rozważ następujące modele obiektów i 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;
}
}
Wywołanie tej akcji spowoduje zgłoszenie wyjątku przez program formatujący, co przekłada się na odpowiedź klienta z kodem stanu 500 (wewnętrzny błąd serwera).
Aby zachować odwołania do obiektów w formacie JSON, dodaj następujący kod, aby Application_Start metodę w pliku Global.asax:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
Teraz akcja kontrolera zwróci kod JSON, który wygląda następująco:
{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}
Zwróć uwagę, że serializator dodaje właściwość "$id" do obu obiektów. Ponadto wykrywa, że właściwość Employee.Department tworzy pętlę, więc zastępuje wartość odwołaniem do obiektu: {"$ref":"1"}.
Uwaga
Odwołania do obiektów nie są standardowe w formacie JSON. Przed użyciem tej funkcji należy rozważyć, czy klienci będą mogli analizować wyniki. Lepszym rozwiązaniem może być usunięcie cykli z grafu. Na przykład link od pracownika z powrotem do działu nie jest naprawdę potrzebny w tym przykładzie.
Aby zachować odwołania do obiektów w kodzie XML, dostępne są dwie opcje. Prostszą opcją jest dodanie [DataContract(IsReference=true)]
do klasy modelu. Parametr IsReference umożliwia odwołania do obiektów. Pamiętaj, że funkcja DataContract wyraża zgodę na serializacji, dlatego należy również dodać atrybuty DataMember do właściwości:
[DataContract(IsReference=true)]
public class Department
{
[DataMember]
public string Name { get; set; }
[DataMember]
public Employee Manager { get; set; }
}
Teraz program formatujący utworzy kod XML podobny do następującego:
<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>
Jeśli chcesz uniknąć atrybutów w klasie modelu, istnieje inna opcja: Utwórz nowe wystąpienie klasy DataContractSerializer specyficzne dla typu i ustaw parametr preserveObjectReferences na wartość true w konstruktorze. Następnie ustaw to wystąpienie jako serializator dla poszczególnych typów w formacie nośnika XML. Poniższy kod pokazuje, jak to zrobić:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue,
false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);
Testowanie serializacji obiektów
Podczas projektowania internetowego interfejsu API warto przetestować sposób serializacji obiektów danych. Można to zrobić bez tworzenia kontrolera lub wywoływania akcji kontrolera.
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);
}