Reputation: 89
I have 2 classes:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public B myB { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<A> myAs { get; set; }
}
Im using Postman to test the Api calls.
public IEnumerable<B> GetBs()
{
return _context.Bs.Include(b => b.myAs).ToList();
}
returns as expected, B objects and a list of their associated A objects:
{
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
},
{
"Id": 2,
"Name": "A2"
}
]
},
{
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 3,
"Name": "A3"
},
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
The reverse however returns a wierd hierarchical structure:
public IEnumerable<A> GetAs()
{
return _context.As.Include(a => a.myB).ToList();
}
returns:
[
{
"Id": 1,
"Name": "A1",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 2,
"Name": "A2"
}
]
}
},
{
"Id": 2,
"Name": "A2",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
}
]
}
},
{
"Id": 3,
"Name": "A3",
"myB": {
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
}
]
The GetAs method returns A objects with B objects with further nested A objects.
My understanding after a little research (I could be very wrong here), is that because A has a navigation property to B (myB) and B has a navigation property to a list of A objects (myAs) this is causing some kind of loop.
My questions are
note A and B are not actually my domain models. I just wanted to keep the example as simple as possible.
Thanks in advance.
Upvotes: 1
Views: 150
Reputation: 31873
A few things here:
The output has the expected shape. As you suspect, the bidirectional references are being expanded by the serializer. Think of what would happen if you manually serialized each property of each object recursively. That is what happening.
To solve the immediate problem configure your default serializer settings like this:
jsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.Serialization.ReferenceLoopHandling.Ignore;
The above is useful when prototyping but when your application is more formalized, you should create and return dedicated view model types from your web API endpoints.
public class AViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<AViewModel> myAs { get; set; }
}
public IEnumerable<BViewModel> GetBs()
{
return _context.Bs.Include(b => b.myAs)
.Select(b => new BViewModel
{
Id = b.Id,
Name = b.Name,
As = b.As.Select(a => new AViewModel
{
Id = a.Id,
Name = a.Name
})
})
.ToList();
}
It is worth noting that there are libraries, such as the well regarded AutoMapper, that can perform these translations between model types for you, automatically assigning corresponding properties by name using reflection.
Personally, I try to avoid reflection based approaches as much as possible as they tend to render code difficult to reason about statically. This hinders both human readers such as ourselves and tools like the C# language.
That said, it can be worth the tradeoff depending on the task at hand. I hope to eventually see language level support that eliminates such boilerplate assignments without stepping into the realm of stringiness but I have a long time to wait.
Upvotes: 1