This is a blog post about why a custom OData function is not returning any data despite returning a 200 code. Or why your function or action is not returning the custom navigation properties you created on a main entity. This can happen when fetching complex objects without using the $expand
query parameter. The $expand
parameter will expand the specific objects in the request. If you don't use expand query parameter your complex objects will not be returned in the response.
In this post I'm going to break down my scenario and the steps that I took to fix the issue. In the example below my function is called GetUsersInRoles()
that returns a custom view model with two properties, Administrators and Users.
The Scenario: GetUsersInRoles()
Function
My situation called for returning a custom view model with two complex objects for roles, Administrators and Users, both of type List<User>
. Each role contains a list of users with properties like Name, Email, and IsActive.
Here's a simplified version of the custom view model:
public class UsersInRolesViewModel
{
public List<User> Administrators { get; set; }
public List<User> Editors { get; set; }
}
public class User
{
public string Name { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
}
And the function in your OData controller might look something like this:
public class UsersController : ODataController
{
[HttpGet]
public IHttpActionResult GetUsersInRoles()
{
var usersInRoles = new UsersInRolesViewModel
;
return Ok(usersInRoles);
}
}
You would expect to access this data by navigating to /odata/users/GetUsersInRoles()
. However, if you've not expanded the complex objects you're met with no data returned or missing complex data from your response. If you have other properties in your response object, those will show up, but not the complex objects as they need to be expanded.
Understanding the Issue
The problem here is not with your model or the controller; it's with how OData handles the serialization of nested custom classes. By default, OData does not expand and serialize nested objects unless explicitly told to do so. This behavior aims to optimize performance and avoid inadvertently sending unnecessary large amounts of nested data over the network.
The Solution: Using the $expand
Query Option
To instruct OData to include the nested classes (Administrators
and Editors
in this case), you need to use the $expand
query option in your request URL. The $expand
option tells OData to "expand" and include the specified nested properties in the response.
The corrected URL to access your data would be:
/odata/users/GetUsersInRoles()?$expand=Administrators,Editors
To make this work, ensure your OData configuration in Program.cs is set up for the custom function like so:
var usersEntitySet = builder.EntitySet<User>("Users");
usersEntitySet.Function("GetUsersInRoles").Returns<UsersInRolesViewModel>();
- Adjust the Model or Controller if Necessary: In some cases, you might need to adjust your model or controller to ensure that the
$expand
option is properly applied. However, in most scenarios involving simple property expansion, no additional changes are required beyond enabling the query option in your configuration.
Conclusion
The $expand
query option is a powerful tool in your arsenal for controlling data serialization, especially when dealing with complex nested models. By correctly applying this option, you can ensure that your application delivers exactly the data it needs, no more and no less.