OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
Other
446 stars 155 forks source link

$select non-nullable property inside a nullable complex object throws #1262

Open Xriuk opened 2 weeks ago

Xriuk commented 2 weeks ago

Assemblies affected ASP.NET Core OData 8.2.4

Describe the bug A $select path targeting a non-nullable property inside a nullable complex property (with null value) of an entity throws:

Microsoft.OData.ODataException: The property '...' of type '...' has a null value, which is not allowed.

Reproduce steps A GET request to:

https://.../ContactFormEmails?$select=Locale/LanguageCode

Throws:

Microsoft.OData.ODataException: The property 'LanguageCode[Nullable=False]' of type 'Edm.String' has a null value, which is not allowed.
17:49:43:603       at Microsoft.OData.WriterValidationUtils.ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, String propertyName, IEdmModel model)
17:49:43:603       at Microsoft.OData.WriterValidator.ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, String propertyName, Boolean isTopLevel, IEdmModel model)
17:49:43:603       at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteNullPropertyAsync(ODataPropertyInfo property)
17:49:43:603       at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WritePropertyAsync(ODataProperty property, IEdmStructuredType owningType, Boolean isTopLevel, IDuplicatePropertyNameChecker duplicatePropertyNameChecker, ODataResourceMetadataBuilder metadataBuilder)
17:49:43:603       at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WritePropertiesAsync(IEdmStructuredType owningType, IEnumerable`1 properties, Boolean isComplexValue, IDuplicatePropertyNameChecker duplicatePropertyNameChecker, ODataResourceMetadataBuilder metadataBuilder)
17:49:43:603       at Microsoft.OData.JsonLight.ODataJsonLightWriter.StartResourceAsync(ODataResource resource)
17:49:43:603       at Microsoft.OData.ODataWriterCore.<>c.<<WriteStartResourceImplementationAsync>b__196_0>d.MoveNext()
17:49:43:603    --- End of stack trace from previous location ---
17:49:43:603       at Microsoft.OData.ODataWriterCore.InterceptExceptionAsync[TArg0](Func`3 action, TArg0 arg0)
17:49:43:603       at Microsoft.OData.ODataWriterCore.WriteStartResourceImplementationAsync(ODataResource resource)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteResourceAsync(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteComplexAndExpandedNavigationPropertyAsync(IEdmProperty edmProperty, SelectItem selectItem, ResourceContext resourceContext, ODataWriter writer)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteComplexPropertiesAsync(SelectExpandNode selectExpandNode, ResourceContext resourceContext, ODataWriter writer)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteResourceAsync(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetItemAsync(Object item, IEdmStructuredTypeReference elementType, Boolean isUntypedCollection, IEdmTypeReference resourceSetType, ODataWriter writer, IODataEdmTypeSerializer resourceSerializer, ODataSerializerContext writeContext)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSetAsync(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectAsync(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
17:49:43:603       at Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatterHelper.WriteToStreamAsync(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, HttpRequest request, IHeaderDictionary requestHeaders, IODataSerializerProvider serializerProvider)

Data Model

public class Language{
    public string LanguageCode { get; set; }
}

public class ContactFormEmail {
    public int Id { get; set; }

    ...

    public Language? Locale { get; set; }
}

EDM (CSDL) Model

...
<EntityType Name="ContactFormEmail">
    <Key>
        <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="Edm.Int32" Nullable="false" />
    ...
    <Property Name="Locale" Type="Models.Language" />
</EntityType>
<ComplexType Name="Language">
    <Property Name="LanguageCode" Type="Edm.String" Nullable="false" />
</ComplexType>
...
habbes commented 2 weeks ago

@Xriuk Seems like the error in the writer validation and no in the select clause. I'm curious, does the same endpoint return the response correctly if you remove the $select query option from the url?

Xriuk commented 2 weeks ago

@habbes Yes, the endpoint returns the correct response without the $select query option, or with just $select=Locale where it correctly returns Locale: null, so it is a nesting problem.