diff --git a/src/Microsoft.OData.Core/Json/ODataJsonReader.cs b/src/Microsoft.OData.Core/Json/ODataJsonReader.cs index 8094c7f7c7..93a9992e61 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonReader.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonReader.cs @@ -416,6 +416,17 @@ protected override bool ReadAtNestedPropertyInfoImplementation() return this.ReadAtNestedPropertyInfoSynchronously(); } + /// + /// Asynchronous implementation of the reader logic when in state 'PropertyInfo'. + /// + /// A task that represents the asynchronous read operation. + /// The value of the TResult parameter contains true if more items can be read from the reader; otherwise false + /// + protected override Task ReadAtNestedPropertyInfoImplementationAsync() + { + return this.ReadAtNestedPropertyInfoAsynchronously(); + } + #endregion #region Stream @@ -1537,6 +1548,54 @@ private bool ReadAtNestedPropertyInfoSynchronously() return true; } + /// + /// Asynchronous implementation of the reader logic when in state 'PropertyInfo'. + /// + /// + /// A task that represents the asynchronous read operation. + /// The value of the TResult parameter contains true if more items can be read from the reader; otherwise false + /// + /// + /// Pre-Condition: JsonNodeType.Property: there are more properties after the nested resource info in the owning resource + /// Post-Condition: JsonNodeType.StartObject start of the expanded resource nested resource info to read next + /// JsonNodeType.StartArray start of the expanded resource set nested resource info to read next + /// JsonNoteType.Primitive (null) expanded null resource nested resource info to read next + /// JsonNoteType.Property property after deferred link or entity reference link + /// JsonNodeType.EndObject end of the parent resource + /// + private async Task ReadAtNestedPropertyInfoAsynchronously() + { + Debug.Assert(this.CurrentScope.State == ODataReaderState.NestedProperty, "Must be on 'NestedProperty' scope."); + JsonNestedPropertyInfoScope nestedPropertyInfoScope = (JsonNestedPropertyInfoScope)this.CurrentScope; + ODataJsonReaderNestedPropertyInfo nestedPropertyInfo = nestedPropertyInfoScope.ReaderNestedPropertyInfo; + Debug.Assert(nestedPropertyInfo != null); + + ODataPropertyInfo propertyInfo = this.CurrentScope.Item as ODataPropertyInfo; + Debug.Assert(propertyInfo != null, "Reading Nested Property Without an ODataPropertyInfo"); + + ODataStreamPropertyInfo streamPropertyInfo = propertyInfo as ODataStreamPropertyInfo; + if (streamPropertyInfo != null && !String.IsNullOrEmpty(streamPropertyInfo.ContentType)) + { + this.StartNestedStreamInfo(new ODataJsonReaderStreamInfo(streamPropertyInfo.PrimitiveTypeKind, streamPropertyInfo.ContentType)); + } + else if (!nestedPropertyInfo.WithValue) + { + // It's a nested non-stream property + this.PopScope(ODataReaderState.NestedProperty); + + // We are reading a next nested info. + return await this.ReadNextNestedInfoAsync() + .ConfigureAwait(false); + } + else + { + this.StartNestedStreamInfo( + new ODataJsonReaderStreamInfo(propertyInfo.PrimitiveTypeKind)); + } + + return true; + } + /// /// Implementation of the reader logic when in state 'Stream'. /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonReaderTests.cs index 07bc6e19ea..fb3e9549e4 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonReaderTests.cs @@ -1641,6 +1641,112 @@ await SetupJsonReaderAndRunTestAsync( Assert.Empty(verifyResourceActionStack); } + [Fact] + public async Task ReadResourceWithNestedPropertyInfoAsync() + { + var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Orders/$entity\"," + + "\"Id\":1," + + "\"Amount\":130," + + "\"ShippingAddress@odata.type\":\"#NS.Address\"}"; + + ODataResource orderResource = null; + ODataPropertyInfo shippingAddressPropertyInfo = null; + + await SetupJsonReaderAndRunTestAsync( + payload, + this.orderEntitySet, + this.orderEntityType, + (jsonReader) => DoReadAsync( + jsonReader, + verifyResourceAction: (resource) => + { + orderResource = resource; + }, + verifyNestedPropertyInfoAction: (nestedPropertyInfo) => + { + shippingAddressPropertyInfo = nestedPropertyInfo; + })); + + Assert.NotNull(orderResource); + Assert.Equal("NS.Order", orderResource.TypeName); + Assert.Equal(2, orderResource.Properties.Count()); + + var idProperty = orderResource.Properties.ElementAt(0); + var amountProperty = orderResource.Properties.ElementAt(1); + + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + Assert.Equal("Amount", amountProperty.Name); + Assert.Equal(130M, amountProperty.Value); + + Assert.NotNull(shippingAddressPropertyInfo); + Assert.Equal("ShippingAddress", shippingAddressPropertyInfo.Name); + } + + [Fact] + public async Task ReadResourceWithStreamPropertyContentTypeUnspecifiedAsync() + { + var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + + "\"Id\":1," + + "\"Name\":\"Sue\"," + + "\"Photo@odata.mediaEditLink\":\"http://tempuri.org/Customers(1)/Photo/Edit\"," + + "\"Photo@odata.mediaReadLink\":\"http://tempuri.org/Customers(1)/Photo\"," + + "\"Photo@odata.mediaEtag\":\"media-etag\"," + + "\"Photo\":\"AQIDBAUGBwgJAA==\"}"; + + ODataResource customerResource = null; + ODataPropertyInfo photoPropertyInfo = null; + byte[] photoBuffer = null; + int bytesRead = 0; + + await SetupJsonReaderAndRunTestAsync( + payload, + this.customerEntitySet, + this.customerEntityType, + (jsonReader) => DoReadAsync( + jsonReader, + verifyResourceAction: (resource) => + { + customerResource = resource; + }, + verifyNestedPropertyInfoAction: (nestedPropertyInfo) => + { + photoPropertyInfo = nestedPropertyInfo; + }, + verifyBinaryStreamAction: async (binaryStream) => + { + var maxLength = 10; + photoBuffer = new byte[maxLength]; + bytesRead = await binaryStream.ReadAsync(photoBuffer, 0, maxLength); + })); + + Assert.NotNull(customerResource); + Assert.Equal("NS.Customer", customerResource.TypeName); + Assert.Equal(3, customerResource.Properties.Count()); + + var idProperty = customerResource.Properties.ElementAt(0); + var nameProperty = customerResource.Properties.ElementAt(1); + var photoProperty = customerResource.Properties.ElementAt(2); + + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + Assert.Equal("Name", nameProperty.Name); + Assert.Equal("Sue", nameProperty.Value); + Assert.Equal("Photo", photoProperty.Name); + + Assert.Equal(10, bytesRead); + Assert.NotNull(photoBuffer); + Assert.Equal(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }, photoBuffer); + + Assert.NotNull(photoPropertyInfo); + var photoStreamInfoProperty = Assert.IsType(photoPropertyInfo); + Assert.Equal("Photo", photoStreamInfoProperty.Name); + Assert.Equal("http://tempuri.org/Customers(1)/Photo", photoStreamInfoProperty.ReadLink.OriginalString); + Assert.Equal("http://tempuri.org/Customers(1)/Photo/Edit", photoStreamInfoProperty.EditLink.OriginalString); + Assert.Null(photoStreamInfoProperty.ContentType); + Assert.Equal("media-etag", photoStreamInfoProperty.ETag); + } + [Fact] public async Task ReadNestedResourceSetAsync_ThrowsExceptionForInvalidItemsInResourceSet() { @@ -1714,7 +1820,7 @@ private async Task DoReadAsync( Action verifyDeletedResourceAction = null, Action verifyDeltaLinkAction = null, Action verifyEntityReferenceLinkAction = null, - Action verifyNestedPropertyInfoAction = null, + Action verifyNestedPropertyInfoAction = null, Action verifyBinaryStreamAction = null, Action verifyTextStreamAction = null, Action verifyPrimitiveAction = null) @@ -1823,9 +1929,12 @@ private async Task DoReadAsync( break; case ODataReaderState.NestedProperty: + var nestedPropertyInfo = jsonReader.Item as ODataPropertyInfo; + Assert.NotNull(nestedPropertyInfo); + if (verifyNestedPropertyInfoAction != null) { - verifyNestedPropertyInfoAction(jsonReader.Item); + verifyNestedPropertyInfoAction(nestedPropertyInfo); } break;