Skip to content

Writing top-level property with ODataJsonElementValue fails with exception stream value not supported #3162

Open
@habbes

Description

@habbes

Short summary (3-5 sentences) describing the issue.

ODataMessageWriter supports writing a top-level property (one that is not embedded in a resource). One use case for this is to write the result of an action or function call that returns a primitive value.

As demonstrated in the code snippet below, when you write a top-level property with a primitive value using ODataMessageWriter, you usually get an object with a field called value containing the serialized value (the property's original value is ignored) and an odata.context field.

var messageWriter = new ODataMessageWriter(responseMessage, messageWriterSettings);
await messageWriter.WritePropertyAsync(new ODataProperty
{
    Name = "propName",
    Value = true
});

Result:

{"@odata.context":"https://tempuri.org/$metadata#Edm.Boolean","value":true }

However, if the value is inside a JsonElement, and you use ODataJsonElementValue to store in an OData property a so:

var messageWriter = new ODataMessageWriter(asyncResponseMessage, this.messageWriterSettings);
var jsonValue = JsonDocument.Parse("true").RootElement;
await messageWriter.WritePropertyAsync(new ODataProperty
{
    Name = "Id",
    Value = new ODataJsonElementValue(jsonValue)
});

You will get the following exception

Microsoft.OData.ODataException: 'The stream value must be a property of an ODataResource instance.'
   at Microsoft.OData.ODataContextUrlInfo.GetTypeNameForValue(ODataValue value, IEdmModel model) in C:\odata.net\src\Microsoft.OData.Core\ODataContextUrlInfo.cs:line 458
   at Microsoft.OData.ODataContextUrlInfo.Create(ODataValue value, ODataVersion version, ODataUri odataUri, IEdmModel model) in C:\odata.net\src\Microsoft.OData.Core\ODataContextUrlInfo.cs:line 81
   at Microsoft.OData.Json.ODataJsonPropertySerializer.GetContextUrlInfo(ODataProperty property) in C:\odata.net\src\Microsoft.OData.Core\Json\ODataJsonPropertySerializer.cs:line 884
   at Microsoft.OData.Json.ODataJsonPropertySerializer.<>c.<<WriteTopLevelPropertyAsync>b__9_0>d.MoveNext() in C:\odata.net\src\Microsoft.OData.Core\Json\ODataJsonPropertySerializer.cs:line 286
   at Microsoft.OData.Json.ODataJsonSerializer.<WriteTopLevelPayloadAsync>d__30`2.MoveNext() in C:\odata.net\src\Microsoft.OData.Core\Json\ODataJsonSerializer.cs:line 588
   at Microsoft.OData.Json.ODataJsonOutputContext.<WritePropertyImplementationAsync>d__69.MoveNext() in C:\odata.net\src\Microsoft.OData.Core\Json\ODataJsonOutputContext.cs:line 1084
   at Microsoft.OData.Json.ODataJsonOutputContext.<WritePropertyAsync>d__35.MoveNext() in C:\odata.net\src\Microsoft.OData.Core\Json\ODataJsonOutputContext.cs:line 461
   at Microsoft.OData.ODataMessageWriter.<WriteToOutputAsync>d__101.MoveNext() in C:\odata.net\src\Microsoft.OData.Core\ODataMessageWriter.cs:line 1292

Assemblies affected

Microsoft.OData.Core 8.x

Reproduce steps

call ODataMessageWriter.WritePropertyAsync with an ODataProperty whose Value is an ODataJsonElementValue.

Expected result

If the JsonElement is a primitive value, then it should be handled the same way as writing the corresponding ODataPrimitiveValue. If it's an array or object, then it's a bit tricky, maybe it could be treated as a resource or untyped?

Actual result

Throws an exception (see summary above).

Additional detail

This exception occurs because OData Writer is trying to generate a context URL for the value but it doesn't know the type. Specifically, it gets thrown in ODataContextUrlInfo.GetTypeNameForValue, this method handles the different child types of ODataValue, but doesn't handle ODataJsonElementValue. We should add a case to handle ODataJsonElementValue.

ODataJsonElementValue is a niche feature added in 2023 to provide an optimization for scenario where the payload input is already JSON document parsed by System Text Json (JsonElement). If the server already knows that the contents of the parsed JsonElement is valid OData, then it could save a lot of memory and CPU cycles to pass it down as-is to the underlying ODataUtf8JsonWriter without breaking it down. The use case for this would be some gateway that proxies responses from an OData service.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions