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

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

5 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 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 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

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

12 months ago

How to Order Events in Microservices by Using Azure Service Bus (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 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

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

2 years ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

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

2 years ago