'Self referencing loop detected for property in WebApi 2
I've created a Web Api to save new products and reviews in database. Below is the WebApi code:
POST api/Products
[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = product.ProductId }, product);
}
Product class
public class Product
{
public int ProductId { get; set; }
[Required]
public string Name { get; set; }
public string Category { get; set; }
public int Price { get; set; }
//Navigation Property
public ICollection<Review> Reviews { get; set; }
}
Review class
public class Review
{
public int ReviewId { get; set; }
public int ProductId { get; set; }
[Required]
public string Title { get; set; }
public string Description { get; set; }
//Navigation Property
public Product Product { get; set; }
}
I'm using google chrome extension 'POSTMAN' to test the api. When I try to save details by creating a POST request in POSTMAN:
{
"Name": "Product 4",
"Category": "Category 4",
"Price": 200,
"Reviews": [
{
"ReviewId": 1,
"ProductId": 1,
"Title": "Review 1",
"Description": "Test review 1",
"Product": null
},
{
"ReviewId": 2,
"ProductId": 1,
"Title": "Review 2",
"Description": "Test review 2",
"Product": null
}
]
}
Which shows following error:
"Message":"An error has occurred.",
"ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.",
"ExceptionType":"System.InvalidOperationException",
"StackTrace":null,
"InnerException":{
"Message":"An error has occurred.",
"ExceptionMessage":"Self referencing loop detected for property 'Product' with type 'HelloWebAPI.Models.Product'.
How can I resolve this error?
Solution 1:[1]
Avoid using the same class you use in Entity Framework to map your entities in the API methods. Create DTO classes for using with API, then convert them to your Entity class manually or using tools like Auto Mapper which help you doing this.
Anyway, if you still want to use your Product and Review classes, the two simplest options I could remember are:
Removing the circular reference property
As identied by Stuart:
public class Review
{
public int ReviewId { get; set; }
public int ProductId { get; set; }
[Required]
public string Title { get; set; }
public string Description { get; set; }
}
Decorate circular reference property with [IgnoreDataMember]
public class Review
{
public int ReviewId { get; set; }
public int ProductId { get; set; }
[Required]
public string Title { get; set; }
public string Description { get; set; }
//Navigation Property
[IgnoreDataMember]
public Product Product { get; set; }
}
About ignoring properties when (de)serializing, you can refer to this question/answer.
Using DTO classes
You create two new classes:
public class CreateProductRequest
{
[Required]
public string Name { get; set; }
public string Category { get; set; }
public int Price { get; set; }
//Navigation Property
public IEnumerable<CreateReviewRequest> Reviews { get; set; }
}
public class CreateReviewRequest
{
[Required]
public string Title { get; set; }
public string Description { get; set; }
}
Then fix your controller action like this:
[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(CreateProductRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var product = new Product
{
Name = request.Name,
Category = request.Category,
Price = request.Price
}
if (request.Reviews != null)
product.Reviews = request.Reviews.Select(r => new Review
{
Title = r.Title,
Description = r.Description
});
db.Products.Add(product);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = product.ProductId }, product);
}
I know it looks redundant, but this is because I'm doing everything manually. If I was using something like Auto Mapper, we could reduce it to:
[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(CreateProductRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var product = AutoMapper.Map<Product>(request);
db.Products.Add(product);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = product.ProductId }, product);
}
Mixing Entity Framework (or any other ORM) classes with service layer classes (e.g Web API, MVC, WCF) often causes problems for people which still don't know how serialization occurs.
There are situations where I do use directly the classes, for simple scenarios, because this is not a rule that should be strictly followed. I believe each situation has its own needs.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |

