From 25d55773ab9632d5c2f1ee9f6be3dbeb15bc170d Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Fri, 23 Aug 2024 20:38:08 -0700 Subject: [PATCH] Fixes #1301: Enable read the property without value --- .../ODataResourceDeserializer.cs | 18 ++- .../Wrapper/ODataReaderExtensions.cs | 6 + .../Formatter/Wrapper/ODataResourceWrapper.cs | 8 ++ .../Microsoft.AspNetCore.OData.xml | 18 ++- .../PublicAPI.Shipped.txt | 2 + .../ODataResourceDeserializerTests.cs | 17 +++ .../Wrapper/ODataReaderExtensionsTests.cs | 125 +++++++++++++++++- .../Wrapper/ODataResourceWrapperTests.cs | 54 ++++++++ ...rosoft.AspNetCore.OData.PublicApi.Net8.bsl | 2 + 9 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataResourceWrapperTests.cs diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs index 4fdc92d2b..c0e6ece3f 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs @@ -474,6 +474,21 @@ public virtual void ApplyNestedProperty(object resource, ODataNestedResourceInfo } } + /// + /// Deserializes the nested property info from into . + /// Nested property info contains annotations for the property but without the property value. + /// + /// The object into which the properties should be read. + /// The resource object containing the structural properties. + /// The type of the resource. + /// The deserializer context. + public virtual void ApplyNestedPropertyInfos(object resource, ODataResourceWrapper resourceWrapper, + IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + // Add this method to provide customers an solution to customize the deserializer to handle the properties without value. + // We Will fill this method later when we enable the instance annotation feature. + } + /// /// Deserializes the structural properties from into . /// @@ -489,7 +504,7 @@ public virtual void ApplyStructuralProperties(object resource, ODataResourceWrap throw new ArgumentNullException(nameof(resourceWrapper)); } - foreach (ODataProperty property in resourceWrapper.Resource.Properties) + foreach (ODataProperty property in resourceWrapper.Resource.Properties.OfType()) { ApplyStructuralProperty(resource, property, structuredType, readContext); } @@ -532,6 +547,7 @@ private void ApplyResourceProperties(object resource, ODataResourceWrapper resou IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) { ApplyStructuralProperties(resource, resourceWrapper, structuredType, readContext); + ApplyNestedPropertyInfos(resource, resourceWrapper, structuredType, readContext); ApplyNestedProperties(resource, resourceWrapper, structuredType, readContext); } diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs index c0fc09238..f71e2116d 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs @@ -200,6 +200,12 @@ private static void ReadODataItem(ODataReader reader, Stack it resourceSetParentWrapper.Items.Add(new ODataPrimitiveWrapper((ODataPrimitiveValue)reader.Item)); break; + case ODataReaderState.NestedProperty: + Contract.Assert(itemsStack.Count > 0, "The nested property info should be a non-null primitive value within resource wrapper."); + ODataResourceWrapper resourceParentWrapper = (ODataResourceWrapper)itemsStack.Peek(); + resourceParentWrapper.NestedPropertyInfos.Add((ODataPropertyInfo)reader.Item); + break; + default: Contract.Assert(false, "We should never get here, it means the ODataReader reported a wrong state."); break; diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataResourceWrapper.cs b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataResourceWrapper.cs index a0dc68b37..36f39e887 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataResourceWrapper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataResourceWrapper.cs @@ -26,6 +26,8 @@ public ODataResourceWrapper(ODataResourceBase resource) IsDeletedResource = resource != null && resource is ODataDeletedResource; NestedResourceInfos = new List(); + + NestedPropertyInfos = new List(); } /// @@ -42,5 +44,11 @@ public ODataResourceWrapper(ODataResourceBase resource) /// Gets the inner nested resource infos. /// public IList NestedResourceInfos { get; } + + /// + /// Gets the nested property infos. + /// The nested property info is a property without value but could have instance annotations. + /// + public IList NestedPropertyInfos { get; } } } diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml index 24b0986a1..1d2c48fb2 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml @@ -3606,6 +3606,16 @@ The type of the resource. The deserializer context. + + + Deserializes the nested property info from into . + Nested property info contains annotations for the property but without the property value. + + The object into which the properties should be read. + The resource object containing the structural properties. + The type of the resource. + The deserializer context. + Deserializes the structural properties from into . @@ -6331,6 +6341,12 @@ Gets the inner nested resource infos. + + + Gets the nested property infos. + The nested property info is a property without value but could have instance annotations. + + Provides extension methods for to add OData routes. @@ -8668,7 +8684,7 @@ - Get the ODaya query context. + Get the OData query context. The response value. The content as SingleResult.Queryable. diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Shipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Shipped.txt index 1874ae4d2..bdeae4932 100644 --- a/src/Microsoft.AspNetCore.OData/PublicAPI.Shipped.txt +++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Shipped.txt @@ -656,6 +656,7 @@ Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceSetWrapper.ResourceSet Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper.IsDeletedResource.get -> bool Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper.NestedResourceInfos.get -> System.Collections.Generic.IList +Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper.NestedPropertyInfos.get -> System.Collections.Generic.IList Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper.ODataResourceWrapper(Microsoft.OData.ODataResourceBase resource) -> void Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper.Resource.get -> Microsoft.OData.ODataResourceBase Microsoft.AspNetCore.OData.ODataApplicationBuilderExtensions @@ -1817,6 +1818,7 @@ virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataPrimitiveDeser virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyDeletedResource(object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) -> void virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyNestedProperties(object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) -> void virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyNestedProperty(object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataNestedResourceInfoWrapper resourceInfoWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) -> void +virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyNestedPropertyInfos(object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) -> void virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyStructuralProperties(object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) -> void virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyStructuralProperty(object resource, Microsoft.OData.ODataProperty structuralProperty, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) -> void virtual Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceDeserializer.CreateResourceInstance(Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) -> object diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs index 00fbe5d0d..7e76115a7 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs @@ -418,6 +418,23 @@ public void ReadResource_Calls_ApplyStructuralProperties() deserializer.Verify(); } + [Fact] + public void ReadResource_Calls_ApplyNestedPropertyInfos() + { + // Arrange + Mock deserializer = new Mock(_deserializerProvider); + ODataResourceWrapper resourceWrapper = new ODataResourceWrapper(new ODataResource { Properties = Enumerable.Empty() }); + deserializer.CallBase = true; + deserializer.Setup(d => d.CreateResourceInstance(_productEdmType, _readContext)).Returns(42); + deserializer.Setup(d => d.ApplyNestedPropertyInfos(42, resourceWrapper, _productEdmType, _readContext)).Verifiable(); + + // Act + deserializer.Object.ReadResource(resourceWrapper, _productEdmType, _readContext); + + // Assert + deserializer.Verify(); + } + [Fact] public void ReadResource_Calls_ApplyNestedProperties() { diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataReaderExtensionsTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataReaderExtensionsTests.cs index 492093e7b..743ad7ca0 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataReaderExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataReaderExtensionsTests.cs @@ -26,6 +26,13 @@ static ODataReaderExtensionsTests() { Model = new EdmModel(); + // Enum type simpleEnum + EdmEnumType colorEnum = new EdmEnumType("NS", "Color"); + colorEnum.AddMember(new EdmEnumMember(colorEnum, "Red", new EdmEnumMemberValue(0))); + colorEnum.AddMember(new EdmEnumMember(colorEnum, "Blue", new EdmEnumMemberValue(1))); + colorEnum.AddMember(new EdmEnumMember(colorEnum, "Yellow", new EdmEnumMemberValue(2))); + Model.AddElement(colorEnum); + // Address EdmComplexType address = new EdmComplexType("NS", "Address"); address.AddStructuralProperty("Street", EdmCoreModel.Instance.GetString(false)); @@ -36,6 +43,7 @@ static ODataReaderExtensionsTests() Model.AddElement(customer); customer.AddKeys(customer.AddStructuralProperty("CustomerID", EdmCoreModel.Instance.GetInt32(false))); customer.AddStructuralProperty("Name", EdmCoreModel.Instance.GetString(true)); + customer.AddStructuralProperty("Color", new EdmEnumTypeReference(colorEnum, true)); customer.AddStructuralProperty("Location", new EdmComplexTypeReference(address, false)); // Order @@ -116,6 +124,115 @@ public async Task ReadResourceWorksAsExpected() Assert.Equal(new[] { "Location", "Order", "Orders" }, resource.NestedResourceInfos.Select(n => n.NestedResourceInfo.Name)); } + [Fact] + public async Task ReadResourceWithPropertyWithoutValueButWithInstanceAnnotationsWorksAsExpected() + { + // Arrange + // Property 'Name' without value but with instance annotations + const string payload = + "{" + + "\"@odata.context\":\"http://localhost/$metadata#Customers/$entity\"," + + "\"CustomerID\": 17," + + "\"Name@Custom.PrimitiveAnnotation\":123," + + "\"Name@Custom.BooleanAnnotation\":true," + + "\"Location\": { \"Street\":\"154TH AVE\"}" + + "}"; + + IEdmEntitySet customers = Model.EntityContainer.FindEntitySet("Customers"); + Assert.NotNull(customers); // Guard + + // Act + Func> func = mr => mr.CreateODataResourceReaderAsync(customers, customers.EntityType); + ODataItemWrapper item = await ReadPayloadAsync(payload, Model, func, ODataVersion.V4, false, "*"); + + // Assert + Assert.NotNull(item); + ODataResourceWrapper resource = Assert.IsType(item); + Assert.NotNull(resource.Resource); + + // Be noted, the ODL 8 inserts the primitive properties without value into the 'Properties' also. + // So, the Resource.Properties contains "CustomerID" and "Name". + Assert.Equal(2, resource.Resource.Properties.Count()); + + ODataPropertyInfo customerIdPropInfo = resource.Resource.Properties.First(c => c.Name == "CustomerID"); + ODataProperty customerIdProp = Assert.IsType(customerIdPropInfo); + Assert.Equal("CustomerID", customerIdProp.Name); + Assert.Equal(17, customerIdProp.Value); + + ODataPropertyInfo namePropInfo = resource.Resource.Properties.First(c => c.Name == "Name"); + Assert.IsNotType(namePropInfo); + Assert.Equal(2, namePropInfo.InstanceAnnotations.Count); + + ODataPropertyInfo nameProp = Assert.Single(resource.NestedPropertyInfos); + Assert.Equal("Name", nameProp.Name); + Assert.Equal(2, nameProp.InstanceAnnotations.Count); + + ODataInstanceAnnotation primitiveAnnotation = nameProp.InstanceAnnotations.First(i => i.Name == "Custom.PrimitiveAnnotation"); + ODataPrimitiveValue primitiveValue = Assert.IsType(primitiveAnnotation.Value); + Assert.Equal(123, primitiveValue.Value); + + ODataInstanceAnnotation booleanAnnotation = nameProp.InstanceAnnotations.First(i => i.Name == "Custom.BooleanAnnotation"); + ODataPrimitiveValue booleanValue = Assert.IsType(booleanAnnotation.Value); + Assert.True((bool)booleanValue.Value); + + ODataNestedResourceInfoWrapper nestedInfoWrapper = Assert.Single(resource.NestedResourceInfos); + Assert.Equal("Location", nestedInfoWrapper.NestedResourceInfo.Name); + } + + [Fact] + public async Task ReadResourceWithNonPrimitivePropertyWithoutValueButWithInstanceAnnotationsWorksAsExpected() + { + // Arrange + const string payload = + "{" + + "\"@odata.context\":\"http://localhost/$metadata#Customers/$entity\"," + + "\"Color@Custom.PrimitiveAnnotation\":42," + + "\"Name@Custom.PrimitiveAnnotation\":123," + // Put 'Name' after 'Color' property is by design + "\"Location@Custom.PrimitiveAnnotation\":9" + + "}"; + + IEdmEntitySet customers = Model.EntityContainer.FindEntitySet("Customers"); + Assert.NotNull(customers); // Guard + + // Act + Func> func = mr => mr.CreateODataResourceReaderAsync(customers, customers.EntityType); + ODataItemWrapper item = await ReadPayloadAsync(payload, Model, func, ODataVersion.V4, false, "*"); + + // Assert + Assert.NotNull(item); + ODataResourceWrapper resource = Assert.IsType(item); + Assert.NotNull(resource.Resource); + + ODataPropertyInfo namePropInfo = Assert.Single(resource.Resource.Properties); + Assert.IsNotType(namePropInfo); + Assert.Single(namePropInfo.InstanceAnnotations); + + Assert.Equal(3, resource.NestedPropertyInfos.Count); + + // -- Color + ODataPropertyInfo colorProp = resource.NestedPropertyInfos.Single(c => c.Name == "Color"); + ODataInstanceAnnotation colorAnnotation = Assert.Single(colorProp.InstanceAnnotations); + Assert.Equal("Custom.PrimitiveAnnotation", colorAnnotation.Name); + ODataPrimitiveValue primitiveValue = Assert.IsType(colorAnnotation.Value); + Assert.Equal(42, primitiveValue.Value); + + // -- Name + ODataPropertyInfo nameProp = resource.NestedPropertyInfos.Single(c => c.Name == "Name"); + ODataInstanceAnnotation primitiveAnnotation = Assert.Single(nameProp.InstanceAnnotations); + Assert.Equal("Custom.PrimitiveAnnotation", primitiveAnnotation.Name); + primitiveValue = Assert.IsType(primitiveAnnotation.Value); + Assert.Equal(123, primitiveValue.Value); + + // -- Location + ODataPropertyInfo locationProp = resource.NestedPropertyInfos.Single(c => c.Name == "Location"); + ODataInstanceAnnotation locationAnnotation = Assert.Single(locationProp.InstanceAnnotations); + Assert.Equal("Custom.PrimitiveAnnotation", locationAnnotation.Name); + primitiveValue = Assert.IsType(locationAnnotation.Value); + Assert.Equal(9, primitiveValue.Value); + + Assert.Empty(resource.NestedResourceInfos); + } + [Fact] public async Task ReadResourceSetWorksAsExpected() { @@ -575,7 +692,8 @@ public async Task ReadEntityReferenceLinksSetWorksAsExpected_V401() private async Task ReadPayloadAsync(string payload, IEdmModel edmModel, Func> createReader, ODataVersion version = ODataVersion.V4, - bool readUntypedAsString = false) + bool readUntypedAsString = false, + string annotationFilter = null) { var message = new InMemoryMessage() { @@ -591,6 +709,11 @@ private async Task ReadPayloadAsync(string payload, Version = version, }; + if (annotationFilter != null) + { + readerSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter(annotationFilter); + } + using (var msgReader = new ODataMessageReader((IODataRequestMessageAsync)message, readerSettings, edmModel)) { ODataReader reader = await createReader(msgReader); diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataResourceWrapperTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataResourceWrapperTests.cs new file mode 100644 index 000000000..d6ad38778 --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Wrapper/ODataResourceWrapperTests.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using Microsoft.AspNetCore.OData.Formatter.Wrapper; +using Microsoft.OData; +using Xunit; + +namespace Microsoft.AspNetCore.OData.Tests.Formatter.Wrapper +{ + public class ODataResourceWrapperTests + { + [Fact] + public void ODataResourceWrapper_SetsUsingODataResource_CorrectProperties() + { + // Arrange & Act & Assert + ODataResource resource = new ODataResource + { + TypeName = "NS.Namespace" + }; + + // Act + ODataResourceWrapper resourceWrapper = new ODataResourceWrapper(resource); + + // Assert + Assert.Same(resource, resourceWrapper.Resource); + Assert.False(resourceWrapper.IsDeletedResource); + Assert.Empty(resourceWrapper.NestedPropertyInfos); + Assert.Empty(resourceWrapper.NestedResourceInfos); + } + + [Fact] + public void ODataResourceWrapper_SetsUsingODataDeletedResource_CorrectProperties() + { + // Arrange & Act & Assert + ODataDeletedResource deletedResource = new ODataDeletedResource + { + TypeName = "NS.Namespace" + }; + + // Act + ODataResourceWrapper resourceWrapper = new ODataResourceWrapper(deletedResource); + + // Assert + Assert.Same(deletedResource, resourceWrapper.Resource); + Assert.True(resourceWrapper.IsDeletedResource); + Assert.Empty(resourceWrapper.NestedPropertyInfos); + Assert.Empty(resourceWrapper.NestedResourceInfos); + } + } +} diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net8.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net8.bsl index 0cf9bca97..e4e9b3438 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net8.bsl +++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net8.bsl @@ -2018,6 +2018,7 @@ public class Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataResourceD public virtual void ApplyDeletedResource (object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) public virtual void ApplyNestedProperties (object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) public virtual void ApplyNestedProperty (object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataNestedResourceInfoWrapper resourceInfoWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) + public virtual void ApplyNestedPropertyInfos (object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) public virtual void ApplyStructuralProperties (object resource, Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) public virtual void ApplyStructuralProperty (object resource, Microsoft.OData.ODataProperty structuralProperty, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) public virtual object CreateResourceInstance (Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, Microsoft.AspNetCore.OData.Formatter.Deserialization.ODataDeserializerContext readContext) @@ -2716,6 +2717,7 @@ public sealed class Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataResourceWr public ODataResourceWrapper (Microsoft.OData.ODataResourceBase resource) bool IsDeletedResource { public get; } + System.Collections.Generic.IList`1[[Microsoft.OData.ODataPropertyInfo]] NestedPropertyInfos { public get; } System.Collections.Generic.IList`1[[Microsoft.AspNetCore.OData.Formatter.Wrapper.ODataNestedResourceInfoWrapper]] NestedResourceInfos { public get; } Microsoft.OData.ODataResourceBase Resource { public get; } }