Self referencing loop detected - Getting back data from WebApi to the browser

C#Entity FrameworkSerializationasp.net Web-Apijson.net

C# 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.Re‌​ferenceLoopHandling = 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(options => options.UseLazyLoadingProxies()... or db.Configuration.LazyLoadingEnabled = true;

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 :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1943020View Question on Stackoverflow
Solution 1 - C#Sam LeachView Answer on Stackoverflow
Solution 2 - C#TallmarisView Answer on Stackoverflow
Solution 3 - C#Mohsen AfshinView Answer on Stackoverflow
Solution 4 - C#BonView Answer on Stackoverflow
Solution 5 - C#René SchindhelmView Answer on Stackoverflow
Solution 6 - C#Stewart HouView Answer on Stackoverflow
Solution 7 - C#it dũngView Answer on Stackoverflow
Solution 8 - C#MikeView Answer on Stackoverflow
Solution 9 - C#Rob SedgwickView Answer on Stackoverflow
Solution 10 - C#OgglasView Answer on Stackoverflow
Solution 11 - C#PAWAN RAJ ShakyaView Answer on Stackoverflow
Solution 12 - C#ZymotikView Answer on Stackoverflow
Solution 13 - C#ImranView Answer on Stackoverflow
Solution 14 - C#spadelivesView Answer on Stackoverflow
Solution 15 - C#chakedaView Answer on Stackoverflow
Solution 16 - C#LastTribunalView Answer on Stackoverflow