ASP.NET Web API'sinde JSON ve XML Serileştirme

Bu makalede, ASP.NET Web API'sindeki JSON ve XML biçimlendiricileri açıklanmaktadır.

ASP.NET Web API'sinde , medya türü biçimlendirici şunları yapabilecek bir nesnedir:

  • HTTP ileti gövdesinden CLR nesnelerini okuma
  • HTTP ileti gövdesine CLR nesneleri yazma

Web API'sinde hem JSON hem de XML için medya türü biçimlendiriciler sağlanır. Çerçeve bu biçimlendiricileri varsayılan olarak işlem hattına ekler. İstemciler, HTTP isteğinin Accept üst bilgisinde JSON veya XML isteyebilir.

İçindekiler

JSON Media-Type Biçimlendirici

JSON biçimlendirmesi JsonMediaTypeFormatter sınıfı tarafından sağlanır. Varsayılan olarak, JsonMediaTypeFormatter serileştirme gerçekleştirmek için Json.NET kitaplığını kullanır. Json.NET bir üçüncü taraf açık kaynak projesidir.

İsterseniz JsonMediaTypeFormatter sınıfını Json.NET yerine DataContractJsonSerializer kullanacak şekilde yapılandırabilirsiniz. Bunu yapmak için UseDataContractJsonSerializer özelliğini true olarak ayarlayın:

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

JSON Seri Hale Getirme

Bu bölümde, varsayılan Json.NET seri hale getirici kullanılarak JSON biçimlendiricisinin bazı özel davranışları açıklanmaktadır. Bunun Json.NET kitaplığının kapsamlı belgeleri olması amaçlanmamıştır; daha fazla bilgi için Json.NET Belgeleri'ne bakın.

Ne Seri hale getirilir?

Varsayılan olarak, tüm ortak özellikler ve alanlar serileştirilmiş JSON'a dahil edilir. Bir özelliği veya alanı yoksaymak için, bunu JsonIgnore özniteliğiyle süsleyin.

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

"Kabul et" yaklaşımını tercih ediyorsanız sınıfı DataContract özniteliğiyle süsleyin. Bu öznitelik varsa, DataMember'e sahip olmadığı sürece üyeler yoksayılır. Özel üyeleri seri hale getirmek için DataMember da kullanabilirsiniz.

