// We need to append the key value path segment from $id. private static void AppendIdForRef(IList <ODL.ODataPathSegment> segments, ODL.KeySegment id) { if (id == null || !(segments.Last() is ODL.NavigationPropertyLinkSegment)) { return; } segments.Add(id); }
/// <summary> /// Check if this segment is equal to another segment. /// </summary> /// <param name="other">the other segment to check.</param> /// <returns>true if the other segment is equal.</returns> /// <exception cref="System.ArgumentNullException">Throws if the input other is null.</exception> internal override bool Equals(ODataPathSegment other) { ExceptionUtils.CheckArgumentNotNull(other, "other"); KeySegment otherKeySegment = other as KeySegment; return(otherKeySegment != null && otherKeySegment.Keys.SequenceEqual(this.Keys) && otherKeySegment.EdmType == this.edmType && otherKeySegment.NavigationSource == this.navigationSource); }
/// <summary> /// Translate a KeySegment /// </summary> /// <param name="segment">the segment to Translate</param> /// <returns>Defined by the implementer.</returns> public override string Translate(KeySegment segment) { Debug.Assert(segment != null, "segment != null"); List <KeyValuePair <string, object> > keys = segment.Keys.ToList(); StringBuilder builder = new StringBuilder(); KeySerializer.AppendKeyExpression(builder, new Collection <KeyValuePair <string, object> >(keys), p => p.Key, p => p.Value); return(builder.ToString()); }
/// <summary> /// Add the key segment in the ODataPath, the method does not modify current ODataPath instance, /// it returns a new ODataPath without ending type segment. /// If last segment is type cast, the key would be appended before type cast segment. /// </summary> /// <param name="path">Path to perform the computation on.</param> /// <param name="keys">The set of key property names and the values to be used in searching for the given item.</param> /// <param name="edmType">The type of the item this key returns.</param> /// <param name="navigationSource">The navigation source that this key is used to search.</param> /// <returns>A new ODataPath with key segment added</returns> public static ODataPath AddKeySegment(this ODataPath path, IEnumerable <KeyValuePair <string, object> > keys, IEdmEntityType edmType, IEdmNavigationSource navigationSource) { var handler = new SplitEndingSegmentOfTypeHandler <TypeSegment>(); path.WalkWith(handler); KeySegment keySegment = new KeySegment(keys, edmType, navigationSource); ODataPath newPath = handler.FirstPart; newPath.Add(keySegment); AppendLastSegment(handler, newPath); return(newPath); }
/// <summary> /// Parses the key properties based on the segment's target type, then creates a new segment for the key. /// </summary> /// <param name="segment">The segment to apply the key to.</param> /// <param name="previousKeySegment">The parent node's key segment.</param> /// <param name="key">The key to apply.</param> /// <param name="resolver">The resolver to use.</param> /// <returns>The newly created key segment.</returns> private static KeySegment CreateKeySegment(ODataPathSegment segment, KeySegment previousKeySegment, SegmentArgumentParser key, ODataUriResolver resolver) { Debug.Assert(segment != null, "segment != null"); Debug.Assert(key != null && !key.IsEmpty, "key != null && !key.IsEmpty"); Debug.Assert(segment.SingleResult == false, "segment.SingleResult == false"); IEdmEntityType targetEntityType = null; if (!(segment.TargetEdmType != null && segment.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType))) { throw ExceptionUtil.CreateSyntaxError(); } Debug.Assert(targetEntityType != null, "targetEntityType != null"); // Make sure the keys specified in the uri matches with the number of keys in the metadata var keyProperties = targetEntityType.Key().ToList(); if (keyProperties.Count != key.ValueCount) { NavigationPropertySegment currentNavPropSegment = segment as NavigationPropertySegment; if (currentNavPropSegment != null) { key = KeyFinder.FindAndUseKeysFromRelatedSegment(key, keyProperties, currentNavPropSegment.NavigationProperty, previousKeySegment); } // if we still didn't find any keys, then throw an error. if (keyProperties.Count != key.ValueCount && resolver.GetType() == typeof(ODataUriResolver)) { throw ExceptionUtil.CreateBadRequestError(ErrorStrings.BadRequest_KeyCountMismatch(targetEntityType.FullName())); } } if (!key.AreValuesNamed && key.ValueCount > 1 && resolver.GetType() == typeof(ODataUriResolver)) { throw ExceptionUtil.CreateBadRequestError(ErrorStrings.RequestUriProcessor_KeysMustBeNamed); } IEnumerable <KeyValuePair <string, object> > keyPairs; if (!key.TryConvertValues(targetEntityType, out keyPairs, resolver)) { throw ExceptionUtil.CreateSyntaxError(); } return(new KeySegment(segment, keyPairs, targetEntityType, segment.TargetEdmNavigationSource)); }
/// <summary> /// Parse the odata path. /// </summary> /// <param name="model">The model to use for path parsing.</param> /// <param name="serviceRoot">The service root of the OData path.</param> /// <param name="odataPath">The OData path to parse.</param> /// <param name="template">The flag indicates whether the path is template or not.</param> /// <param name="serviceProvider">The service proivder.</param> /// <param name="httpContext"></param> /// <param name="resolverSettings"></param> /// <returns>A parsed representation of the path, or <c>null</c> if the path does not match the model.</returns> public static ODataPath Parse(IEdmModel model, string serviceRoot, string odataPath, bool template, IServiceProvider serviceProvider, ODataUriResolverSettings resolverSettings) { ODL.ODataUriParser uriParser; Uri serviceRootUri = null; Uri fullUri = null; // TODO: Replace this type. //NameValueCollection queryString = null; ODataOptions options = serviceProvider.GetRequiredService <IOptions <ODataOptions> >().Value; if (template) { uriParser = new ODL.ODataUriParser(model, new Uri(odataPath, UriKind.Relative), serviceProvider); uriParser.EnableUriTemplateParsing = true; } else { Contract.Assert(serviceRoot != null); serviceRootUri = new Uri( serviceRoot.EndsWith("/", StringComparison.Ordinal) ? serviceRoot : serviceRoot + "/"); fullUri = new Uri(serviceRootUri, odataPath); //queryString = fullUri.ParseQueryString(); uriParser = new ODL.ODataUriParser(model, serviceRootUri, fullUri, serviceProvider); } uriParser.Resolver = resolverSettings.CreateResolver(); if (options.UrlKeyDelimiter != null) { uriParser.UrlKeyDelimiter = options.UrlKeyDelimiter; } else { // ODL changes to use ODataUrlKeyDelimiter.Slash as default value. // Web API still uses the ODataUrlKeyDelimiter.Parentheses as default value. // Please remove it after fix: https://github.com/OData/odata.net/issues/642 uriParser.UrlKeyDelimiter = ODataUrlKeyDelimiter.Parentheses; } ODL.ODataPath path; UnresolvedPathSegment unresolvedPathSegment = null; ODL.KeySegment id = null; try { path = uriParser.ParsePath(); } catch (ODL.ODataUnrecognizedPathException ex) { if (ex.ParsedSegments != null && ex.ParsedSegments.Any() && (ex.ParsedSegments.Last().EdmType is IEdmComplexType || ex.ParsedSegments.Last().EdmType is IEdmEntityType) && ex.CurrentSegment != ODataSegmentKinds.Count) { if (!ex.UnparsedSegments.Any()) { path = new ODL.ODataPath(ex.ParsedSegments); unresolvedPathSegment = new UnresolvedPathSegment(ex.CurrentSegment); } else { // Throw ODataException if there is some segment following the unresolved segment. throw new ODataException(Error.Format( SRResources.InvalidPathSegment, ex.UnparsedSegments.First(), ex.CurrentSegment)); } } else { throw; } } if (!template && path.LastSegment is ODL.NavigationPropertyLinkSegment) { IEdmCollectionType lastSegmentEdmType = path.LastSegment.EdmType as IEdmCollectionType; if (lastSegmentEdmType != null) { ODL.EntityIdSegment entityIdSegment = null; bool exceptionThrown = false; try { entityIdSegment = uriParser.ParseEntityId(); if (entityIdSegment != null) { // Create another ODataUriParser to parse $id, which is absolute or relative. ODL.ODataUriParser parser = new ODL.ODataUriParser(model, serviceRootUri, entityIdSegment.Id); id = parser.ParsePath().LastSegment as ODL.KeySegment; } } catch (ODataException) { // Exception was thrown while parsing the $id. // We will throw another exception about the invalid $id. exceptionThrown = true; } if (exceptionThrown || (entityIdSegment != null && (id == null || !(id.EdmType.IsOrInheritsFrom(lastSegmentEdmType.ElementType.Definition) || lastSegmentEdmType.ElementType.Definition.IsOrInheritsFrom(id.EdmType))))) { throw new ODataException(Error.Format(SRResources.InvalidDollarId, "$id" /*queryString.Get("$id")*/)); } } } // do validation for the odata path path.WalkWith(new DefaultODataPathValidator(model)); // do segment translator (for example parameter alias, key & function parameter template, etc) var segments = ODataPathSegmentTranslator.Translate(model, path, uriParser.ParameterAliasNodes).ToList(); if (unresolvedPathSegment != null) { segments.Add(unresolvedPathSegment); } if (!template) { AppendIdForRef(segments, id); } return(new ODataPath(segments) { ODLPath = path }); }
/// <summary> /// Translate a KeySegment /// </summary> /// <param name="segment">the segment to Translate</param> /// <returns>UserDefinedValue</returns> /// <exception cref="System.ArgumentNullException">Throws if the input segment is null.</exception> public override bool Translate(KeySegment segment) { ExceptionUtils.CheckArgumentNotNull(segment, "segment"); return(false); }
/// <summary> /// Handle a KeySegment /// </summary> /// <param name="segment">the segment to Handle</param> public virtual void Handle(KeySegment segment) { throw new NotImplementedException(); }
/// <summary> /// Handle a KeySegment /// </summary> /// <param name="segment">the segment to Handle</param> public override void Handle(KeySegment segment) { CommonHandler(segment); }
/// <summary> /// Find any related keys from the parent key segment, if it exists, and add them to the raw key values that /// we already have from the uri. /// </summary> /// <param name="rawKeyValuesFromUri">The raw key values as we've parsed them from the uri.</param> /// <param name="targetEntityKeyProperties">The list of key properties on the target entity.</param> /// <param name="currentNavigationProperty">The current navigation property that we're trying to follow using the raw key values</param> /// <param name="keySegmentOfParentEntity">The key segment of the parent entity in this path, if it exists. Null otherwise</param> /// <returns>A new SegmentArgumentParser with any keys that were found added to its list of NamedValues.</returns> /// <throws>Throws if the input currentNavigationProperty is null.</throws> public static SegmentArgumentParser FindAndUseKeysFromRelatedSegment(SegmentArgumentParser rawKeyValuesFromUri, IEnumerable <IEdmStructuralProperty> targetEntityKeyProperties, IEdmNavigationProperty currentNavigationProperty, KeySegment keySegmentOfParentEntity) { ExceptionUtils.CheckArgumentNotNull(currentNavigationProperty, "currentNavigationProperty"); ExceptionUtils.CheckArgumentNotNull(rawKeyValuesFromUri, "rawKeyValuesFromUri"); ReadOnlyCollection <IEdmStructuralProperty> targetKeyPropertyList = targetEntityKeyProperties != null ? new ReadOnlyCollection <IEdmStructuralProperty>(targetEntityKeyProperties.ToList()) : new ReadOnlyCollection <IEdmStructuralProperty>(new List <IEdmStructuralProperty>()); // should only get here if the number of raw parameters from the uri is different than the number of key properties for the target entity. Debug.Assert(rawKeyValuesFromUri.ValueCount < targetKeyPropertyList.Count, "rawKeyValuesFromUri.ValueCount < targetEntityKeyProperties.Count()"); // if the raw key from the uri has positional values, there must be only one of them // its important to cache this value here because we'll change it when we add new // named values below (the implementation of AreValuesNamed is just namedValues !=null) bool hasPositionalValues = !rawKeyValuesFromUri.AreValuesNamed; if (hasPositionalValues && rawKeyValuesFromUri.ValueCount > 1) { return(rawKeyValuesFromUri); } if (keySegmentOfParentEntity == null) { return(rawKeyValuesFromUri); } // TODO: p2 merge the below 2 pieces of codes // find out if any target entity key properties have referential constraints that link them to the previous rawKeyValuesFromUri. List <EdmReferentialConstraintPropertyPair> keysFromReferentialIntegrityConstraint = ExtractMatchingPropertyPairsFromNavProp(currentNavigationProperty, targetKeyPropertyList).ToList(); foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint) { KeyValuePair <string, object> valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.DependentProperty.Name); if (valueFromParent.Key != null) { // if the key from the referential integrity constraint is one of the target key properties // and that key property isn't already populated in the raw key values from the uri, then // we set that value to the value from the parent key segment. if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name)) { rawKeyValuesFromUri.AddNamedValue( keyFromReferentialIntegrityConstraint.PrincipalProperty.Name, ConvertKeyValueToUriLiteral(valueFromParent.Value, rawKeyValuesFromUri.KeyAsSegment)); } } } // also need to look to see if any nav props exist in the target set that refer back to this same set, which might have // referential constraints also. keysFromReferentialIntegrityConstraint.Clear(); IEdmNavigationProperty reverseNavProp = currentNavigationProperty.Partner; if (reverseNavProp != null) { keysFromReferentialIntegrityConstraint.AddRange(ExtractMatchingPropertyPairsFromReversedNavProp(reverseNavProp, targetKeyPropertyList)); } foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint) { KeyValuePair <string, object> valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name); if (valueFromParent.Key != null) { // if the key from the referential integrity constraint is one of the target key properties // and that key property isn't already populated in the raw key values from the uri, then // we set that value to the value from the parent key segment. if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.DependentProperty.Name)) { rawKeyValuesFromUri.AddNamedValue( keyFromReferentialIntegrityConstraint.DependentProperty.Name, ConvertKeyValueToUriLiteral(valueFromParent.Value, rawKeyValuesFromUri.KeyAsSegment)); } } } // if we had a positional value before, then we need to add that value as a new named value. // the name that we choose will be the only value from the target entity key properties // that isn't already set in the NamedValues list. if (hasPositionalValues) { if (rawKeyValuesFromUri.NamedValues != null) { List <IEdmStructuralProperty> unassignedProperties = targetKeyPropertyList.Where(x => !rawKeyValuesFromUri.NamedValues.ContainsKey(x.Name)).ToList(); if (unassignedProperties.Count == 1) { rawKeyValuesFromUri.AddNamedValue(unassignedProperties[0].Name, rawKeyValuesFromUri.PositionalValues[0]); } else { return(rawKeyValuesFromUri); } // clear out the positional value so that we keep a consistent state in the // raw keys from uri. rawKeyValuesFromUri.PositionalValues.Clear(); } else { return(rawKeyValuesFromUri); } } return(rawKeyValuesFromUri); }
/// <summary> /// Translate a KeySegment /// </summary> /// <param name="segment">the segment to Translate</param> /// <returns>Defined by the implementer.</returns> public virtual T Translate(KeySegment segment) { throw new NotImplementedException(); }
private ODataPath Parse(string serviceRoot, string odataPath, IServiceProvider requestContainer, bool template) { ODataUriParser uriParser; Uri serviceRootUri = null; Uri fullUri = null; IEdmModel model = requestContainer.GetRequiredService <IEdmModel>(); if (template) { uriParser = new ODataUriParser(model, new Uri(odataPath, UriKind.Relative), requestContainer); uriParser.EnableUriTemplateParsing = true; } else { Contract.Assert(serviceRoot != null); serviceRootUri = new Uri( serviceRoot.EndsWith("/", StringComparison.Ordinal) ? serviceRoot : serviceRoot + "/"); // Concatenate the root and path and create a Uri. Using Uri to build a Uri from // a root and relative path changes the casing on .NetCore. However, odataPath may // be a full Uri. if (!Uri.TryCreate(odataPath, UriKind.Absolute, out fullUri)) { fullUri = new Uri(serviceRootUri + odataPath); } // Due to a bug in the System.Uri some relative paths are rejected if they contain // a ':' symbol on a position greater than 1024. This careful check should mitigate // this problem by encoding these characters before the path is combined with // service roor Uri. // https://github.com/dotnet/corefx/issues/29011 if (!Uri.IsWellFormedUriString(odataPath, UriKind.RelativeOrAbsolute) && odataPath.IndexOf(':') > MaxUriSchemeName) { var odataPathColonEncoded = odataPath.Replace(":", "%3A"); if (Uri.IsWellFormedUriString(odataPathColonEncoded, UriKind.Relative)) { odataPath = odataPathColonEncoded; } } fullUri = new Uri(serviceRootUri, odataPath); uriParser = new ODataUriParser(model, serviceRootUri, fullUri, requestContainer); } if (UrlKeyDelimiter != null) { uriParser.UrlKeyDelimiter = UrlKeyDelimiter; } else { uriParser.UrlKeyDelimiter = ODataUrlKeyDelimiter.Slash; } ODL.ODataPath path; UnresolvedPathSegment unresolvedPathSegment = null; ODL.KeySegment id = null; try { path = uriParser.ParsePath(); } catch (ODataUnrecognizedPathException ex) { if (ex.ParsedSegments != null && ex.ParsedSegments.Any() && (ex.ParsedSegments.Last().EdmType is IEdmComplexType || ex.ParsedSegments.Last().EdmType is IEdmEntityType) && ex.CurrentSegment != ODataSegmentKinds.Count) { if (!ex.UnparsedSegments.Any()) { path = new ODL.ODataPath(ex.ParsedSegments); unresolvedPathSegment = new UnresolvedPathSegment(ex.CurrentSegment); } else { // Throw ODataException if there is some segment following the unresolved segment. throw new ODataException(Error.Format( SRResources.InvalidPathSegment, ex.UnparsedSegments.First(), ex.CurrentSegment)); } } else { throw; } } if (!template && path.LastSegment is ODL.NavigationPropertyLinkSegment) { IEdmCollectionType lastSegmentEdmType = path.LastSegment.EdmType as IEdmCollectionType; if (lastSegmentEdmType != null) { ODL.EntityIdSegment entityIdSegment = null; bool exceptionThrown = false; try { entityIdSegment = uriParser.ParseEntityId(); if (entityIdSegment != null) { // Create another ODataUriParser to parse $id, which is absolute or relative. ODataUriParser parser = new ODataUriParser(model, serviceRootUri, entityIdSegment.Id, requestContainer); id = parser.ParsePath().LastSegment as ODL.KeySegment; } } catch (ODataException) { // Exception was thrown while parsing the $id. // We will throw another exception about the invalid $id. exceptionThrown = true; } if (exceptionThrown || (entityIdSegment != null && (id == null || !(id.EdmType.IsOrInheritsFrom(lastSegmentEdmType.ElementType.Definition) || lastSegmentEdmType.ElementType.Definition.IsOrInheritsFrom(id.EdmType))))) { // System.Net.Http on NetCore does not have the Uri extension method // ParseQueryString(), to avoid a platform-specific call, extract $id manually. string idValue = fullUri.Query; string idParam = "$id="; int start = idValue.IndexOf(idParam, StringComparison.OrdinalIgnoreCase); if (start >= 0) { int end = idValue.IndexOf("&", start, StringComparison.OrdinalIgnoreCase); if (end >= 0) { idValue = idValue.Substring(start + idParam.Length, end - 1); } else { idValue = idValue.Substring(start + idParam.Length); } } throw new ODataException(Error.Format(SRResources.InvalidDollarId, idValue)); } } } // do validation for the odata path path.WalkWith(new DefaultODataPathValidator(model)); // do segment translator (for example parameter alias, key & function parameter template, etc) var segments = ODataPathSegmentTranslator.Translate(model, path, uriParser.ParameterAliasNodes).ToList(); if (unresolvedPathSegment != null) { segments.Add(unresolvedPathSegment); } if (!template) { AppendIdForRef(segments, id); } return(new ODataPath(segments) { Path = path }); }
/// <summary> /// Determine the NavigationSource of a KeySegment /// </summary> /// <param name="segment">The KeySegment to look in.</param> /// <returns>The IEdmNavigationSource of this KeySegment</returns> /// <exception cref="System.ArgumentNullException">Throws if the input segment is null.</exception> public override IEdmNavigationSource Translate(KeySegment segment) { ExceptionUtils.CheckArgumentNotNull(segment, "segment"); return(segment.NavigationSource); }
/// <summary> /// Tries to handle the current segment as a key property value. /// </summary> /// <param name="segmentText">The segment text.</param> /// <param name="previous">The previous segment.</param> /// <param name="previousKeySegment">The parent node's key segment.</param> /// <param name="odataUrlKeyDelimiter">Key delimiter used in url.</param> /// <param name="resolver">The resolver to use.</param> /// <param name="keySegment">The key segment that was created if the segment could be interpreted as a key.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <returns>Whether or not the segment was interpreted as a key.</returns> internal static bool TryHandleSegmentAsKey(string segmentText, ODataPathSegment previous, KeySegment previousKeySegment, ODataUrlKeyDelimiter odataUrlKeyDelimiter, ODataUriResolver resolver, out KeySegment keySegment, bool enableUriTemplateParsing = false) { Debug.Assert(previous != null, "previous != null"); Debug.Assert(odataUrlKeyDelimiter != null, "odataUrlKeyDelimiter != null"); Debug.Assert(resolver != null, "resolver != null"); keySegment = null; // If the current convention does not support keys-as-segments, then this does not apply. if (!odataUrlKeyDelimiter.EnableKeyAsSegment) { return(false); } // Keys only apply to collections, so if the prior segment is already a singleton, do not treat this segment as a key. if (previous.SingleResult) { return(false); } // System segments (ie '$count') are never keys. if (IsSystemSegment(segmentText)) { return(false); } // If the previous type is not an entity collection type // TODO: collapse this and SingleResult. IEdmEntityType targetEntityType; if (previous.TargetEdmType == null || !previous.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType)) { return(false); } // Previously KeyAsSegment only allows single key, but we can also leverage related key finder to auto fill // missed key value from referential constraint information, which would be done in CreateKeySegment. // CreateKeySegment method will check whether key properties are missing after taking in related key values. keySegment = CreateKeySegment(previous, previousKeySegment, SegmentArgumentParser.FromSegment(segmentText, enableUriTemplateParsing), resolver); return(true); }
/// <summary>Tries to create a key segment for the given filter if it is non empty.</summary> /// <param name="previous">Segment on which to compose.</param> /// <param name="previousKeySegment">The parent node's key segment.</param> /// <param name="parenthesisExpression">Parenthesis expression of segment.</param> /// <param name="resolver">The resolver to use.</param> /// <param name="keySegment">The key segment that was created if the key was non-empty.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <returns>Whether the key was non-empty.</returns> internal static bool TryCreateKeySegmentFromParentheses(ODataPathSegment previous, KeySegment previousKeySegment, string parenthesisExpression, ODataUriResolver resolver, out ODataPathSegment keySegment, bool enableUriTemplateParsing = false) { Debug.Assert(parenthesisExpression != null, "parenthesisExpression != null"); Debug.Assert(previous != null, "segment!= null"); Debug.Assert(resolver != null, "resolver != null"); if (previous.SingleResult) { throw ExceptionUtil.CreateSyntaxError(); } SegmentArgumentParser key; if (!SegmentArgumentParser.TryParseKeysFromUri(parenthesisExpression, out key, enableUriTemplateParsing)) { throw ExceptionUtil.CreateSyntaxError(); } // People/NS.Employees() is OK, just like People() is OK if (key.IsEmpty) { keySegment = null; return(false); } keySegment = CreateKeySegment(previous, previousKeySegment, key, resolver); return(true); }
public override ODataPath Parse(string serviceRoot, string odataPath, IServiceProvider requestContainer) { ODL.ODataUriParser uriParser; Uri serviceRootUri = null; Uri fullUri = null; string dataSourceName = odataPath.Split('/')[0]; IEdmModel model = requestContainer.GetService(typeof(IEdmModel)) as IEdmModel; Contract.Assert(serviceRoot != null); serviceRootUri = new Uri( serviceRoot.EndsWith("/", StringComparison.Ordinal) ? serviceRoot : serviceRoot + "/"); // Concatenate the root and path and create a Uri. Using Uri to build a Uri from // a root and relative path changes the casing on .NetCore. However, odataPath may // be a full Uri. if (!Uri.TryCreate(odataPath, UriKind.Absolute, out fullUri)) { fullUri = new Uri(serviceRootUri + odataPath); } serviceRootUri = new Uri(serviceRootUri, dataSourceName); uriParser = new ODL.ODataUriParser(model, serviceRootUri, fullUri, requestContainer); if (UrlKeyDelimiter != null) { uriParser.UrlKeyDelimiter = UrlKeyDelimiter; } else { // ODL changes to use ODataUrlKeyDelimiter.Slash as default value. // Web API still uses the ODataUrlKeyDelimiter.Parentheses as default value. // Please remove it after fix: https://github.com/OData/odata.net/issues/642 uriParser.UrlKeyDelimiter = ODataUrlKeyDelimiter.Parentheses; } ODL.ODataPath path; UnresolvedPathSegment unresolvedPathSegment = null; ODL.KeySegment id = null; try { path = uriParser.ParsePath(); } catch (ODL.ODataUnrecognizedPathException ex) { if (ex.ParsedSegments != null && ex.ParsedSegments.Any() && (ex.ParsedSegments.Last().EdmType is IEdmComplexType || ex.ParsedSegments.Last().EdmType is IEdmEntityType) && ex.CurrentSegment != ODataSegmentKinds.Count) { if (!ex.UnparsedSegments.Any()) { path = new ODL.ODataPath(ex.ParsedSegments); unresolvedPathSegment = new UnresolvedPathSegment(ex.CurrentSegment); } else { // Throw ODataException if there is some segment following the unresolved segment. throw new ODataException(String.Format(CultureInfo.CurrentCulture, "InvalidPathSegment", ex.UnparsedSegments.First(), ex.CurrentSegment)); } } else { throw; } } if (path.LastSegment is ODL.NavigationPropertyLinkSegment) { IEdmCollectionType lastSegmentEdmType = path.LastSegment.EdmType as IEdmCollectionType; if (lastSegmentEdmType != null) { ODL.EntityIdSegment entityIdSegment = null; bool exceptionThrown = false; try { entityIdSegment = uriParser.ParseEntityId(); if (entityIdSegment != null) { // Create another ODataUriParser to parse $id, which is absolute or relative. ODL.ODataUriParser parser = new ODL.ODataUriParser(model, serviceRootUri, entityIdSegment.Id, requestContainer); id = parser.ParsePath().LastSegment as ODL.KeySegment; } } catch (ODataException) { // Exception was thrown while parsing the $id. // We will throw another exception about the invalid $id. exceptionThrown = true; } if (exceptionThrown || (entityIdSegment != null && (id == null || !(id.EdmType.IsOrInheritsFrom(lastSegmentEdmType.ElementType.Definition) || lastSegmentEdmType.ElementType.Definition.IsOrInheritsFrom(id.EdmType))))) { // System.Net.Http on NetCore does not have the Uri extension method // ParseQueryString(), to avoid a platform-specific call, extract $id manually. string idValue = fullUri.Query; string idParam = "$id="; int start = idValue.IndexOf(idParam, StringComparison.OrdinalIgnoreCase); if (start >= 0) { int end = idValue.IndexOf("&", start, StringComparison.OrdinalIgnoreCase); if (end >= 0) { idValue = idValue.Substring(start + idParam.Length, end - 1); } else { idValue = idValue.Substring(start + idParam.Length); } } throw new ODataException(String.Format(CultureInfo.CurrentCulture, "InvalidDollarId", idValue)); } } } // do validation for the odata path path.WalkWith(new DefaultODataPathValidator(model)); // do segment translator (for example parameter alias, key & function parameter template, etc) var segments = ODataPathSegmentTranslator.Translate(model, path, uriParser.ParameterAliasNodes).ToList(); if (unresolvedPathSegment != null) { segments.Add(unresolvedPathSegment); } AppendIdForRef(segments, id); return(new ODataPath(segments)); }