Reputation: 4685
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':
Postman JSON result (it should return all items, shouldn't it?):
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" }
}
});
Upvotes: 2
Views: 1545
Reputation: 4685
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);
}
Upvotes: 1
Reputation: 53600
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.
Upvotes: 3