Reputation: 33
I have a fairly simple Web API written in C#, using EF. The idea is pretty much a to do application, with the specific relationship being: A User can have many To Do Lists, and each To Do List can have many To Do Items.
Here are my Data Models:
public class User
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public string? Password { get; set; } // Be sure to properly hash and salt passwords
public string? ProfilePhotoUrl { get; set; } // Store the URL to the user's profile photo
public List<ToDoList>? ToDoLists { get; set; } // A user can have multiple to-do lists
}
public class ToDoList
{
public Guid Id { get; set; }
public string? Title { get; set; }
public List<ToDo>? ToDos { get; set; } // Each to-do list contains multiple to-dos
// Foreign Key to User
public Guid UserId { get; set; }
public User? User { get; set; }
}
public class ToDo
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public bool? InProgress { get; set; }
public bool? IsComplete { get; set; }
public DateTime Created { get; set; } = DateTime.Now;
public ToDoList? ToDoList { get; set; }
public Guid? ToDoListId{ get; set; }
}
I have a controller that allows Users to register, login etc. One of the things I want to do is retrieve each To Do List a User may have. I have the code in a service:
public Task<User> GetToDoListsForUsers(Guid userId)
{
return _context.Users
.Include(u => u.ToDoLists)
.FirstOrDefaultAsync(u => u.Id == userId);
}
Which is then called by the controller:
[HttpGet("{userId}/todolists")]
public async Task<ActionResult<IEnumerable<ToDoList>>> GetToDoListsForUser(Guid userId)
{
var user = await _userService.GetToDoListsForUsers(userId);
if (user == null)
{
return NotFound("User not found");
}
var toDoLists = user.ToDoLists;
if (toDoLists.Count == 0)
{
return NotFound("user has no lists");
}
return Ok(toDoLists);
}
However, when I run this I get the following Error:
An unhandled exception has occurred while executing the request.
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.Id.
I believe this is because the User has a reference to ToDoList, and the ToDoList has a reference back to User but I need this reference both ways as they are Foreign Keys.
I also came across this solution online to add in these lines:
var jsonSettings = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve,
};
var serializedUser = JsonSerializer.Serialize(user, jsonSettings);
var deserializedUser = JsonSerializer.Deserialize<User>(serializedUser, jsonSettings);
var toDoLists = deserializedUser.ToDoLists;
To the controller method but this did not work either.
Any help available in fixing this?
Upvotes: 3
Views: 656
Reputation: 143098
TBH the found "solution" does not make any sense, using the same settings with ReferenceHandler.Preserve
will just restore the references back.
You can just return the serialized manually data:
var serializedLists = JsonSerializer.Serialize(user.ToDoLists, jsonSettings);
return Content(serializedLists);
Or better apply the ReferenceHandler.Preserve
setting globally:
builder.Services.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);
Also option worth considering (one which I use by default) - creating DTOs and mapping to them so no loops are present and response has only required data.
Read a little bit more - Avoid or control circular references in Entity Framework Core .
Upvotes: 2