locked
Getting JsonSerializationException RRS feed

  • Question

  • User1642115476 posted

    Hello,   I'm having an issue trying to convert an object to json. The error is a Newtonsoft.Json.JsonSerializationException:  

    Self referencing loop detected for property 'Project' with type  'System.Data.Entity.DynamicProxies.Project_F29F70EF89942F6344C5B0A3A7910EF55268857CD0ECC4A484776B2F4394EF79'. Path '[0].Categories[0]'.

    The problem is that the object (it's actually a list of objects) has a property which is another object that refers back to the first object:   

    public partial class Project { ...     public virtual ICollection<Category> Categories { get; set; } ... }   public partial class Category { ...     public virtual Project Project { get; set; } ... }  

    This is all fine and dandy as far as Entity Framework is concerned, but to convert this to json would result in an infinite regress, hence the exception.   Here is my code:

              public async Task<HttpResponseMessage> GetProjects()         {             var projects = _projectService.GetProjects().ToList();             string jsonString = JsonConvert.SerializeObject(projects); // <-- Offending line             return Request.CreateResponse(HttpStatusCode.OK, jsonString);         }  

    I've looked online for solutions to this and I found this stackoverflow post:   https://stackoverflow.com/questions/7397207/json-net-error-self-referencing-loop-detected-for-type   They suggest three solutions, none of which work:  

    1) Ignore the circular reference:           public async Task<HttpResponseMessage> GetProjects()         {             var projects = _projectService.GetProjects().ToList();             JsonSerializerSettings settings = new JsonSerializerSettings()             {                 ReferenceLoopHandling = ReferenceLoopHandling.Ignore             };             string jsonString = JsonConvert.SerializeObject(projects, settings);             return Request.CreateResponse(HttpStatusCode.OK, jsonString);         }  

    This resulted in the call to SerializeObject(...) hanging for a bit then throwing a  System.OutOfMemoryException (which tells me the circular references were NOT being ignored).   Mind you, the author of this proposed solution at stackoverflow says to set the ignore setting in WebApiConfig.cs but I tried that and it has no effect.   He also says:   "If you want to use this fix in a non-api ASP.NET project, you can add the above line to Global.asax.cs, but first add: var config = GlobalConfiguration.Configuration;"   Mine's a web API with no global file so I shouldn't have to do this.   I also don't want to ignore circular references because I don't want to lose data.  

    2) Preserve the circular reference:           public async Task<HttpResponseMessage> GetProjects()         {             var projects = _projectService.GetProjects().ToList();             JsonSerializerSettings settings = new JsonSerializerSettings()             {                 ReferenceLoopHandling = ReferenceLoopHandling.Serialize,                 PreserveReferencesHandling = PreserveReferencesHandling.Objects             };             string jsonString = JsonConvert.SerializeObject(projects, settings);             return Request.CreateResponse(HttpStatusCode.OK, jsonString);         }  

    This just resulted in the request timing out because it would just hang.   Again, the author says to put this in WebApiConfig.cs, but again this had no effect.  

    3) Add ignore/preserve reference attributes to the objects and properties:  

    Ignoring Categories:   public partial class Project { ...     [JsonIgnore]     public virtual ICollection<Category> Categories { get; set; } ... }  

    This has no effect. I hover over the project list and see that it still has categories, and each category still has an instance of the project. I still get the same exception.   Again, even if this worked, I don't want to ignore the categories.  

    Preserve Categories:   [JsonObject(IsReference = true)] public partial class Project { ...     public virtual ICollection<Category> Categories { get; set; } ... }  

    Again, same results.   Even if this method worked, the attributes wouldn't be preserved. I'd be doing it on Entity Framework classes which are re-generated automatically every time I recompile. (Is there a way to tell it to set these attributes in the model? Can I set them on the other half of the partial class?)   Alternatively, I'm open to suggestions other than converting to json and sending back in the response. Is there another way to get the data back to the client?   What would be the fix to this problem? Thanks.

    Wednesday, January 17, 2018 6:21 PM

Answers

  • User475983607 posted

    gib9898_00

    As for the second article, I'll await your response to see if you still think it's a good idea to return a view from the API to the MVC application.

    It is very simple.  Return a ViewModel, a POCO, a complex type whatever you want to call it from the Web API action.  Not an entity.  The POCO should be in a separate project that is shared by the API and MVC application.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, January 18, 2018 12:33 PM

