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?
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 .