2

For some reason only the first item of each array is being returned as JSON, any clues why?

Here is what I see during debugging, as you can tell, I have two items in 'Category' and two items in 'Tasks':

enter image description here

Postman JSON result (it should return all items, shouldn't it?): enter image description here

For reference, here is my 'Category.cs':

public class Category
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public DateTime Timestamp { get; set; }
    public string Username { get; set; }

    public ApplicationUser ApplicationUser { get; set; }

    public virtual ICollection<Task> Tasks { get; set; }
}

My 'Task.cs':

public class Task
{
    public int TaskId { get; set; }
    public string Name { get; set; }
    public DateTime Timestamp { get; set; }

    public virtual Category Category { get; set; }
}

and my Api:

    [HttpGet]
    public JsonResult Get()
    {
        var result = _repo.GetAllForUser("[email protected]");

        return Json(result);
    }

And repository:

    public IEnumerable<Category> GetAllForUser(string name)
    {
        return _ctx.Categories
                    .Where(c => c.ApplicationUser.UserName == name)
                    .Include(c => c.Tasks)
                    .ToList();           
    }

Here is what I insert into database, and what I should retrieve from the Api:

        categories.Add(new Category
        {
            Name = "cat 1",
            Tasks = new List<Task>
            {
                new Task { Name="task 1" },
                new Task { Name="task 2" }
            }
        });
        categories.Add(new Category
        {
            Name = "cat 2",
            Tasks = new List<Task>
            {
                new Task { Name="task 3" },
                new Task { Name="task 4" }
            }
        });
9
  • Could you share how your Task model looks like? Commented Aug 9, 2016 at 16:10
  • provide more relevant code Commented Aug 9, 2016 at 16:19
  • edited question by adding more related code Commented Aug 9, 2016 at 16:29
  • 1
    @mic4ael: There is no Json class, it's the Json method from controller which is a shortcut for new JsonObjectResult with status code Commented Aug 9, 2016 at 16:45
  • 1
    Are you running your app on Windows? Based on your models, it appears you have circular references(Task references Category and vice versa) which results in Json.net throwing exceptions : github.com/aspnet/IISIntegration/issues/39 ...try this: enable logging and see if your are seeing any exceptions being logged...also try on non-IIS scenarios (example: directly in console doing dotnet run and enable console logging) Commented Aug 9, 2016 at 18:12

2 Answers 2

3

As Kiran pointed out, you have circular references in your models, which is causing an exception. This bug is incorrectly making it look like the request is completing with partial data. (The circular reference is Category -> Tasks -> Task -> Category)

What's actually happening is an unhandled exception halfway through the JSON serialization of the response. Instead of aborting the connection (as it should), ASP.NET Core is sending back everything that was serialized until the error occurred.

You can either define a DTO class that doesn't include the reference from Task back to Category, or return an anonymous type:

[HttpGet]
public JsonResult Get()
{
    var result = _repo.GetAllForUser("[email protected]");

    var response = new {
        categoryId: result.CategoryId,
        name: result.Name,
        timestamp: result.Timestamp,
        username: result.Username,
        tasks: result.Tasks.Select(t => new {
            taskId: t.TaskId,
            name: t.Name,
            timestamp: t.Timestamp
        })
    };

    return Json(response);
}

If you do this often, it makes sense to create DTO class and use a tool like AutoMapper to do the mapping for you.

Sign up to request clarification or add additional context in comments.

2 Comments

Great explanation, appreciate it. Indeed, it was the circular reference, once I have removed the Category variable from my Task class, it worked as expected. What is weird to me is that this is how EF recommends to wire Model First classes. It is also easy to navigate from class to class when performing queries. So what is the actual recommendation, should I prevent the circular reference when creating my Model First classes, or perhaps I shouldjust return an anonymous type from my Api, just like you have specified above. thanks
@lucas It's usually not a good idea to directly return entity models as responses, since they usually have things you don't want or need the user to see (including circular references). I think the best solution is to have another set of models (DTOs or "response models") that you can map to.
1

Nate's answer is perfect, he responded with great recommendations that pointed me to find great solution for my scenario. I have decided to share it in case someone would run into similar problem. As a reminder, this is ASP.NET Core Model First MVC project with Api. I have utilized AutoMapper for mapping my Model First classes with corresponding view models.

Here are my Model First classes:

public class Category
{
    public Category()
    {
        Tasks = new HashSet<Task>();
    }

    public int CategoryId { get; set; }
    public string Name { get; set; }
    public DateTime Timestamp { get; set; }
    public string Username { get; set; }

    public ApplicationUser ApplicationUser { get; set; }

    public virtual ICollection<Task> Tasks { get; set; }
}

public class Task
{
    public int TaskId { get; set; }
    public string Name { get; set; }
    public DateTime Timestamp { get; set; }
    public int CategoryId { get; set; }

    public virtual Category Category { get; set; }
}

Corresponding view models:

public class CategoryViewModel
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public DateTime Timestamp { get; set; }

    public IEnumerable<TaskViewModel> Tasks { get; set; }
}

public class TaskViewModel
{
    public int TaskId { get; set; }
    public string Name { get; set; }
    public DateTime Timestamp { get; set; }
}

AutoMapper setup in 'Setup.cs' file:

Mapper.Initialize(config =>
        {
            config.CreateMap<Category, CategoryViewModel>().ReverseMap();
            config.CreateMap<Task, TaskViewModel>().ReverseMap();
        });

And finally my fully working Api method:

[HttpGet]
    public JsonResult Get()
    {
        var result = Mapper.Map<IEnumerable<CategoryViewModel>>(_repo.GetAllForUser("[email protected]"));

        return Json(result);
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.