diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs index bbe7a6b5f..68371295f 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs @@ -489,8 +489,14 @@ public virtual void ApplyStructuralProperties(object resource, ODataResourceWrap throw new ArgumentNullException(nameof(resourceWrapper)); } - foreach (ODataProperty property in resourceWrapper.Resource.Properties) + foreach (ODataPropertyInfo propertyInfo in resourceWrapper.Resource.Properties) { + if (!(propertyInfo is ODataProperty property)) + { + // Cannot deserialize property without value + continue; + } + ApplyStructuralProperty(resource, property, structuredType, readContext); } } diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs index cc9a6346e..6076ebc26 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs @@ -200,6 +200,10 @@ private static void ReadODataItem(ODataReader reader, Stack it resourceSetParentWrapper.Items.Add(new ODataPrimitiveWrapper((ODataPrimitiveValue)reader.Item)); break; + case ODataReaderState.NestedProperty: + // Property without value - do nothing + break; + default: Contract.Assert(false, "We should never get here, it means the ODataReader reported a wrong state."); break; diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs index 8edab1348..5477d3e3e 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs @@ -1553,6 +1553,38 @@ public void ReadAsync_ThrowsOnUnknownEntityType() typeof(Product), _readContext).Wait(), "The property 'Concurrency' does not exist on type 'ODataDemo.Product'. Make sure to only use property names that are defined by the type or mark the type as open type."); } + [Fact] + public async Task ReadAsync_ForPropertyWithoutValue() + { + // Arrange + string content = "{" + + "\"ID\":0," + + "\"Name\":\"Bread\"," + + "\"Description\":\"Whole grain bread\"," + + "\"PublishDate\":\"1997-07-01\"," + + "\"ReleaseDate@odata.type\":\"#Edm.DateTimeOffset\"," + // OData annotation - Property without value + "\"DiscontinuedDate@Is.DateTimeOffset\":true," + // Custom annotation - Property without value + "\"Rating\":4," + + "\"Price\":2.5" + + "}"; + + ODataResourceDeserializer deserializer = new ODataResourceDeserializer(_deserializerProvider); + + // Act + Product product = await deserializer.ReadAsync( + GetODataMessageReader(content, _edmModel), + typeof(Product), + _readContext) as Product; + + // Assert + Assert.Equal(0, product.ID); + Assert.Equal(4, product.Rating); + Assert.Equal(2.5m, product.Price); + Assert.Equal(product.PublishDate, new Date(1997, 7, 1)); + Assert.Null(product.ReleaseDate); + Assert.Null(product.DiscontinuedDate); + } + private static ODataMessageReader GetODataMessageReader(IODataRequestMessage oDataRequestMessage, IEdmModel edmModel) { return new ODataMessageReader(oDataRequestMessage, new ODataMessageReaderSettings(), edmModel);