Self referencing loop detected - Getting back data from WebApi to the browser
C#Entity FrameworkSerializationasp.net Web-Apijson.netC# Problem Overview
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.
C# Solutions
Solution 1 - C#
> 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;
}
Solution 2 - C#
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;
Solution 3 - C#
In ASP.NET Core the fix is as follows:
services
.AddMvc()
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
Solution 4 - C#
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);
}
Solution 5 - C#
ASP.NET Core Web-API (.NET Core 2.0):
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcJsonOptions>(config =>
{
config.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
}
Solution 6 - C#
If using DNX / MVC 6 / ASP.NET vNext blah blah, even HttpConfiguration
is missing. You must config formatters by using following codes in your Startup.cs
file.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().Configure<MvcOptions>(option =>
{
//Clear all existing output formatters
option.OutputFormatters.Clear();
var jsonOutputFormatter = new JsonOutputFormatter();
//Set ReferenceLoopHandling
jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
//Insert above jsonOutputFormatter as the first formatter, you can insert other formatters.
option.OutputFormatters.Insert(0, jsonOutputFormatter);
});
}
Solution 7 - C#
As part of ASP.NET Core 3.0, the team moved away from including Json.NET by default. You can read more about that in general in the [Including Json.Net to netcore 3.x][1]https://github.com/aspnet/Announcements/issues/325
An error could be caused by you using lazyloading:
services.AddDbContext
fix: add to startup.cs
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
Solution 8 - C#
Using this:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
didn't work for me. Instead I created a new, simplified version of my model class just to test, and that returned fine. This article goes into some of the issues I was having in my model that worked great for EF, but weren't serializable:
http://www.asp.net/web-api/overview/data/using-web-api-with-entity-framework/part-4
Solution 9 - C#
ReferenceLoopHandling.Ignore didn't work for me. The only way I could get round it was to remove via code the links back to the parent I didn't want and keep the ones I did.
parent.Child.Parent = null;
Solution 10 - C#
For a new Asp.Net Web Application using .Net Framework 4.5:
Web Api: Goto App_Start -> WebApiConfig.cs:
Should look something like this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// ReferenceLoopHandling.Ignore will solve the Self referencing loop detected error
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//Will serve json as default instead of XML
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Solution 11 - C#
Due to lazy loading you are getting this error. Hence my suggestion is to remove virtual key from property. If you are working with API then lazy loading is not good for your API health.
No need to add extra line in your config file.
public class Question
{
public int QuestionId { get; set; }
public string Title { get; set; }
public ICollection<Answer> Answers { get; set; }
}
public class Answer
{
public int AnswerId { get; set; }
public string Text { get; set; }
public int QuestionId { get; set; }
public Question Question { get; set; }
}
Solution 12 - C#
I found this error was being caused when I generated an edmx (XML file that defines a conceptual model) of an existing database and it had Navigation properties to both the parent and child tables. I deleted all the navigation links to the parent objects, as I only wanted to navigate to children, and the problem was solved.
Solution 13 - C#
Entities db = new Entities()
db.Configuration.ProxyCreationEnabled = false;
db.Configuration.LazyLoadingEnabled = false;
Solution 14 - C#
You can dynamically create a new child collection to easily work around this problem.
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).Select(b=> new {
b.QuestionId,
b.Title
Answers = b.Answers.Select(c=> new {
c.AnswerId,
c.Text,
c.QuestionId }))
.ToList();
return questions;
}
Solution 15 - C#
None of the configurations in the answers above worked for me in ASP.NET Core 2.2.
I had the add the JsonIgnore
attributes on my virtual navigation properties.
public class Question
{
public int QuestionId { get; set; }
public string Title { get; set; }
[JsonIgnore]
public virtual ICollection<Answer> Answers { get; set; }
}
Solution 16 - C#
I think in most cases, if you are having this problem, modifying the serializer will onlly treat the symptom..
It is better to fix the underlying data source or calling code if possible. Obviously, if you have no control of those, there is nothing you can do, other than quit :)