diff --git a/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs
index c6eaace36a..420268b3a3 100644
--- a/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs
+++ b/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs
@@ -1111,6 +1111,17 @@ in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(nest
nestedResourceInfo.TypeAnnotation = new ODataTypeAnnotation((string)propertyAnnotation.Value);
break;
+ case ODataAnnotationNames.ODataCount:
+ Debug.Assert(propertyAnnotation.Value is long && propertyAnnotation.Value != null, "The odata.count annotation should have been parsed as a non-null long.");
+ nestedResourceInfo.Count = (long?)propertyAnnotation.Value;
+ break;
+
+ // TODO: do we support odata.context uri here? why?
+ case ODataAnnotationNames.ODataContext:
+ Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.context annotation should have been parsed as a non-null Uri.");
+ nestedResourceInfo.ContextUrl = (Uri)propertyAnnotation.Value;
+ break;
+
default:
throw new ODataException(Error.Format(SRResources.ODataJsonResourceDeserializer_UnexpectedDeferredLinkPropertyAnnotation, nestedResourceInfo.Name, propertyAnnotation.Key));
}
diff --git a/src/Microsoft.OData.Core/Json/ODataJsonResourceSerializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonResourceSerializer.cs
index 50ac30406d..813d42487c 100644
--- a/src/Microsoft.OData.Core/Json/ODataJsonResourceSerializer.cs
+++ b/src/Microsoft.OData.Core/Json/ODataJsonResourceSerializer.cs
@@ -208,7 +208,7 @@ internal void WriteResourceEndMetadataProperties(IODataJsonWriterResourceState r
Debug.Assert(resource.MetadataBuilder != null, "resource.MetadataBuilder != null");
navigationLinkInfo.NestedResourceInfo.MetadataBuilder = resource.MetadataBuilder;
- this.WriteNavigationLinkMetadata(navigationLinkInfo.NestedResourceInfo, duplicatePropertyNameChecker);
+ this.WriteNavigationLinkMetadata(navigationLinkInfo.NestedResourceInfo, duplicatePropertyNameChecker, count: false);
navigationLinkInfo = resource.MetadataBuilder.GetNextUnprocessedNavigationLink();
}
@@ -240,7 +240,8 @@ internal void WriteResourceEndMetadataProperties(IODataJsonWriterResourceState r
///
/// The navigation link to write the metadata for.
/// The DuplicatePropertyNameChecker to use.
- internal void WriteNavigationLinkMetadata(ODataNestedResourceInfo nestedResourceInfo, IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
+ /// The boolean value indicating to write the count value if has.
+ internal void WriteNavigationLinkMetadata(ODataNestedResourceInfo nestedResourceInfo, IDuplicatePropertyNameChecker duplicatePropertyNameChecker, bool count = false)
{
Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null");
Debug.Assert(!string.IsNullOrEmpty(nestedResourceInfo.Name), "The nested resource info Name should have been validated by now.");
@@ -261,6 +262,12 @@ internal void WriteNavigationLinkMetadata(ODataNestedResourceInfo nestedResource
this.ODataAnnotationWriter.WritePropertyAnnotationName(navigationLinkName, ODataAnnotationNames.ODataNavigationLinkUrl);
this.JsonWriter.WriteValue(this.UriToString(navigationLinkUrl));
}
+
+ if (count && nestedResourceInfo.Count != null)
+ {
+ this.ODataAnnotationWriter.WritePropertyAnnotationName(navigationLinkName, ODataAnnotationNames.ODataCount);
+ this.JsonWriter.WriteValue(nestedResourceInfo.Count.Value);
+ }
}
///
@@ -567,8 +574,9 @@ await this.WriteOperationsAsync(functions.Cast(), /*isAction*/ f
///
/// The navigation link to write the metadata for.
/// The DuplicatePropertyNameChecker to use.
+ /// he boolean value indicating to write the count value if has.
/// A task that represents the asynchronous write operation.
- internal async Task WriteNavigationLinkMetadataAsync(ODataNestedResourceInfo nestedResourceInfo, IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
+ internal async Task WriteNavigationLinkMetadataAsync(ODataNestedResourceInfo nestedResourceInfo, IDuplicatePropertyNameChecker duplicatePropertyNameChecker, bool count = false)
{
Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null");
Debug.Assert(!string.IsNullOrEmpty(nestedResourceInfo.Name), "The nested resource info Name should have been validated by now.");
@@ -592,6 +600,12 @@ await this.ODataAnnotationWriter.WritePropertyAnnotationNameAsync(navigationLink
await this.JsonWriter.WriteValueAsync(this.UriToString(navigationLinkUrl))
.ConfigureAwait(false);
}
+
+ if (count && nestedResourceInfo.Count.HasValue)
+ {
+ await this.ODataAnnotationWriter.WritePropertyAnnotationNameAsync(navigationLinkName, ODataAnnotationNames.ODataCount).ConfigureAwait(false);
+ await this.JsonWriter.WriteValueAsync(nestedResourceInfo.Count.Value).ConfigureAwait(false);
+ }
}
///
diff --git a/src/Microsoft.OData.Core/Json/ODataJsonWriter.cs b/src/Microsoft.OData.Core/Json/ODataJsonWriter.cs
index 671ae24808..6498e05157 100644
--- a/src/Microsoft.OData.Core/Json/ODataJsonWriter.cs
+++ b/src/Microsoft.OData.Core/Json/ODataJsonWriter.cs
@@ -968,7 +968,7 @@ protected override void WriteDeferredNestedResourceInfo(ODataNestedResourceInfo
Debug.Assert(this.writingResponse, "Deferred links are only supported in response, we should have verified this already.");
// A deferred nested resource info is just the link metadata, no value.
- this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker);
+ this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker, count: true);
}
///
@@ -999,7 +999,7 @@ protected override void StartNestedResourceInfoWithContent(ODataNestedResourceIn
}
// Write the nested resource info metadata first. The rest is written by the content resource or resource set.
- this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker);
+ this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker, count: false);
}
else
{
@@ -2038,7 +2038,7 @@ protected override Task WriteDeferredNestedResourceInfoAsync(ODataNestedResource
// A deferred nested resource info is just the link metadata, no value.
return this.jsonResourceSerializer.WriteNavigationLinkMetadataAsync(
nestedResourceInfo,
- this.DuplicatePropertyNameChecker);
+ this.DuplicatePropertyNameChecker, count: true);
}
///
@@ -2078,7 +2078,7 @@ await this.jsonResourceSerializer.WriteNestedResourceInfoContextUrlAsync(innerNe
// Write the nested resource info metadata first. The rest is written by the content resource or resource set.
await this.jsonResourceSerializer.WriteNavigationLinkMetadataAsync(
innerNestedResourceInfo,
- this.DuplicatePropertyNameChecker).ConfigureAwait(false);
+ this.DuplicatePropertyNameChecker, count: false).ConfigureAwait(false);
}
}
else
diff --git a/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs b/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs
index 29451a8266..2d1247e5a2 100644
--- a/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs
+++ b/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs
@@ -49,6 +49,17 @@ public string Name
set;
}
+ /// Gets or sets the number of items for this nested resource info.
+ /// Be noted, this count property is for nested resource info without content.
+ /// For nested resource info with content, please specify the count on ODataResourceSetBase.Count.
+ ///
+ /// The number of items in the resource set.
+ public long? Count
+ {
+ get;
+ set;
+ }
+
/// Gets or sets the URI representing the Unified Resource Locator (URL) of the link.
/// The URI representing the Unified Resource Locator (URL) of the link.
public Uri Url
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonEntryAndFeedSerializerTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonEntryAndFeedSerializerTests.cs
index 45777b0d96..2be751e3cb 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonEntryAndFeedSerializerTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonEntryAndFeedSerializerTests.cs
@@ -51,6 +51,21 @@ public void SerializedNavigationPropertyShouldIncludeNavigationLinkUrl()
Assert.Contains("NavigationProperty@odata.navigationLink\":\"http://example.com/navigation", jsonResult);
}
+ [Fact]
+ public void SerializedNavigationPropertyShouldIncludeCountIfApply()
+ {
+ var jsonResult = this.SerializeJsonFragment(serializer =>
+ serializer.WriteNavigationLinkMetadata(
+ new ODataNestedResourceInfo
+ {
+ Name = "NavigationProperty",
+ Count = 42
+ },
+ new DuplicatePropertyNameChecker(), true));
+
+ Assert.Contains("NavigationProperty@odata.count\":42", jsonResult);
+ }
+
[Fact]
public void WriteOperationsOnRequestsShouldThrow()
{
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceSerializerTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceSerializerTests.cs
index 5a546d1476..0a52530c12 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceSerializerTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceSerializerTests.cs
@@ -311,6 +311,27 @@ public async Task WriteNavigationLinkMetadataAsync_WritesNavigationLinkMetadata(
"\"BestSeller@odata.navigationLink\":\"http://tempuri.org/Categories(1)/BestSeller\"", result);
}
+ [Fact]
+ public async Task WriteNavigationLinkMetadataAsync_WritesCountMetadata()
+ {
+ var nestedResourceInfo = new ODataNestedResourceInfo
+ {
+ Name = "BestSeller",
+ IsCollection = true,
+ Count = 42
+ };
+
+ var result = await SetupJsonResourceSerializerAndRunTestAsync(
+ (jsonResourceSerializer) =>
+ {
+ return jsonResourceSerializer.WriteNavigationLinkMetadataAsync(
+ nestedResourceInfo,
+ new NullDuplicatePropertyNameChecker(), true);
+ });
+
+ Assert.Equal("{\"BestSeller@odata.count\":42", result);
+ }
+
[Fact]
public async Task WriteNestedResourceInfoContextUrlAsync_WritesNestedResourceInfoContextUrl()
{
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Writer/Json/FullPayloadValidateTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Writer/Json/FullPayloadValidateTests.cs
index 8979616178..64b6ff16a2 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Writer/Json/FullPayloadValidateTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Writer/Json/FullPayloadValidateTests.cs
@@ -842,6 +842,33 @@ public void WritingNestedInlinecountTest()
"\"ContainedCollectionNavProp@odata.navigationLink\":\"http://example.org/odata.svc/navigation\"," +
"\"ContainedCollectionNavProp@odata.count\":1," +
"\"ContainedCollectionNavProp\":[]" +
+ "}" +
+ "]" +
+ "}";
+ Assert.Equal(expectedPayload, result);
+ }
+
+ [Fact]
+ public void WritingNestedInlinecountWithoutContentTest()
+ {
+ this.containedCollectionNavLink.Count = 42;
+ ODataItem[] itemsToWrite = new ODataItem[]
+ {
+ new ODataResourceSet(),
+ this.entryWithOnlyData1,
+ this.containedCollectionNavLink
+ };
+
+ string resourcePath = "EntitySet";
+ string result = this.GetWriterOutputForContentTypeAndKnobValue("application/json;odata.metadata=minimal", true, itemsToWrite, Model, EntitySet, EntityType, null, null, resourcePath);
+
+ string expectedPayload = "{" +
+ "\"@odata.context\":\"http://example.org/odata.svc/$metadata#EntitySet\"," +
+ "\"value\":[" +
+ "{" +
+ "\"ID\":101,\"Name\":\"Alice\"," +
+ "\"ContainedCollectionNavProp@odata.navigationLink\":\"http://example.org/odata.svc/navigation\"," +
+ "\"ContainedCollectionNavProp@odata.count\":42" +
"}" +
"]" +
"}";
@@ -922,6 +949,50 @@ public void ReadingNestedInlinecountTest()
ODataResourceSet topFeed = feedList[1];
Assert.Null(topFeed.Count);
}
+
+ [Fact]
+ public void ReadingNestedInlinecountWithoutContentTest()
+ {
+ string payload = "{" +
+ "\"@odata.context\":\"http://example.org/odata.svc/$metadata#EntitySet\"," +
+ "\"value\":[" +
+ "{" +
+ "\"ID\":101,\"Name\":\"Alice\"," +
+ "\"ContainedCollectionNavProp@odata.context\":\"http://example.org/odata.svc/$metadata#EntitySet(101)/ContainedCollectionNavProp\"," +
+ "\"ContainedCollectionNavProp@odata.navigationLink\":\"http://example.org/odata.svc/navigation\"," +
+ "\"ContainedCollectionNavProp@odata.count\":51" +
+ "}" +
+ "]" +
+ "}";
+ InMemoryMessage message = new InMemoryMessage();
+ message.SetHeader("Content-Type", "application/json;odata.metadata=minimal");
+ message.Stream = new MemoryStream(Encoding.UTF8.GetBytes(payload));
+ List feedList = new List();
+
+ ODataNestedResourceInfo nestedResourceInfo = null;
+ using (var messageReader = new ODataMessageReader((IODataResponseMessage)message, null, Model))
+ {
+ var reader = messageReader.CreateODataResourceSetReader();
+ while (reader.Read())
+ {
+ switch (reader.State)
+ {
+ case ODataReaderState.ResourceSetEnd:
+ feedList.Add(reader.Item as ODataResourceSet);
+ break;
+
+ case ODataReaderState.NestedResourceInfoStart:
+ nestedResourceInfo = reader.Item as ODataNestedResourceInfo;
+ break;
+
+ }
+ }
+ }
+
+ Assert.Single(feedList); // only contains the toplevel
+ Assert.NotNull(nestedResourceInfo);
+ Assert.Equal(51, nestedResourceInfo.Count);
+ }
#endregion Inlinecount Tests
[Fact]