Skip to content

Commit

Permalink
Fix support for containment paths in nav prop bindings (#1109)
Browse files Browse the repository at this point in the history
- Fix numerous issues with containment nav prop bindings
- Fix annotation validation and contextUrl validation tests
- Fix issues with locating the proper contained entity set in FindNavigationTarget
- Fix nav prop binding issues with nav props on complex types
- Fix issues with reading/writing nav prop bindings for contained entity sets
- Add support for writing context for contained nav props under LibraryCompatibility.Version6
- Add support for entity types used exclusively in singletons and single-valued nav props without defining a key
  • Loading branch information
mikepizzo authored and AlanWong-MS committed Mar 19, 2018
1 parent 3dc7ffe commit b214dbf
Show file tree
Hide file tree
Showing 39 changed files with 1,637 additions and 465 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ internal void WriteTopLevelProperty(ODataProperty property)

if (!(this.JsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel))
{
ODataContextUrlInfo contextInfo = ODataContextUrlInfo.Create(property.ODataValue, this.JsonLightOutputContext.MessageWriterSettings.ODataUri, this.Model);
ODataContextUrlInfo contextInfo = ODataContextUrlInfo.Create(property.ODataValue, this.MessageWriterSettings.Version ?? ODataVersion.V4, this.JsonLightOutputContext.MessageWriterSettings.ODataUri, this.Model);
this.WriteContextUriProperty(kind, () => contextInfo);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ internal void WriteOperations(IEnumerable<ODataOperation> operations, bool isAct
internal ODataContextUrlInfo WriteDeltaContextUri(ODataResourceTypeContext typeContext, ODataDeltaKind kind, ODataContextUrlInfo parentContextUrlInfo = null)
{
ODataUri odataUri = this.JsonLightOutputContext.MessageWriterSettings.ODataUri;
return this.WriteContextUriProperty(ODataPayloadKind.Delta, () => ODataContextUrlInfo.Create(typeContext, kind, odataUri), parentContextUrlInfo);
return this.WriteContextUriProperty(ODataPayloadKind.Delta, () => ODataContextUrlInfo.Create(typeContext, this.MessageWriterSettings.Version ?? ODataVersion.V4, kind, odataUri), parentContextUrlInfo);
}

/// <summary>
Expand All @@ -350,7 +350,7 @@ internal ODataContextUrlInfo WriteDeltaContextUri(ODataResourceTypeContext typeC
internal ODataContextUrlInfo WriteResourceContextUri(ODataResourceTypeContext typeContext, ODataContextUrlInfo parentContextUrlInfo = null)
{
ODataUri odataUri = this.JsonLightOutputContext.MessageWriterSettings.ODataUri;
return this.WriteContextUriProperty(ODataPayloadKind.Resource, () => ODataContextUrlInfo.Create(typeContext, /* isSingle */ true, odataUri), parentContextUrlInfo);
return this.WriteContextUriProperty(ODataPayloadKind.Resource, () => ODataContextUrlInfo.Create(typeContext, this.MessageWriterSettings.Version ?? ODataVersion.V4, /* isSingle */ true, odataUri), parentContextUrlInfo);
}

/// <summary>
Expand All @@ -361,7 +361,7 @@ internal ODataContextUrlInfo WriteResourceContextUri(ODataResourceTypeContext ty
internal ODataContextUrlInfo WriteResourceSetContextUri(ODataResourceTypeContext typeContext)
{
ODataUri odataUri = this.JsonLightOutputContext.MessageWriterSettings.ODataUri;
return this.WriteContextUriProperty(ODataPayloadKind.ResourceSet, () => ODataContextUrlInfo.Create(typeContext, /* isSingle */ false, odataUri));
return this.WriteContextUriProperty(ODataPayloadKind.ResourceSet, () => ODataContextUrlInfo.Create(typeContext, this.MessageWriterSettings.Version ?? ODataVersion.V4, /* isSingle */ false, odataUri));
}

/// <summary>
Expand Down
6 changes: 4 additions & 2 deletions src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -786,13 +786,15 @@ protected override void StartNestedResourceInfoWithContent(ODataNestedResourceIn
{
// Write @odata.context annotation for navigation property
var containedEntitySet = this.CurrentScope.NavigationSource as IEdmContainedEntitySet;
if (containedEntitySet != null)
if (containedEntitySet != null && this.jsonLightOutputContext.MessageWriterSettings.LibraryCompatibility < ODataLibraryCompatibility.Version7)
{
ODataContextUrlInfo info = ODataContextUrlInfo.Create(
this.CurrentScope.NavigationSource,
this.CurrentScope.ResourceType.FullTypeName(),
containedEntitySet.NavigationProperty.Type.TypeKind() != EdmTypeKind.Collection,
this.CurrentScope.ODataUri);
this.CurrentScope.ODataUri,
this.jsonLightOutputContext.MessageWriterSettings.Version ?? ODataVersion.V4);

this.jsonLightResourceSerializer.WriteNestedResourceInfoContextUrl(nestedResourceInfo, info);
}

Expand Down
40 changes: 26 additions & 14 deletions src/Microsoft.OData.Core/ODataContextUrlInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,17 @@ private ODataContextUrlInfo()
/// Create ODataContextUrlInfo for OdataValue.
/// </summary>
/// <param name="value">The ODataValue to be used.</param>
/// <param name="version">OData Version.</param>
/// <param name="odataUri">The odata uri info for current query.</param>
/// <param name="model">The model used to handle unsigned int conversions.</param>
/// <returns>The generated ODataContextUrlInfo.</returns>
internal static ODataContextUrlInfo Create(ODataValue value, ODataUri odataUri = null, IEdmModel model = null)
internal static ODataContextUrlInfo Create(ODataValue value, ODataVersion version, ODataUri odataUri = null, IEdmModel model = null)
{
return new ODataContextUrlInfo()
{
TypeName = GetTypeNameForValue(value, model),
ResourcePath = ComputeResourcePath(odataUri),
QueryClause = ComputeQueryClause(odataUri),
QueryClause = ComputeQueryClause(odataUri, version),
IsUndeclared = ComputeIfIsUndeclared(odataUri)
};
}
Expand Down Expand Up @@ -117,8 +118,9 @@ internal static ODataContextUrlInfo Create(ODataCollectionStartSerializationInfo
/// <param name="expectedEntityTypeName">The expectedEntity for current element.</param>
/// <param name="isSingle">Whether target is single item.</param>
/// <param name="odataUri">The odata uri info for current query.</param>
/// <param name="version">The OData Version of the response.</param>
/// <returns>The generated ODataContextUrlInfo.</returns>
internal static ODataContextUrlInfo Create(IEdmNavigationSource navigationSource, string expectedEntityTypeName, bool isSingle, ODataUri odataUri)
internal static ODataContextUrlInfo Create(IEdmNavigationSource navigationSource, string expectedEntityTypeName, bool isSingle, ODataUri odataUri, ODataVersion version)
{
EdmNavigationSourceKind kind = navigationSource.NavigationSourceKind();
string navigationSourceEntityType = navigationSource.EntityType().FullName();
Expand All @@ -131,7 +133,7 @@ internal static ODataContextUrlInfo Create(IEdmNavigationSource navigationSource
IncludeFragmentItemSelector = isSingle && kind != EdmNavigationSourceKind.Singleton,
NavigationPath = ComputeNavigationPath(kind, odataUri, navigationSource.Name),
ResourcePath = ComputeResourcePath(odataUri),
QueryClause = ComputeQueryClause(odataUri),
QueryClause = ComputeQueryClause(odataUri, version),
IsUndeclared = ComputeIfIsUndeclared(odataUri)
};
}
Expand All @@ -140,10 +142,11 @@ internal static ODataContextUrlInfo Create(IEdmNavigationSource navigationSource
/// Create ODataContextUrlInfo from ODataResourceTypeContext
/// </summary>
/// <param name="typeContext">The ODataResourceTypeContext to be used.</param>
/// <param name="version">The OData Version of the response</param>
/// <param name="isSingle">Whether target is single item.</param>
/// <param name="odataUri">The odata uri info for current query.</param>
/// <returns>The generated ODataContextUrlInfo.</returns>
internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext, bool isSingle, ODataUri odataUri = null)
internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext, ODataVersion version, bool isSingle, ODataUri odataUri = null)
{
Debug.Assert(typeContext != null, "typeContext != null");

Expand Down Expand Up @@ -172,7 +175,7 @@ internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext,
IncludeFragmentItemSelector = isSingle && typeContext.NavigationSourceKind != EdmNavigationSourceKind.Singleton,
NavigationPath = ComputeNavigationPath(typeContext.NavigationSourceKind, odataUri, typeContext.NavigationSourceName),
ResourcePath = ComputeResourcePath(odataUri),
QueryClause = ComputeQueryClause(odataUri),
QueryClause = ComputeQueryClause(odataUri, version),
IsUndeclared = ComputeIfIsUndeclared(odataUri)
};
}
Expand All @@ -181,10 +184,11 @@ internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext,
/// Create contextUrlInfo for delta
/// </summary>
/// <param name="typeContext">The ODataResourceTypeContext to be used.</param>
/// <param name="version">The OData version of the response.</param>
/// <param name="kind">The delta kind.</param>
/// <param name="odataUri">The odata uri info for current query.</param>
/// <returns>The generated ODataContextUrlInfo.</returns>
internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext, ODataDeltaKind kind, ODataUri odataUri = null)
internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext, ODataVersion version, ODataDeltaKind kind, ODataUri odataUri = null)
{
Debug.Assert(typeContext != null, "typeContext != null");

Expand All @@ -205,7 +209,7 @@ internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext,
contextUriInfo.NavigationPath = ComputeNavigationPath(typeContext.NavigationSourceKind, odataUri,
typeContext.NavigationSourceName);
contextUriInfo.ResourcePath = ComputeResourcePath(odataUri);
contextUriInfo.QueryClause = ComputeQueryClause(odataUri);
contextUriInfo.QueryClause = ComputeQueryClause(odataUri, version);
contextUriInfo.IsUndeclared = ComputeIfIsUndeclared(odataUri);
}

Expand Down Expand Up @@ -272,7 +276,7 @@ private static string ComputeResourcePath(ODataUri odataUri)
return string.Empty;
}

private static string ComputeQueryClause(ODataUri odataUri)
private static string ComputeQueryClause(ODataUri odataUri, ODataVersion version)
{
if (odataUri != null)
{
Expand All @@ -283,7 +287,7 @@ private static string ComputeQueryClause(ODataUri odataUri)
}
else
{
return CreateSelectExpandContextUriSegment(odataUri.SelectAndExpand);
return CreateSelectExpandContextUriSegment(odataUri.SelectAndExpand, version);
}
}

Expand Down Expand Up @@ -375,13 +379,14 @@ private static string CreateApplyUriSegment(ApplyClause applyClause)
/// Build the expand clause for a given level in the selectExpandClause
/// </summary>
/// <param name="selectExpandClause">the current level select expand clause</param>
/// <param name="version">OData Version of the response</param>
/// <returns>the select and expand segment for context url in this level.</returns>
private static string CreateSelectExpandContextUriSegment(SelectExpandClause selectExpandClause)
private static string CreateSelectExpandContextUriSegment(SelectExpandClause selectExpandClause, ODataVersion version)
{
if (selectExpandClause != null)
{
string contextUri;
selectExpandClause.Traverse(ProcessSubExpand, CombineSelectAndExpandResult, out contextUri);
selectExpandClause.Traverse(ProcessSubExpand, CombineSelectAndExpandResult, version, out contextUri);
if (!string.IsNullOrEmpty(contextUri))
{
return ODataConstants.ContextUriProjectionStart + contextUri + ODataConstants.ContextUriProjectionEnd;
Expand All @@ -394,10 +399,12 @@ private static string CreateSelectExpandContextUriSegment(SelectExpandClause sel
/// <summary>Process sub expand node, contact with subexpand result</summary>
/// <param name="expandNode">The current expanded node.</param>
/// <param name="subExpand">Generated sub expand node.</param>
/// <param name="version">OData Version of the generated response.</param>
/// <returns>The generated expand string.</returns>
private static string ProcessSubExpand(string expandNode, string subExpand)
private static string ProcessSubExpand(string expandNode, string subExpand, ODataVersion version)
{
return string.IsNullOrEmpty(subExpand) ? null : expandNode + ODataConstants.ContextUriProjectionStart + subExpand + ODataConstants.ContextUriProjectionEnd;
return string.IsNullOrEmpty(subExpand) && version <= ODataVersion.V4 ? null :
expandNode + ODataConstants.ContextUriProjectionStart + subExpand + ODataConstants.ContextUriProjectionEnd;
}

/// <summary>Create combined result string using selected items list and expand items list.</summary>
Expand All @@ -407,8 +414,13 @@ private static string ProcessSubExpand(string expandNode, string subExpand)
private static string CombineSelectAndExpandResult(IList<string> selectList, IList<string> expandList)
{
string currentExpandClause = string.Empty;

if (selectList.Any())
{
// https://github.com/OData/odata.net/issues/1104
// If the user explicitly selects and expands a nav prop, we should include both forms in contextUrl
// We can't, though, because SelectExpandClauseFinisher.AddExplicitNavPropLinksWhereNecessary adds all of
// the expanded items to the select before it gets here, so we can't tell what is explicitly selected by the user.
foreach (var item in expandList)
{
string expandNode = item.Substring(0, item.IndexOf(ODataConstants.ContextUriProjectionStart));
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ internal SelectedPropertiesNode SelectedProperties
get
{
return this.SelectExpandClause != null
? SelectedPropertiesNode.Create(this.SelectExpandClause)
? SelectedPropertiesNode.Create(this.SelectExpandClause, this.Version ?? ODataVersion.V4)
: SelectedPropertiesNode.EntireSubtree;
}
}
Expand Down
13 changes: 8 additions & 5 deletions src/Microsoft.OData.Core/SelectedPropertiesNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,16 @@ internal static SelectedPropertiesNode Create(string selectQueryOption)
/// Creates a node from the given SelectExpandClause.
/// </summary>
/// <param name="selectExpandClause">The value of the $select query option.</param>
/// <param name="version">OData version</param>
/// <returns>A tree representation of the selected properties specified in the query option.</returns>
internal static SelectedPropertiesNode Create(SelectExpandClause selectExpandClause)
internal static SelectedPropertiesNode Create(SelectExpandClause selectExpandClause, ODataVersion version)
{
if (selectExpandClause.AllSelected)
{
return EntireSubtree;
}

return CreateFromSelectExpandClause(selectExpandClause);
return CreateFromSelectExpandClause(selectExpandClause, version);
}

/// <summary>
Expand Down Expand Up @@ -582,19 +583,21 @@ private bool IsOperationSelectedAtThisLevel(IEdmOperation operation, bool mustBe

/// <summary>Create SelectedPropertiesNode from SelectExpandClause.</summary>
/// <param name="selectExpandClause">The SelectExpandClause representing $select and $expand clauses.</param>
/// <param name="version">OData version</param>
/// <returns>SelectedPropertiesNode generated using <paramref name="selectExpandClause"/></returns>
private static SelectedPropertiesNode CreateFromSelectExpandClause(SelectExpandClause selectExpandClause)
private static SelectedPropertiesNode CreateFromSelectExpandClause(SelectExpandClause selectExpandClause, ODataVersion version)
{
SelectedPropertiesNode node;
selectExpandClause.Traverse(ProcessSubExpand, CombineSelectAndExpandResult, out node);
selectExpandClause.Traverse(ProcessSubExpand, CombineSelectAndExpandResult, version, out node);
return node;
}

/// <summary>Process sub expand node, set name for the node.</summary>
/// <param name="nodeName">Node name for the subexpandnode.</param>
/// <param name="subExpandNode">Generated sub expand node.</param>
/// <param name="version">OData version.</param>
/// <returns>The sub expanded node passed in.</returns>
private static SelectedPropertiesNode ProcessSubExpand(string nodeName, SelectedPropertiesNode subExpandNode)
private static SelectedPropertiesNode ProcessSubExpand(string nodeName, SelectedPropertiesNode subExpandNode, ODataVersion version)
{
if (subExpandNode != null)
{
Expand Down
Loading

0 comments on commit b214dbf

Please sign in to comment.