Skip to content

Commit

Permalink
Fixes #3154: Enable serialize/deserialize navigation property with co…
Browse files Browse the repository at this point in the history
…unt without content
  • Loading branch information
xuzhg committed Dec 19, 2024
1 parent 70eef8a commit bb6fe4f
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 7 deletions.
11 changes: 11 additions & 0 deletions src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
20 changes: 17 additions & 3 deletions src/Microsoft.OData.Core/Json/ODataJsonResourceSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -240,7 +240,8 @@ internal void WriteResourceEndMetadataProperties(IODataJsonWriterResourceState r
/// </summary>
/// <param name="nestedResourceInfo">The navigation link to write the metadata for.</param>
/// <param name="duplicatePropertyNameChecker">The DuplicatePropertyNameChecker to use.</param>
internal void WriteNavigationLinkMetadata(ODataNestedResourceInfo nestedResourceInfo, IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
/// <param name="count">The boolean value indicating to write the count value if has.</param>
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.");
Expand All @@ -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);
}
}

/// <summary>
Expand Down Expand Up @@ -567,8 +574,9 @@ await this.WriteOperationsAsync(functions.Cast<ODataOperation>(), /*isAction*/ f
/// </summary>
/// <param name="nestedResourceInfo">The navigation link to write the metadata for.</param>
/// <param name="duplicatePropertyNameChecker">The DuplicatePropertyNameChecker to use.</param>
/// <param name="count">he boolean value indicating to write the count value if has.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
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.");
Expand All @@ -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);
}
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions src/Microsoft.OData.Core/Json/ODataJsonWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
}

/// <summary>
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.OData.Core/ODataNestedResourceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ public string Name
set;
}

/// <summary>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.
/// </summary>
/// <returns>The number of items in the resource set.</returns>
public long? Count
{
get;
set;
}

/// <summary>Gets or sets the URI representing the Unified Resource Locator (URL) of the link.</summary>
/// <returns>The URI representing the Unified Resource Locator (URL) of the link.</returns>
public Uri Url
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ public void SerializedNavigationPropertyShouldIncludeNavigationLinkUrl()
Assert.Contains("[email protected]\":\"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("[email protected]\":42", jsonResult);
}

[Fact]
public void WriteOperationsOnRequestsShouldThrow()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,27 @@ public async Task WriteNavigationLinkMetadataAsync_WritesNavigationLinkMetadata(
"\"[email protected]\":\"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("{\"[email protected]\":42", result);
}

[Fact]
public async Task WriteNestedResourceInfoContextUrlAsync_WritesNestedResourceInfoContextUrl()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,33 @@ public void WritingNestedInlinecountTest()
"\"[email protected]\":\"http://example.org/odata.svc/navigation\"," +
"\"[email protected]\":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\"," +
"\"[email protected]\":\"http://example.org/odata.svc/navigation\"," +
"\"[email protected]\":42" +
"}" +
"]" +
"}";
Expand Down Expand Up @@ -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\"," +
"\"[email protected]\":\"http://example.org/odata.svc/$metadata#EntitySet(101)/ContainedCollectionNavProp\"," +
"\"[email protected]\":\"http://example.org/odata.svc/navigation\"," +
"\"[email protected]\":51" +
"}" +
"]" +
"}";
InMemoryMessage message = new InMemoryMessage();
message.SetHeader("Content-Type", "application/json;odata.metadata=minimal");
message.Stream = new MemoryStream(Encoding.UTF8.GetBytes(payload));
List<ODataResourceSet> feedList = new List<ODataResourceSet>();

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]
Expand Down

0 comments on commit bb6fe4f

Please sign in to comment.