Self referencing loop detected - Getting back data from WebApi to the browser
I am using Entity Framework and having a problem with getting parent and child data to the browser. Here are my classes:
public class Question
{
public int QuestionId { get; set; }
public string Title { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
public class Answer
{
public int AnswerId { get; set; }
public string Text { get; set; }
public int QuestionId { get; set; }
public virtual Question Question { get; set; }
}
I am using the following code to return the question and answer data:
public IList<Question> GetQuestions(int subTopicId, int questionStatusId)
{
var questions = _questionsRepository.GetAll()
.Where(a => a.SubTopicId == subTopicId &&
(questionStatusId == 99 ||
a.QuestionStatusId == questionStatusId))
.Include(a => a.Answers)
.ToList();
return questions;
}
On the C# side this seems to work however I notice that the answer objects have references back to the question. When I use the WebAPI to get the data to the browser I get the following message:
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.
Self referencing loop detected for property 'question' with type 'Models.Core.Question'.
Is this because the Question has Answers and the Answers have a reference back to Question? All the places I have looked suggest having a reference to the parent in the child so I am not sure what to do. Can someone give me some advice on this.
Is this because the Question has Answers and the Answers have a reference back to Question?
Yes. It cannot be serialized.
EDIT: See Tallmaris's answer and OttO's comment as it is simpler and can be set globally.
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
Old Answer:
Project the EF object Question
to your own intermediate or DataTransferObject. This Dto can then be serialized successfully.
public class QuestionDto
{
public QuestionDto()
{
this.Answers = new List<Answer>();
}
public int QuestionId { get; set; }
...
...
public string Title { get; set; }
public List<Answer> Answers { get; set; }
}
Something like:
public IList<QuestionDto> GetQuestions(int subTopicId, int questionStatusId)
{
var questions = _questionsRepository.GetAll()
.Where(a => a.SubTopicId == subTopicId &&
(questionStatusId == 99 ||
a.QuestionStatusId == questionStatusId))
.Include(a => a.Answers)
.ToList();
var dto = questions.Select(x => new QuestionDto { Title = x.Title ... } );
return dto;
}
You can also try this in your Application_Start()
:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
It should fix your problem without going through many hoops.
EDIT: As per OttO's comment below, use:
ReferenceLoopHandling.Ignore
instead.
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
If using OWIN, remember, no more GlobalSettings for you! You must modify this same setting in an HttpConfiguration object which gets passed to the IAppBuilder UseWebApi function (or whatever service platform you're on)
Would look something like this.
public void Configuration(IAppBuilder app)
{
//auth config, service registration, etc
var config = new HttpConfiguration();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//other config settings, dependency injection/resolver settings, etc
app.UseWebApi(config);
}