8
votes

I have a Web API Controller in my Asp.Net Core Web API 2.2 project.

Messageboard model:

public class MessageBoard
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public ICollection<Message> Messages { get; set; }
    }

Message model:

public class Message
    {
        public long Id { get; set; }
        public string Text { get; set; }
        public string User { get; set; }
        public DateTime PostedDate { get; set; }

        public long MessageBoardId { get; set; }
        [ForeignKey("MessageBoardId")]
        public MessageBoard MessageBoard { get; set; }
    }

This is one of my Web API Controller actions, shortened for brevity:

[Route("api/[controller]")]
[ApiController]
public class MessageBoardsController : ControllerBase
{        
      // GET: api/MessageBoards
      [HttpGet]
      public async Task<ActionResult<IEnumerable<MessageBoard>>> GetMessageBoards()
      {
         return await _context.MessageBoards
            .Include(i => i.Messages)
            .ToListAsync();
      }
}

Whenever I issue a GET request to MessageBoards, only part of the correct JSON is returned. Here is the returned JSON from accessing https://localhost:44384/api/MessageBoards/ on Postman:

[{"id":1,"name":"Test Board 2","description":"A 2nd Message board for testing purposes.","messages":[{"id":1,"text":"Posting my first message!","user":"Jesse","postedDate":"2019-01-01T00:00:00","messageBoardId":1

The JSON is cut-off (hence why it's an ugly block and not beautified by Postman), presumably due to the MessageBoard property on the Message model since it is the first missing JSON item.

How can I make the action correctly return the list of MessageBoards and child Messages?

3
Serializing objects with null properties to json is not an unexpected occurrence and is completely supported by pretty much all frameworks. Not sure what serializer you're using but you might want to swap it out to see if that was the cause, and if it was, open a bug report. - user1228
@Will I am using the default Web API Serializer. I haven't changed any serialization settings since creating the project. - Jack
Have you checked how you configured your serializer to deal with circular dependencies? It seems to me that once the serializer reaches the circular dependency, it just stops the serialization (Maybe an exception?) - Ricardo Alves
@RicardoAlves I hadn't configured it was the issue. I applied Tanvir's configuration and the circular dependency was fixed. - Jack

3 Answers

16
votes

I see you are using Eager Loading in your query. So add the following configuration in your Startup class to ignore cycles that it finds in the object graph and to generate JSON response properly.

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
}

For more details: Related data and serialization in EF Core

1
votes

The selected answer was correct in my case as well, my JSON response was getting truncated by a reference loop in my JSON response, and setting ReferenceLoopHandling.Ignore did indeed solve my issue. However, this is not the best solution in my opinion, as this maintains the circular references in your model. A better solution would use the [JsonIgnore] attribute within the model.

The issue in your model is here:

public class MessageBoard
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public ICollection<Message> Messages { get; set; }
    }

public class Message
    {
        public long Id { get; set; }
        public string Text { get; set; }
        public string User { get; set; }
        public DateTime PostedDate { get; set; }

        public long MessageBoardId { get; set; }
        [ForeignKey("MessageBoardId")]
        public MessageBoard MessageBoard { get; set; } //This is the cause of your circular referece!!!
    }  

As you can see, your MessageBoard navigation property is where this response is truncated. Specifically, it will cause each Message in the json response to contain all of the MessageBoard information for each Message entry in the response. Newtonsoft does not like this. The solution is to simply [JsonIngore] the navigation properties that cause this circular reference. In your code this would be:

public class MessageBoard
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public ICollection<Message> Messages { get; set; }
    }

public class Message
    {
        public long Id { get; set; }
        public string Text { get; set; }
        public string User { get; set; }
        public DateTime PostedDate { get; set; }

        public long MessageBoardId { get; set; }

        [JsonIgnore]
        [ForeignKey("MessageBoardId")]
        public MessageBoard MessageBoard { get; set; } //fixed!!!
    }  
0
votes

Try to create and return a DTO or a new struct/class that will not have cyclic info (MessageBoard has Message that has MessageBoard etc...)