All replies

  • User475983607 posted

    You should not return entities directly.  When an entity navigation property is serialized it does a DB call to fill the property this will keep happening until all the relationships are populated.  This is generally unwanted behavior.  Rather you should return a ViewModel and use a projection query to fill the ViewModel.

    This concept is covered in the Getting Started tutorials.

    However, you can configure NewtonSoft so it will not serialize unpopulated properties.  

    https://johnnycode.com/2012/04/10/serializing-circular-references-with-json-net-and-entity-framework/

    IMHO, using a ViewModel is a better solution as it reduces over-post and under-post situations.

    https://docs.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api

    Wednesday, January 17, 2018 8:39 PM
  • User1642115476 posted

    Just to be clear, the web api is not returning a response to a browser, it's returning a response to a controller.

    The flow goes like:

    browser --> MVC controller --> web API

    It returns on the same path. Can a controller receive a view?

    Wednesday, January 17, 2018 9:21 PM
  • User475983607 posted

    gib9898_00

    Just to be clear, the web api is not returning a response to a browser, it's returning a response to a controller.

    The flow goes like:

    browser --> MVC controller --> web API

    That does not matter.  The error is related to serialization.  The Web API action does not know the request came from a browser user agent or .NET code.

    gib9898_00

    It returns on the same path. Can a controller receive a view?

    I have no idea what you're asking.  An MVC controller action returns a View, an API action returns formatted data like JSON or XML.  Both actions commonly accept arguments as inputs for example a string, int, or complex object.

    Have you tried either if the two solutions above?  Is this an ASP Core Web API?

    Wednesday, January 17, 2018 9:43 PM
  • User1642115476 posted

    "I have no idea what you're asking.  An MVC controller action returns a View, an API action returns formatted data like JSON or XML.  Both actions commonly accept arguments as inputs for example a string, int, or complex object."

    I'm saying that when the api returns the data, it returns it to the MVC application. You're saying that API's return data like JSON or XML. This is exactly what my API is trying to doing now (trying to return JSON). Earlier you said "Rather you should return a ViewModel and use a projection query to fill the ViewModel." I'm assuming you meant from the MVC application to the browser, NOT from the API to the MVC application. If that is correct, I should NOT be returning a ViewModel from the API to the MVC application. That's why I asked the question: Can a controller [in the MVC application] receive [from the API] a view [equipped with a ViewModel]?

    "Have you tried either if the two solutions above?"

    I tried the first one. It doesn't work. That article must be ancient. The JsonNetFormatter it links to needs to be updated. It inherits from MediaTypeFormatter which this MSDN article shows had gone through some major changes:

    https://msdn.microsoft.com/en-us/library/system.net.http.formatting.mediatypeformatter%28v=vs.118%29.aspx?f=255&MSPPError=-2147217396

    ...and this stackoverflow article explains to reasons for:

    https://stackoverflow.com/questions/20095701/the-type-or-namespace-name-formattercontext-could-not-be-found-net-4-5?rq=1

    I laughed when I read: "So what gives? Isn’t it the year 2012?"

    We're already using JsonMediaTypeFormatter all over our application and I'm not about to wipe that out and use this JsonNetFormatter without doing a full battery of regression tests (which will never fly by management). Nevertheless, I tried it just to experiment. I managed to get JsonNetFormatter up-to-date and added this to WebApiConfig.cs (which as I said is what we use in this web API... no global.asax.cs here):

                JsonSerializerSettings settings = new JsonSerializerSettings
                {
                    PreserveReferencesHandling = PreserveReferencesHandling.Objects, // try PreserveReferencesHandling.Arrays
                    //ContractResolver = new CamelCasePropertyNamesContractResolver()
                };
                //config.Formatters.Clear();
                config.Formatters.Add(new JsonNetFormatter(new JsonSerializerSettings()));
     
                // Our JsonMediaTypeFormatter:
                //var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
               //jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();


    Notice what's been commented out:
    * Trying PreserveReferencesHandling.Arrays instead of PreserveReferencesHandling.Objects (because I'm trying to convert a List of projects, like an array, not a single project).
    * Setting the ContractResolver (because our JsonMediaTypeFormatter does).
    * NOT clearing all other formatters.
    * The original code to set the ContractResolver on our JsonMediaTypeFromatter.

    I left these in to show all the different combinations of things I've tried.

    At the end of the day, I'm still stuck with this in the API function in which I'm trying to convert a list of projects to json:

    public async Task<HttpResponseMessage> GetProjects()
            {
                var projects = _projectService.GetProjects().ToList();

                JsonSerializerSettings settings = new JsonSerializerSettings()
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                    PreserveReferencesHandling = PreserveReferencesHandling.Arrays
                };
                string jsonString = JsonConvert.SerializeObject(projects, settings);

                return Request.CreateResponse(HttpStatusCode.OK, jsonString);
            }

    I could take the code for creating the JsonNetFormatter in WebApiConfig.cs and place it here, but I don't know what good that would do. What would I do with a JsonNetFormatter here? I can't pass it into JsonConvert.SerializeObject(...). I can't assign it to anything in JsonConvert. I'm not sure how JsonConvert.SerializeObject(...) would use it.

    As for the second article, I'll await your response to see if you still think it's a good idea to return a view from the API to the MVC application.

    Thursday, January 18, 2018 12:12 AM
  • User-474980206 posted
    The api should have it own poco models and not use entity models. This is same notion as a view model. For an api you might call it the apimodel, vs view model, but the reasoning is the same.

    Thursday, January 18, 2018 1:08 AM
  • User475983607 posted

    gib9898_00

    As for the second article, I'll await your response to see if you still think it's a good idea to return a view from the API to the MVC application.

    It is very simple.  Return a ViewModel, a POCO, a complex type whatever you want to call it from the Web API action.  Not an entity.  The POCO should be in a separate project that is shared by the API and MVC application.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, January 18, 2018 12:33 PM
  • User1642115476 posted

    We ended up going with this approach. Thanks mgebhard for your help.

    Monday, January 22, 2018 5:30 PM