[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 Özellikleri

Salt okunur özellikler varsayılan olarak seri hale getirilir.

Tarihler

Varsayılan olarak, Json.NET tarihleri ISO 8601 biçiminde yazar. UTC (Eşgüdümlü Evrensel Saat) içindeki tarihler bir "Z" soneki ile yazılır. Yerel saat içindeki tarihler saat dilimi uzaklığını içerir. Örnek:

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

Varsayılan olarak, Json.NET saat dilimini korur. DateTimeZoneHandling özelliğini ayarlayarak bunu geçersiz kılabilirsiniz:

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

ISO 8601 yerine Microsoft JSON tarih biçimini ("\/Date(ticks)\/") kullanmayı tercih ediyorsanız seri hale getirici ayarlarında DateFormatHandling özelliğini ayarlayın:

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

Girintileme

Girintili JSON yazmak için Biçimlendirme ayarınıFormatting.Indented olarak ayarlayın:

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

Deve Kasası

Veri modelinizi değiştirmeden, deve büyük/küçük harfleriyle JSON özellik adları yazmak için, seri hale getiricide CamelCasePropertyNamesContractResolver'ı ayarlayın:

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

Anonim ve Weakly-Typed Nesneleri

Eylem yöntemi anonim bir nesne döndürebilir ve JSON'a seri hale getirebilirsiniz. Örnek:

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

Yanıt iletisi gövdesi aşağıdaki JSON'yi içerir:

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

Web API'niz istemcilerden gevşek yapılandırılmış JSON nesneleri alıyorsa, istek gövdesini Newtonsoft.Json.Linq.JObject türüne seri durumdan çıkarabilirsiniz.

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

Ancak, kesin olarak yazılan veri nesnelerini kullanmak genellikle daha iyidir. Ardından verileri kendiniz ayrıştırmanıza gerek yoktur ve model doğrulamanın avantajlarından yararlanırsınız.

XML seri hale getirici anonim türleri veya JObject örneklerini desteklemez. JSON verileriniz için bu özellikleri kullanırsanız, bu makalenin ilerleyen bölümlerinde açıklandığı gibi XML biçimlendiricisini işlem hattından kaldırmanız gerekir.

XML Media-Type Biçimlendirici

XML biçimlendirmesi XmlMediaTypeFormatter sınıfı tarafından sağlanır. Varsayılan olarak , XmlMediaTypeFormatter serileştirme gerçekleştirmek için DataContractSerializer sınıfını kullanır.

İsterseniz, XmlMediaTypeFormatter'ıDataContractSerializer yerine XmlSerializer kullanacak şekilde yapılandırabilirsiniz. Bunu yapmak için UseXmlSerializer özelliğini true olarak ayarlayın:

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

XmlSerializer sınıfı DataContractSerializer'dan daha dar bir tür kümesini destekler, ancak sonuçta elde edilen XML üzerinde daha fazla denetim sağlar. Varolan bir XML şemasıyla eşleşmeniz gerekiyorsa XmlSerializer kullanmayı göz önünde bulundurun.

XML seri hale getirme

Bu bölümde, varsayılan DataContractSerializer kullanılarak XML biçimlendiricisinin bazı özel davranışları açıklanmaktadır.

Varsayılan olarak, DataContractSerializer aşağıdaki gibi davranır:

  • Tüm genel okuma/yazma özellikleri ve alanları seri hale getirilir. Bir özelliği veya alanı atlamak için IgnoreDataMember özniteliğiyle süsleyin.
  • Özel ve korumalı üyeler serileştirilmemiştir.
  • Salt okunur özellikler serileştirilmemiştir. (Ancak, salt okunur koleksiyon özelliğinin içeriği serileştirilir.)
  • Sınıf ve üye adları, XML'de tam olarak sınıf bildiriminde göründükleri gibi yazılır.
  • Varsayılan XML ad alanı kullanılır.

Serileştirme üzerinde daha fazla denetime ihtiyacınız varsa, sınıfı DataContract özniteliğiyle süsleyebilirsiniz. Bu öznitelik mevcut olduğunda sınıfı aşağıdaki gibi seri hale getirilir:

  • "Kabul et" yaklaşımı: Özellikler ve alanlar varsayılan olarak seri hale getirilmemiştir. Bir özelliği veya alanı seri hale getirmek için DataMember özniteliğiyle süsleyin.
  • Özel veya korumalı bir üyeyi seri hale getirmek için DataMember özniteliğiyle süsleyin.
  • Salt okunur özellikler serileştirilmemiştir.
  • Sınıf adının XML'de nasıl görüneceğini değiştirmek için DataContract özniteliğinde Name parametresini ayarlayın.
  • Üye adının XML'de nasıl görüneceğini değiştirmek için DataMember özniteliğinde Name parametresini ayarlayın.
  • XML ad alanını değiştirmek için DataContract sınıfında Namespace parametresini ayarlayın.

Read-Only Özellikleri

Salt okunur özellikler serileştirilmemiştir. Salt okunur bir özelliğin yedekleme özel alanı varsa, özel alanı DataMember özniteliğiyle işaretleyebilirsiniz. Bu yaklaşım, sınıfında DataContract özniteliğini gerektirir.

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

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

Tarihler

Tarihler ISO 8601 biçiminde yazılır. Örneğin, "2012-05-23T20:21:37.9116538Z".

Girintileme

Girintili XML yazmak için Girinti özelliğini true olarak ayarlayın:

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

Per-Type XML Seri Hale Getiricilerini Ayarlama

Farklı CLR türleri için farklı XML serileştiricileri ayarlayabilirsiniz. Örneğin, geriye dönük uyumluluk için XmlSerializer gerektiren belirli bir veri nesneniz olabilir. Bu nesne için XmlSerializer kullanabilir ve diğer türler için DataContractSerializer kullanmaya devam edebilirsiniz.

Belirli bir tür için XML serileştiricisi ayarlamak için SetSerializer'ı çağırabilirsiniz.

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

XmlSerializer veya XmlObjectSerializer'dan türetilen herhangi bir nesne belirtebilirsiniz.

JSON veya XML Biçimlendiricisini Kaldırma

JSON biçimlendiricisini veya XML biçimlendiricisini kullanmak istemiyorsanız, biçimlendirici listesinden kaldırabilirsiniz. Bunu yapmak için temel nedenler şunlardır:

  • Web API'nizin yanıtlarını belirli bir medya türüyle kısıtlamak için. Örneğin, yalnızca JSON yanıtlarını desteklemeye karar verebilir ve XML biçimlendiricisini kaldırabilirsiniz.
  • Varsayılan biçimlendiriciyi özel bir biçimlendiriciyle değiştirmek için. Örneğin, JSON biçimlendiricisini kendi özel JSON biçimlendirici uygulamanızla değiştirebilirsiniz.

Aşağıdaki kod, varsayılan biçimlendiricilerin nasıl kaldırılacağını gösterir. Bunu Global.asax içinde tanımlanan Application_Start yönteminizden çağır.

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

Döngüsel Nesne Başvurularını İşleme

Varsayılan olarak, JSON ve XML biçimlendiricileri tüm nesneleri değer olarak yazar. İki özellik aynı nesneye başvuruda bulunursa veya aynı nesne bir koleksiyonda iki kez görünürse, biçimlendirici nesneyi iki kez serileştirir. Seri hale getirici grafikte bir döngü algıladığında bir özel durum oluşturacağından, nesne grafınız döngüler içeriyorsa bu belirli bir sorundur.

Aşağıdaki nesne modellerini ve denetleyiciyi göz önünde bulundurun.

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

Bu eylemi çağırmak, biçimlendiricinin bir özel durum oluşturmasına neden olur ve bu durum kodu istemciye 500 (İç Sunucu Hatası) yanıtına dönüşür.

JSON'da nesne başvurularını korumak için Global.asax dosyasındaki Application_Start yöntemine aşağıdaki kodu ekleyin:

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

Şimdi denetleyici eylemi şuna benzer bir JSON döndürür:

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

Seri hale getiricinin her iki nesneye de "$id" özelliği eklediğine dikkat edin. Ayrıca Employee.Department özelliğinin bir döngü oluşturduğunu algılar, bu nedenle değeri bir nesne başvurusuyla değiştirir: {"$ref":"1"}.

Not

Nesne başvuruları JSON'da standart değildir. Bu özelliği kullanmadan önce, istemcilerinizin sonuçları ayrıştırıp ayrıştıramayacağını göz önünde bulundurun. Döngüleri grafikten kaldırmak daha iyi olabilir. Örneğin, bu örnekte Çalışandan Departman bağlantısına gerçekten ihtiyaç duyulmaz.

XML'de nesne başvurularını korumak için iki seçeneğiniz vardır. Daha basit seçenek, model sınıfınıza eklemektir [DataContract(IsReference=true)] . IsReference parametresi nesne başvurularını etkinleştirir. DataContract'in serileştirmeyi kabul ettiğini unutmayın, bu nedenle özelliklere DataMember öznitelikleri de eklemeniz gerekir:

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

Şimdi biçimlendirici aşağıdakine benzer bir XML üretecektir:

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

Model sınıfınızda özniteliklerden kaçınmak istiyorsanız başka bir seçenek daha vardır: Türe özgü yeni bir DataContractSerializer örneği oluşturun ve oluşturucuda preserveObjectReferences değerini true olarak ayarlayın. Ardından bu örneği XML medya türü biçimlendiricisinde tür başına seri hale getirici olarak ayarlayın. Aşağıdaki kod bunun nasıl yapılacağını gösterir:

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

Nesne Serileştirmeyi Test Etme

Web API'nizi tasarladığınızda, veri nesnelerinizin nasıl seri hale getirileceğini test etmek yararlı olur. Bunu, denetleyici oluşturmadan veya denetleyici eylemi çağırmadan yapabilirsiniz.

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