Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET. Newtonsoft.Json.JsonSerializationException: 'Self referencing loop detected with type 'E_Store2021.Models.Product' [duplicate]

I'm trying to get SubCategory after Include(p => p.SubCategory) and get the error:

"Newtonsoft.Json.JsonSerializationException: 'Self referencing loop detected with type 'E_Store2021.Models.Product'. Path '[0].Product.SubCategory.Products' "

I need to get SubCategoryName. What should I do in this case? Without Include() everything works fine. The same will happen with the company.

Markup:

<tr>
    <td>
        <figure class="itemside align-items-center">
            <div class="aside"><img src="@("~/images/content/" + item.Product.ImagePath)" asp-append-version="true" class="img-sm"/></div>
            <figcaption class="info">
                <a href="#" class="title text-dark" data-abc="true">@item.Product.ProductName</a>
                <p class="text-muted small">Category: <br> Brand: @item?.Product?.Company.CompanyName</p>
                <p class="text-muted small">SubCategory: @item.Product?.SubCategory?.SubCategoryName</p>
            </figcaption>
        </figure>
    </td>
    <td>
        <select class="form-control">
            <option>1</option>
            <option>2</option>
            <option>3</option>
            <option>4</option>
        </select>
    </td>
    <td>
        <div class="price-wrap"> <var class="price">@item.Product.UnitPrice</var> <small class="text-muted"> $9.20 each </small> </div>
    </td>
    <td class="text-right d-none d-md-block"> <a data-original-title="Save to Wishlist" title="" href="" class="btn btn-light" data-toggle="tooltip" data-abc="true"> <i class="fa fa-heart"></i></a> <a href="" class="btn btn-light" data-abc="true"> Remove</a> </td>
</tr>

Code:

namespace E_Store2021.Controllers
{
    public class CartController : Controller
    {
        private ApplicationDbContext _context;

        public CartController(ApplicationDbContext context)
        {
            _context = context;
        }

        [AllowAnonymous]
        public IActionResult Index()
        {
            var cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
            ShoppingCartModel.ShoppingCartItems = cart;
            ShoppingCartModel.Total = cart?.Sum(item => item.Product.UnitPrice * item.Quantity);
            return View();
        }

        [AllowAnonymous]
        public IActionResult Buy(int id)
        {
            if (SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart") == null)
            {
                List<ShoppingCartItem> cart = new List<ShoppingCartItem>();
                cart.Add(new ShoppingCartItem { Product = _context.Products.Include(p=>p.SubCategory).FirstOrDefault(p => p.ProductID == id), Quantity = 1 });
                SessionHelper.SetObjectAsJson(HttpContext.Session, "cart", cart);
            }
            else
            {
                List<ShoppingCartItem> cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
                int index = IsExist(id);
                if (index != -1)
                    cart[index].Quantity++;
                else
                    cart.Add(new ShoppingCartItem { Product = _context.Products.Include(p => p.SubCategory).FirstOrDefault(p => p.ProductID == id), Quantity = 1 });
                SessionHelper.SetObjectAsJson(HttpContext.Session, "cart", cart);
            }
            return RedirectToAction("Index");
        }

        [AllowAnonymous]
        public IActionResult Remove(int id)
        {
            List<ShoppingCartItem> cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
            int index = IsExist(id);

            cart.RemoveAt(index);

            SessionHelper.SetObjectAsJson(HttpContext.Session, "cart", cart);

            return RedirectToAction("Index");
        }

        [AllowAnonymous]
        private int IsExist(int id)
        {
            List<ShoppingCartItem> cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
            for (int i = 0; i < cart.Count; i++)
            {
                if (cart[i].Product.ProductID.Equals(id))
                    return i;
            }
            return -1;
        }
    }
}

SessionHelper. An error occurs here when trying to serialize an object.

public static class SessionHelper
{
    public static void SetObjectAsJson(this ISession session, string key, object value)
    {
        session.SetString(key, JsonConvert.SerializeObject(value));
    }

    public static T GetObjectFromJson<T>(this ISession session, string key)
    {
        var value = session.GetString(key);

        return value == null ? default : JsonConvert.DeserializeObject<T>(value);
    }
}
like image 909
Evgeny20 Avatar asked Oct 16 '25 17:10

Evgeny20


1 Answers

Not sure if you've solved this already, since it was asked yesterday, or why there were no reactions.

This is a commonly encountered issue when there are circular references in the object structure. The best practice is to avoid these in the first place in your DTOs / viewmodels.

But if it's fine for you to exclude these circular references from the serialization, you're lucky, because you're using Newtonsoft JSON, which supports configuring this behavior via the JsonSerializerSettings.ReferenceLoopHandling option.

I think this should be enough here, since it seems the self-referencing occurs between Products.SubCategory and SubCategory.Products, and what you want is SubCategory.Name, which should still be populated.

If it's not feasible to use this setting, you can just define a ProductDto or ProductViewModel, which could perhaps even flatten the hierarchy, and contain - besides the other properties - a SubCategoryName property instead of including the whole SubCategory entity (or however works for you).

There are three ways to use this aforementioned option.

1) Make it a default setting in ASP.NET Core serialization when you're returning an object result in controller actions:

services.AddMvc()
    .AddNewtonsoftJson(options => {
        options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    });

Keep in mind that if you were using ASP.NET Core 3+, and there were no existing AddNewtonsoftJson() call in the configuration beforehand, doing this will replace Microsoft's new System.Text.Json serializer with Newtonsoft's serializer (which is slower).

2) Make it a default global setting for Newtonsoft serializer:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};

// Then all subsequent manual serialization will be done with this setting.
string json = JsonConvert.SerializeObject (object);

Note that configuring it this way won't change the behavior of ASP.NET Core controller actions, because that serialization appears to use a separate settings instance.

3) Pass a JsonSerializerSettings instance each time explicitly when you're manually serializing:

JsonSerializerSettings settings = new JsonSerializerSettings
{
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
};
    
string json = JsonConvert.SerializeObject (object, settings);

So, in your case, solution 2) or 3) is what you need. And thanks for the edit; in my original answer I didn't take into consideration that you're manually calling SerializeObject().

like image 164
Leaky Avatar answered Oct 18 '25 13:10

Leaky



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!