İçeriğe geç

Asp.NET Web API’da Circular Reference Handling

Merhaba arkadaşlar.

Yılbaşından sonra çıkacak olan Asp.Net Web API kitabımıza odaklandığım için bu aralar fazla makale yazamıyorum. Fakat e-mail aracılığı ile gelen sorular ve benimde bir kaç projede karşı karşıya gelmem nedeniyle “Self referencing loop detected” problemini nasıl handle edebileceğimizi kitabın bir bölümünden alarak makale olarak koymak istedim. 🙂

Circular referans yani döngüsel referans, farklı modellerin birbirlerini referans olarak görmesidir. Özellikle entity framework kullanılarak oluşturulan modeller içerisindeki, navigation property’ler üzerinde görülmektedir. Dilerseniz aşağıdaki model kod bloğuna bir bakalım:

public class Customer
{
    public Customer()
    {
        Orders = new Collection<Order>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }

    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int BasketId { get; set; }
    public DateTime OrderDate { get; set; }

    public Customer Customer { get; set; }
}

Yukarıdaki kod bloğunda bulunan Customer ve Order modellerinin navigation property’lerine baktığımızda Customer’ın Order tipinde bir collection’a sahip olduğunu, Order’ın ise Customer tipinde döngüsel bir navigation property’e sahip olduğunu görebiliriz.

İlgili serializer bu modeli serialize etmeye çalıştığında ise aşağıdaki hatayı verecektir:

self loop reference

Serialize işlemi sırasında birbirlerini döngüsel olarak referans gösteren iki navigation property’den dolayı serialize işlemi bir loop’a girmekte ve bu sebeple ilgili serializer bu döngüyü nasıl handle edebileceğini bilmediği için “Self referencing loop detected for property” hatasını vermektedir.

Asp.NET Web API içerisinde bu sorunu handle edebilmek için üç farklı yol vardır:

1) Global Olarak Circular Referans’ı Ignore Etmek

Json.NET serializer’ın, serializer ayarlarında aşağıdaki kod bloğu ile global düzeyde circular referans’ı ignore edebiliriz.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    }
}

Yukarıdaki kod bloğu ile loop referans’a sebep veren property, ilgili referans bilgisini kaybetmiş olacak ve aşağıdaki gibi bir json çıktısı ediyor olacağız:

{
    "Id": 1,
    "Name": "Gökhan",
    "Surname": "Gökalp",
    "Email": "gokhan@gokalp.com",
    "PhoneNumber": "09999999999",
    "Orders": [{
        "Id": 1,
        "BasketId": 1,
        "OrderDate": "2015-11-07T00:00:00+03:00"
    }]
}


2) Global Olarak Circular Referans’ı Korumak Etmek

Json.NET serializer’ın serializer ayarlarında, circular referans’ı ignore edebildiğimiz gibi preserving ayarı ile bunu global düzeyde koruyabilmemizde mümkündür.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;
    }
}

Yukarıdaki kod bloğu ile circular referans’a sebep olabilecek referansları nasıl handle edebileceğini ilgili serializer’a bildiriyoruz ve bunun sonucunda ise json çıktısı biraz değişmiş olacak.

{
    "$id": "1",
    "Id": 1,
    "Name": "Gökhan",
    "Surname": "Gökalp",
    "Email": "gokhan@gokalp.com",
    "PhoneNumber": "09999999999",
    "Orders": {
        "$id": "2",
        "$values": [{
            "$id": "3",
            "Id": 1,
            "BasketId": 1,
            "OrderDate": "2015-11-07T00:00:00+03:00",
            "Customer": {
                "$ref": "1"
            }
        }]
    }
}

Yukarıdaki json çıktısında görebileceğimiz üzere PreserveReferencesHandling ayarı ile “$id” ve “$ref” değerleri tüm referans bilgilerini tutabilmektedir ve böylece döngüsel referans hatasına sebep olabilecek problemin önüne geçebilmektedir.

