Instance Annotations for properties with $select query option
@xuzhg is already working on this.
Since Mar 26, 2024.
- Dominant language
- C#
- Stars
- 505
- Forks
- 186
- PR merge metrics
- No merged PRs in 30d
Description
Describe the bug
I would like to add instance annotation to a property (OwnerId) by including an additional property (OwnerIdName) as its value.
This setup works as expected without using the $select query option. However, when $select is applied, the resourceContext.ResourceInstance does not include the OwnerIdName property, leading to a null reference error because the OwnerIdName is not fetched and thus cannot be added as an instance annotation.
I am seeking advice on how to ensure OwnerIdName is included as an instance annotation for OwnerId when $select is applied, or how to work around this issue
Code
I've created:
-
ODataSerializerProviderandODataResourceSerializerto provide custom serialization and add annotation. -
IEdmModel, where I add
OwnerIdNameas ignored property, -
Simple Controller to consume client request.
Below i've attached my codes, result without $select and error with $select param.
Additionally, i've also added codes to my repo.
public class CustomODataSerializerProvider : ODataSerializerProvider
{
private IServiceProvider _rootProvider;
public CustomODataSerializerProvider(IServiceProvider rootContainer) : base(rootContainer)
{
_rootProvider = rootContainer;
}
public override IODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.Definition.TypeKind == EdmTypeKind.Entity)
{
return new CustomODataResourceSerializer(this);
}
return base.GetEdmTypeSerializer(edmType);
}
}
public class CustomODataResourceSerializer(IODataSerializerProvider provider) : ODataResourceSerializer(provider)
{
public override ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
{
var resource = base.CreateResource(selectExpandNode, resourceContext);
if (resource != null)
{
var resourceContextPropDictionary = GetPropertiesDictionary(resourceContext.ResourceInstance);
foreach (var prop in resource.Properties)
{
var propNameToLower = prop.Name.ToLower();
if (resourceContextPropDictionary.TryGetValue($"{propNameToLower}name", out object lookupNamePropValue))
{
prop.InstanceAnnotations.Add(new ODataInstanceAnnotation("lookup.name", new ODataPrimitiveValue(lookupNamePropValue)));
}
}
}
return resource;
}
private Dictionary<string, object> GetPropertiesDictionary(object obj)
{
Dictionary<string, object> propertiesDictionary = new Dictionary<string, object>();
PropertyInfo[] properties = obj.GetType().GetProperties();
foreach (var property in properties)
{
object value = property.GetValue(obj);
propertiesDictionary.Add(property.Name.ToLower(), value);
}
return propertiesDictionary;
}
}
public static IEdmModel CreateEdmModel()
{
var builder = new ODataConventionModelBuilder();
var accountConfiguration = builder.EntitySet<Account>("Accounts").EntityType;
accountConfiguration.Ignore(x => x.OwnerIdName);
return builder.GetEdmModel();
}
builder.Services.AddControllers()
.AddOData(opt => opt.AddRouteComponents("odata", EdmModelBuilder.CreateEdmModel(),
builder => builder.AddSingleton<IODataSerializerProvider, CustomODataSerializerProvider>()).EnableQueryFeatures());
public class Account
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid OwnerId { get; set; }
public string OwnerIdName { get; set; }
}
public class AccountsController() : ODataController
{
private static readonly IList<Account> accounts =
[
new() { Id = Guid.NewGuid(), Name = "Interstellar Mining Corp", OwnerId = Guid.NewGuid(), OwnerIdName = "User 1" },
new() { Id = Guid.NewGuid(), Name = "Quantum Communications", OwnerId = Guid.NewGuid(), OwnerIdName = "User 2" },
new() { Id = Guid.NewGuid(), Name = "Galaxy Graphics", OwnerId = Guid.NewGuid(), OwnerIdName = "User 3" }
];
[HttpGet]
[EnableQuery]
public IEnumerable<Account> Get()
{
return accounts;
}
}
Result without $select param:
{
"@odata.context": "https://localhost:7243/odata/$metadata#Accounts",
"value": [
{
"Id": "fb752e65-4649-447d-859e-bc3d5349edee",
"Name": "Interstellar Mining Corp",
"OwnerId@lookup.name": "User 1",
"OwnerId": "f04b6814-f409-467e-84bf-f993bff69d7b"
},
{
"Id": "1bbfc7fe-04ae-4b4a-adfc-b77e8251cd67",
"Name": "Quantum Communications",
"OwnerId@lookup.name": "User 2",
"OwnerId": "80d96bd6-74cd-4970-b071-dc10264b3aeb"
},
{
"Id": "7c50381d-0007-48b1-9e24-b04c249f7004",
"Name": "Galaxy Graphics",
"OwnerId@lookup.name": "User 3",
"OwnerId": "a71672ac-9e39-446a-bf1d-99775d2178a8"
}
]
}
Error with $select param (line 23 in file CustomODataResourceSerializer.cs):
System.ArgumentNullException: 'Cannot create an ODataPrimitiveValue from null; use ODataNullValue instead.'
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.