83

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.

Community
  • 1
  • 1
  • 6
    Use Dto for your web api, avoiding return Entity directly in your reaponse – cuongle Jun 26 '13 at 07:08
  • What is Dto? Our whole application uses EF, we are using AngularJS on the client, and we have no problems other than for this one case. –  Jun 26 '13 at 07:23
  • 1
    What I meant you should define your Dto for your Web Api, Dto is kind of similar with ViewModel in MVC. Dto is like a wrapper o your EF model to provide data your your client (angularjs). – cuongle Jun 26 '13 at 07:30
  • 2
    possible duplicate of [JSON.NET Error Self referencing loop detected for type](http://stackoverflow.com/questions/7397207/json-net-error-self-referencing-loop-detected-for-type) – mayabelle Nov 01 '13 at 15:13
  • You might have a look at my answer on **[“Self Referencing Loop Detected” exception with JSON.Net](https://stackoverflow.com/questions/40472419/self-referencing-loop-detected-exception-with-json-net/51235783#51235783)** page. – Murat Yıldız Jul 08 '18 at 20:36

16 Answers16

74

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; 
}
Sam Leach
  • 12,746
  • 9
  • 45
  • 73
  • 3
    I would like to add, for me setting ReferenceLoopHandling.Ignore did not work, setting it globaly or on the API startup didnt work at all. I managed to get it working by decorating the navigation property of the child class with [JsonIgnore] . I still get the ParentId but the Parent navigation is ignored while serialising. – Claiton Lovato Nov 19 '15 at 21:47
  • Hello, Ignoring the serialization will break the circular dependency Question>Answer>Question. Does the DTO approach preserve it? – Bartosz May 15 '17 at 10:55
  • I have this problem in an old ASP.NET MVC project. GlobalConfiguration.Configuration does not have Formatters. Can you plz suggest what can be done for that? – Ren Oct 02 '18 at 02:33
  • 1
    GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌​ferenceLoopHandling = ReferenceLoopHandling.Ignore; -> Where to put this line of code??? – anhtv13 May 13 '20 at 08:17
56

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;
Zze
  • 18,229
  • 13
  • 85
  • 118
Tallmaris
  • 7,605
  • 3
  • 28
  • 58
  • 79
    I know this is an old thread but for those who stumble upon it in the future, try: GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; – OttO Jan 07 '14 at 22:38
  • @OttO, your suggestions worked for me. Thanks very much. – J86 Jul 12 '14 at 10:09
  • 2
    Code goes into infinite loop and shows stack overflow exception after adding this line. – Microsoft Developer May 19 '16 at 06:24
  • GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; works better – Dragos Durlut Sep 26 '16 at 13:22
  • @Demodave you have to create your JsonSerializer using the static `Create()` method that accepts a setting as parameter. Docs: http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonSerializer_Create_1.htm – Tallmaris Oct 21 '16 at 16:10
22

In ASP.NET Core the fix is as follows:

services
.AddMvc()
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
Mohsen Afshin
  • 13,273
  • 10
  • 65
  • 90
21

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);
}
Bon
  • 1,083
  • 12
  • 23
  • 1
    You saved my day. I was wondering why the answer above was not working. Yes, using OWIN setting in Global.asax will not work. – Sithu Oct 20 '16 at 11:57
5

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;
    });
}
René Schindhelm
  • 1,887
  • 2
  • 14
  • 15
4

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);
        });
    }
Stewart Hou
  • 769
  • 6
  • 9
3

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;
        });
it dũng
  • 29
  • 2
2

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

Mike
  • 135
  • 2
  • 10
1

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;
Rob Sedgwick
  • 4,342
  • 6
  • 50
  • 87
1

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 }
        );
    }
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
0

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

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.

Zymotik
  • 6,412
  • 3
  • 39
  • 48
0

Entities db = new Entities()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;

Imran
  • 1,951
  • 1
  • 10
  • 7
0

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; 
    }
spadelives
  • 1,588
  • 13
  • 23
0

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; }
}
chakeda
  • 1,551
  • 1
  • 18
  • 40
0

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

I Stand With Russia
  • 6,254
  • 8
  • 39
  • 67