4

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

1 Answer 1

10

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().

Sign up to request clarification or add additional context in comments.

2 Comments

#3 was the fix for me.
That fixed it for me, but the List Item that was changed for me, was no updated in the json file. So no it didn't really work for me. It just got rid of the error for me.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.