Categories: Asp.Net Web API

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:

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.

Gökhan Gökalp

View Comments

Recent Posts

Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (Güvenlik Taraması, SBOM’lar, Artifact’lerin İmzalanması ve Doğrulanması) – Bölüm 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 ay ago

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 yıl ago

Azure Service Bus Kullanarak Microservice’lerde Event’ler Nasıl Sıralanır (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 yıl ago

.NET Microservice’lerinde Outbox Pattern’ı ile Eventual Consistency için Atomicity Sağlama

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 yıl ago

Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 yıl ago