Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #3154: Enable serialize/deserialize navigation property with count without content #3155

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Preview

Copilot AI Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The count parameter should be set to false when calling WriteNavigationLinkMetadata for deferred nested resource info.

Suggested change
this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker, count: true);
this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker, count: false);

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
}

/// <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);
Copy link
Preview

Copilot AI Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The count parameter should be set to true when calling WriteNavigationLinkMetadata for nested resource info with content.

Suggested change
this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker, count: false);
this.jsonResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker, count: true);

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
}
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);
xuzhg marked this conversation as resolved.
Show resolved Hide resolved
}

/// <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);
xuzhg marked this conversation as resolved.
Show resolved Hide resolved
}
}
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
Loading