NOT: Unutmamalıyız ki PreserveReferencesHandling ayarı ile json çıktısı üzerinde referansları tutabilmesi için oluşturulmuş olan bu object referansları bir JSON standartı değildir. Bu nedenle bu işlemi yapmadan önce ilgili client’ın gelen json sonucunu nasıl parse edebileceğini bilmesi gerekmektedir.

3) Model veya Property Düzeyinde Circular Referans’ı Ignore Ermek veya Korumak

Bu yöntemde ise global düzey yerine işi biraz daha spesifikleştirerek model veya property düzeyinde attribute’ler aracılığı ile gerçekleştirebilmek mümkündür. Dilerseniz şimdi hemen Customer sınıfına bir göz atalım:

public class Customer
{
    public Customer()
    {
        Orders = new Collection<Order>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }

    [JsonIgnore]
    [IgnoreDataMember]
    public ICollection<Order> Orders { get; set; }
}

JsonIgnore ve IgnoreDataMember attribute’leri ile hem Json.Net için hem de XmlSerializer için serialize işlemi sırasında bu property’i ignore etmesini bildirdik. Bu sayede serialize işlemi sırasında bu property serialize işlemine tabi olmayacaktır.

Şimdide ilgili referansı koruyabilmek için yapılması gerekene bir bakalım:

[JsonObject(IsReference = true)]
public class Customer
{
    public Customer()
    {
        Orders = new Collection<Order>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }

    public ICollection<Order> Orders { get; set; }
}

Model düzeyinde eklemiş olduğumuz JsonObject(IsReference = true) attribute’ü ile ilgili Orders referansını koruması gerektiğini bildirdik. Aynı işlemi XmlSerializer için ise [DataContract(IsReference=true)] attribute’ü ile yapabilmekte mümkündür. Unutmayalım, DataContract attribute’ünü eklediğimizde serializer opt-in yaklaşımına göre davranacaktır ve serialize işlemine tabi olmasını istediğimiz property’lere DataMember attribute’ünü eklememiz gerekmektedir.

Dilerseniz şimdi birde XML için oluşacak sonuca [DataContract(IsReference=true)] attribute’ünü ekleyerek bir bakalım. Öncelikle Customer modelimizi opt-in yaklaşımına göre hazırlayalım:

[DataContract(IsReference = true)]
public class Customer
{
    public Customer()
    {
        Orders = new Collection<Order>();
    }

    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public string Surname { get; set; }
    [DataMember]
    public string Email { get; set; }
    [DataMember]
    public string PhoneNumber { get; set; }

    [DataMember]
    public ICollection<Order> Orders { get; set; }
}

Modelimizi oluşturduk ve şimdi serialize işlemi sonucunda oluşacak olan XML çıktısına bir bakalım:

<Customer xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="http://schemas.datacontract.org/2004/07/JSONSerialization.Models" z:Id="i1">
  <Email>gokhan@gokalp.com</Email>
  <Id>1</Id>
  <Name>Gökhan</Name>
  <Orders>
    <Order>
      <BasketId>1</BasketId>
      <Customer z:Ref="i1"/>
      <Id>1</Id>
      <OrderDate>2015-11-07T00:00:00+03:00</OrderDate>
    </Order>
  </Orders>
  <PhoneNumber>09999999999</PhoneNumber>
  <Surname>Gökalp</Surname>
</Customer>

XML çıktısında gördüğümüz üzere “z:Id” ve “z:Ref” attribute’leri üzerinde referans değerleri tutulmaktadır.

Umarım faydalı bir makale olmuştur.

Kategori:Asp.Net Web API

2 Yorum

  1. Begüm Begüm

    Yazınız ve bilgilendirme için çok teşekkür ederim, çok işime yaradı 🙂
    Başarılar

Begüm için bir yanıt yazın Yanıtı iptal et

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.