Resource values
Applies To: # OData core lib v7 supported OData Core Lib V7
ODataComplexValue
is widely used in OData libraries v5.x and v6.x. However, it’s removed in OData library v7.x because complex type should support the navigation property. We should treat the complex same as the entity.
So, the main changes OData v7.x design are:
- Remove ODataComplexValue
- Rename ODataEntry as ODataResource, use that to represent the instance of entity and complex.
- Rename ODataFeed as ODataResourceSet, use that to represent the instance of collection of entity or complex.
Along with more and more customers upgrade from ODL v6.x to ODL v7.x, many customers find it’s hard to use the library without the ODataComplexValue
.
Because most of OData customers:
- Don’t need navigation property on complex type.
- Can’t convert the instance of entity or complex easily from literal or to literal. Like the Json.Net
- Can’t create the instance annotation with “complex or entity, or collection of them”
- Can’t write or read the top-level resource
- Can’t write or read the top-level property with resource or collection of resource value
- Can’t read the parameter resource or collection of resource value directly
Below is the main inheritance of the ODataValue vs ODataItem in ODL v6.x.
Below is the main inheritance of the ODataValue vs ODataItem in ODL v7.x.
The main changes from 6.x to 7.x is:
ODataComplexValue
is removed.ODataValue
is derived fromODataItem
.
We should introduce a new class named ODataResourceValue
derived from ODataValue
, same as:
We will introduce a new class named ODataResourceValue
, it should look like (Same as ODataComplexValue
):
public sealed class ODataResourceValue : ODataValue
{
public string TypeName { get; set; }
public IEnumerable<ODataProperty> Properties { get; set; }
public ICollection<ODataInstanceAnnotation> InstanceAnnotations { get;set; }
}
Where:
- TypeName: save the resource type name.
- Properties: save the all properties, include the property with resource value or collection of resource value.
- InstanceAnnotations: save the instance annotations for this resource value.
We don’t need to change anything for ODataCollectionValue
, because it also supports to have ODataResourceValue
as its element.
We don’t need to change anything for ODataProperty
, because it also supports to create ODataProperty
with ODataResourceValue
or ODataCollectionValue
.
We don’t need to change anything in ODataResource
except to verify the properties don’t include any property whose value is an ODataResourceValue
or Collection of ODataResourceValue.
public class ODataResource
{
...
public IEnumerable<ODataProperty> Properties
{
get { return this.MetadataBuilder.GetProperties(this.properties); }
set
{
// TODO: Add validation here. It's not allowed a property with value as "ODataResourceValue" or a collection of "ODataResourceValue"
this.properties = value;
}
}
}
We don’t need to change anything in ODataResourceSet
.
We should add a new method named WriteResourceValue(…)
to write an ODataResourceValue
in ODataJsonLightValueSerializer.
public void WriteResourceValue(ODataResourceValue resourceValue, …)
{
// TODO
}
This method is the key method in writing scenario.
We should update WriteCollectionValue(...)
method to call above WriteResourceValue(...)
if the item is an ODataResourceValue
.
foreach (object item in items)
{
ODataResourceValue itemAsResourceValue = item as ODataResourceValue;
if (itemAsResourceValue != null)
{
this.WriteResourceValue(…);
}
else
{
…// primitive, enum
}
}
We should update ODataJsonLightPropertyWriter.cs to support writing the property with ODataResourceValue
or collection of resource value.
This change should in WriteProperty(...)
method, it supports to write top-level property and non-top-level property (Nested Property).
private void WriteProperty(…)
{
......
ODataResourceValue resourceValue = value as ODataResourceValue;
if (resourceValue != null)
{
this.JsonWriter.WriteName(propertyName);
this.JsonLightValueSerializer.WriteResourceValue(resourceValue, …);
return;
}
}
We don’t need to change any codes for property with value as ODataCollectionValue
which element is ODataResourceValue
.
We should update JsonLightInstanceAnnotationWriter.cs to support:
We should add the below codes in WriteInstanceAnnotation(...)
method by calling the WriteResourceValue(...)
if the instance annotation value is an ODataResourceValue
.
ODataResourceValue resourceValue = value as ODataResourceValue;
if (resourceValue != null)
{
this.WriteInstanceAnnotationName(propertyName, name);
this.valueSerializer.WriteResourceValue(resourceValue,…);
return;
}
We don’t need to do anything. Because it supports to write the ODataCollectionValue
, which in turns will call WriteResourceValue()
for each ODataResourceValue
elements.
We should update ODataJsonLightCollectionWriter.cs to support write collection with ODataResourceValue
item.
protected override void WriteCollectionItem(object item, IEdmTypeReference expectedItemType)
{
ODataResourceValue resourceValue = item as ODataResourceValue;
if (resourceValue != null)
{
this.jsonLightCollectionSerializer.WriteResourceValue(resourceValue,…);
}
…
}
We should update ODataJsonLightParameterWriter.cs to support write resource or collection of resource value.
protected override void WriteValueParameter(string parameterName, object parameterValue, IEdmTypeReference expectedTypeReference)
{
......
ODataResourceValue resourceValue = parameterValue as ODataResourceValue;
if (resourceValue != null)
{
this.jsonLightValueSerializer.WriteResourceValue(resourceValue, ….);
}
}
Normally, if you want to write a Collection parameter, you should do:
var parameterWriter = new ODataJsonLightParameterWriter(outputContext, operation: null);
parameterWriter.WriteStart();
var collectionWriter = parameterWriter.CreateCollectionWriter("collection");
collectionWriter.WriteStart(new ODataCollectionStart());
collectionWriter.WriteItem("item1");
collectionWriter.WriteEnd();
parameterWriter.WriteEnd();
However, i think we should support to write the collection value directly if customer call WriteValueParameter()
method with the ODataCollectionValue
.
Basically, we don’t need to change any codes for the “Collection parameter value” writer. Customer still can use “CreateCollectionWriter” to write the collection with more information.
Besides, We don’t need to change any codes for Resource or ResourceSet parameter writer. Customer still can use them to writer ODataResource
or ODataResourceSet
one by one. See:
- CreateFormatResourceWriter
- CreateFormatResourceSetWriter
We should support to convert ODataResourceValue
and collection of it to Uri literal when customer call ConvertToUriLiteral(...)
in ODataUriUitl.cs.
public static string ConvertToUriLiteral(object value, ODataVersion version, IEdmModel model)
{
......
ODataResourceValue resourceValue = value as ODataResourceValue;
if (resourceValue != null)
{
return ODataUriConversionUtils.ConvertToResourceLiteral(resourceValue, model, version);
}
......
}
We should update ODataJsonLightPropertyAndValueDeserialier.cs to read the resource value.
private ODataResourceValue ReadResourceValue(
bool insideJsonObjectValue,
bool insideComplexValue,
string propertyName,
IEdmStructuredTypeReference structuredTypeReference,
string payloadTypeName,
PropertyAndAnnotationCollector propertyAndAnnotationCollector)
{…}
This method is the key method in reading scenario, it should support to:
- Read its own instance annotation
- Read all properties value, include nested resource value.
The above method is called from:
private object ReadNonEntityValueImplementation(…)
{
......
case EdmTypeKind.Complex: // nested complex
case EdmTypeKind.Entity: // nested entity
......
result = ReadResourceValue(......);
break;
}
For the collection of resource, owing that ReadCollectionValue()
will call ReadNonEntityValueImplemenation(…)
to read its elements, so, if the item is entity or complex
, it will return DataResourceValue
. We don’t need to change any codes.
ODataJsonLightPropertyAndValueDeserialier.cs has the following method to read instance annotation value:
internal object ReadCustomInstanceAnnotationValue (PropertyAndAnnotationCollector propertyAndAnnotationCollector, string name)
{
object customInstanceAnnotationValue = this.ReadNonEntityValueImplementation(…);
}
So, we don’t need to change any codes for it.
ODataJsonLightCollectionDeserializer.cs will call ReadNonEntityValueImplementation
. We don’t need change any code.
However, there’s some validation codes that need to change.
ODataJsonLightErrorDeserializer.cs will call ReadNonEntityValueImplementation
. We don’t need change any code.
ODataJsonLightResourceDeserializer.cs will call ReadNonEntityValueImplementation
to read the value of its property.
However,
- It is not used to read the “complex and collection of complex”
- It is not used to read the “navigation property”
- It’s ONLY used it to read the primitive, enum and collection of them. And, for the “complex and collection of complex”, we still create nested resource info. So, we don’t need to change anything.
ODataJsonLightParameterDeserialzer.cs is used to read parameter value. So far, for the entity, complex, it only returns a parameter state as “Resource”, for the collection of them, return a parameter state of “ResourceSet” as below:
- Primitive type, read as primitive value.
- Enum type, read as “ODataEnumValue”
- TypeDefintion, read as “TypeDefinition” value
- Complex, Entity, read nothing, just return “ReaderState.Resource”
- Collection,
- If element is primitive, read as primitive value.
- If element is enum, read nothing, just return “ReaderState.Collection”
- If element is complex, entity, read nothing, just return “ReaderState.ResourceSet”. So, we should have a configuration enable customer to change the logic. For example: On ODataMessageReader, we can enable customer to create a parameter reader which can read all parameter as value.
public ODataParameterReader CreateODataParameterReader(IEdmOperation operation, bool readAllAsValue)
{
......
}
So, if customer call the above method using true for readAllAsValue
, he can get:
- Complex, Entity, read as “ODataResourceValue”
- Collection, read as “ODataCollectionValue”.
ODataJsonLightPropertyAndValueDeserializer.cs can read the top-level property into a ODataProperty. So, we can read a top-level complex, entity, or collection or complex, entity property.
We should convert the ODataResourceValue
from JSON Uri literal in ConvertFromUriLiteral(...)
in ODataUriUtils.cs.
So far, if you create:
ODataCollectionValue value = null;
String str = ConvertToUriLiteral(value, ODataVersion.V4, model);
Assert.Equal("null", str); // true?
However, it should be "[]" ?
In the 6.x version, if a complex value has instance annotations, those instance annotations will not write out when we call like:
ODataComplexValue value = new ODataComplexValue()
{
TypeName = "TestModel.Address",
Properties = new ODataProperty[] { new ODataProperty() { Name = "Street", Value = "street" }} ,
InstanceAnnotations = new[]
{
new ODataInstanceAnnotation("Custom.Ok", new ODataComplexValue
{
TypeName = "TestModel.Address",
Properties = new ODataProperty[] { new ODataProperty() { Name = "Street", Value = "street" }})
}
}
string str = ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.40, model);
Where, str
doesn’t include the instance annotation? But, we should include the instance annotation.