/// <summary> /// Converts an instance of <see cref="ODataPath" /> into an OData link. /// </summary> /// <param name="path">The OData path to convert into a link.</param> /// <returns> /// The generated OData link. /// </returns> public virtual string Link(ODataPath path) { if (path == null) { throw Error.ArgumentNull("path"); } return path.ToString(); }
/// <summary> /// Matches the current template with an OData path. /// </summary> /// <param name="path">The OData path to be matched against.</param> /// <param name="values">The dictionary of matches to be updated in case of a match.</param> /// <returns><c>true</c> in case of a match; otherwise, <c>false</c>.</returns> public bool TryMatch(ODataPath path, IDictionary<string, object> values) { if (path.Segments.Count != Segments.Count) { return false; } for (int index = 0; index < Segments.Count; index++) { if (!Segments[index].TryMatch(path.Segments[index], values)) { return false; } } return true; }
private static ODataPathTemplate Templatify(ODataPath path, string pathTemplate) { if (path == null) { throw new ODataException(Error.Format(SRResources.InvalidODataPathTemplate, pathTemplate)); } List<ODataPathSegmentTemplate> templateSegments = new List<ODataPathSegmentTemplate>(); foreach (ODataPathSegment pathSegment in path.Segments) { switch (pathSegment.SegmentKind) { case ODataSegmentKinds._Unresolved: throw new ODataException( Error.Format(SRResources.UnresolvedPathSegmentInTemplate, pathSegment.ToString(), pathTemplate)); case ODataSegmentKinds._Key: templateSegments.Add(new KeyValuePathSegmentTemplate((KeyValuePathSegment)pathSegment)); break; case ODataSegmentKinds._Function: templateSegments.Add(new BoundFunctionPathSegmentTemplate((BoundFunctionPathSegment)pathSegment)); break; case ODataSegmentKinds._UnboundFunction: templateSegments.Add(new UnboundFunctionPathSegmentTemplate((UnboundFunctionPathSegment)pathSegment)); break; case ODataSegmentKinds._DynamicProperty: templateSegments.Add(new DynamicPropertyPathSegmentTemplate((DynamicPropertyPathSegment)pathSegment)); break; default: templateSegments.Add(pathSegment); break; } } return new ODataPathTemplate(templateSegments); }
private static void CheckNavigableProperty(ODataPath path, IEdmModel model) { Contract.Assert(path != null); Contract.Assert(model != null); foreach (ODataPathSegment segment in path.Segments) { NavigationPathSegment navigationPathSegment = segment as NavigationPathSegment; if (navigationPathSegment != null) { if (EdmLibHelpers.IsNotNavigable(navigationPathSegment.NavigationProperty, model)) { throw new ODataException(Error.Format( SRResources.NotNavigablePropertyUsedInNavigation, navigationPathSegment.NavigationProperty.Name)); } } } }
/// <summary> /// Creates a set of transformed route values that will be used to select an action. /// </summary> /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param> /// <param name="values">The route values associated with the current match.</param> /// <returns>A task which asynchronously returns a set of route values.</returns> public override ValueTask <RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (values == null) { throw new ArgumentNullException(nameof(values)); } if (httpContext.ODataFeature().Path != null) { // Noted: if there's a route mapping with ODataPrefix == null, // for example: router.MapODataRoute(routeName: "odata", routePrefix: null, ...) // This route will match all requests. // Therefore, this route will be a candidate and its tranformer will be called. // So, we use the ODataPath setting to verify whether the request is transformed or not. // Maybe we have a better solution later. return(new ValueTask <RouteValueDictionary>(result: null)); } (string routeName, object oDataPathValue) = values.GetODataRouteInfo(); if (routeName != null) { HttpRequest request = httpContext.Request; // We need to call Uri.GetLeftPart(), which returns an encoded Url. // The ODL parser does not like raw values. Uri requestUri = new Uri(request.GetEncodedUrl()); string requestLeftPart = requestUri.GetLeftPart(UriPartial.Path); string queryString = request.QueryString.HasValue ? request.QueryString.ToString() : null; // Call ODL to parse the Request URI. // ODataPath path = ODataPathRouteConstraint.GetODataPath(oDataPathValue as string, requestLeftPart, queryString, () => request.CreateRequestContainer(routeName)); ODataPath path = new ODataPath(); if (path != null) { // Set all the properties we need for routing, querying, formatting IODataFeature odataFeature = httpContext.ODataFeature(); odataFeature.Path = path; odataFeature.RouteName = routeName; odataFeature.IsEndpointRouting = true; // mark as Endpoint routing // Noted: we inject the ActionSelector and use it to select the best OData action. // In .NET 5 or later, this maybe change. RouteContext routeContext = new RouteContext(httpContext); var condidates = _selector.SelectCandidates(routeContext); var actionDescriptor = _selector.SelectBestCandidate(routeContext, condidates); ControllerActionDescriptor controllerActionDescriptor = actionDescriptor as ControllerActionDescriptor; if (controllerActionDescriptor != null) { RouteValueDictionary newValues = new RouteValueDictionary(); foreach (var item in values) { newValues.Add(item.Key, item.Value); } foreach (var item in routeContext.RouteData.Values) { newValues[item.Key] = item.Value; } newValues["controller"] = controllerActionDescriptor.ControllerName; newValues["action"] = controllerActionDescriptor.ActionName; newValues["odataPath"] = oDataPathValue; // Noted, here's a working around for mulitiple actions in same controller. // For example, we have two "Get" methods in same controller, in order to help "EndpointSelector" // to select the correct Endpoint, we save the ActionDescriptor value into ODataFeature. odataFeature.ActionDescriptor = controllerActionDescriptor; return(new ValueTask <RouteValueDictionary>(newValues)); } } else { // The request doesn't match this route so dispose the request container. request.DeleteRequestContainer(true); } } return(new ValueTask <RouteValueDictionary>(result: null)); }
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { if (httpContext == null) { throw Error.ArgumentNull(nameof(httpContext)); } IODataFeature odataFeature = httpContext.ODataFeature(); if (odataFeature.Path != null) { // If we have the OData path setting, it means there's some Policy working. // Let's skip this default OData matcher policy. return(Task.CompletedTask); } // The goal of this method is to perform the final matching: // Map between route values matched by the template and the ones we want to expose to the action for binding. // (tweaking the route values is fine here) // Invalidating the candidate if the key/function values are not valid/missing. // Perform overload resolution for functions by looking at the candidates and their metadata. for (var i = 0; i < candidates.Count; i++) { ref CandidateState candidate = ref candidates[i]; if (!candidates.IsValidCandidate(i)) { continue; } IODataRoutingMetadata metadata = candidate.Endpoint.Metadata.OfType <IODataRoutingMetadata>().FirstOrDefault(); if (metadata == null) { continue; } ODataTemplateTranslateContext translatorContext = new ODataTemplateTranslateContext(httpContext, candidate.Values, metadata.Model); try { ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext); if (odataPath != null) { odataFeature.PrefixName = metadata.Prefix; odataFeature.Model = metadata.Model; odataFeature.Path = odataPath; MergeRouteValues(translatorContext.UpdatedValues, candidate.Values); // Shall we break the remaining candidates? // So far the answer is no. Because we can use this matcher to obsolete the unmatched endpoint. // break; } else { candidates.SetValidity(i, false); } } catch (Exception) { } }
private static ODataPath Parse( IEdmModel model, string serviceRoot, string odataPath, ODataUriResolverSetttings resolverSettings, bool enableUriTemplateParsing) { ODataUriParser uriParser; Uri serviceRootUri = null; Uri fullUri = null; // TODO: Replace this type. //NameValueCollection queryString = null; if (enableUriTemplateParsing) { uriParser = new ODataUriParser(model, new Uri(odataPath, UriKind.Relative)); 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 ODataUriParser(model, serviceRootUri, fullUri); } uriParser.Resolver = resolverSettings.CreateResolver(); Semantic.ODataPath path; UnresolvedPathSegment unresolvedPathSegment = null; Semantic.KeySegment id = null; try { path = uriParser.ParsePath(); } catch (ODataUnrecognizedPathException ex) { if (ex.ParsedSegments != null && ex.ParsedSegments.Count() > 0 && (ex.ParsedSegments.Last().EdmType is IEdmComplexType || ex.ParsedSegments.Last().EdmType is IEdmEntityType) && ex.CurrentSegment != ODataSegmentKinds.Count) { if (ex.UnparsedSegments.Count() == 0) { path = new Semantic.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 (!enableUriTemplateParsing && path.LastSegment is Semantic.NavigationPropertyLinkSegment) { IEdmCollectionType lastSegmentEdmType = path.LastSegment.EdmType as IEdmCollectionType; if (lastSegmentEdmType != null) { Semantic.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); id = parser.ParsePath().LastSegment as Semantic.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, /*queryString.Get("$id")*/ "$id")); } } } ODataPath webAPIPath = ODataPathSegmentTranslator.TranslateODLPathToWebAPIPath( path, model, unresolvedPathSegment, id, enableUriTemplateParsing, uriParser.ParameterAliasNodes); CheckNavigableProperty(webAPIPath, model); return(webAPIPath); }
private static string GetRootElementName(ODataPath path) { if (path != null) { ODataPathSegment lastSegment = path.Segments.LastOrDefault(); if (lastSegment != null) { BoundActionPathSegment actionSegment = lastSegment as BoundActionPathSegment; if (actionSegment != null) { return actionSegment.Action.Name; } PropertyAccessPathSegment propertyAccessSegment = lastSegment as PropertyAccessPathSegment; if (propertyAccessSegment != null) { return propertyAccessSegment.Property.Name; } } } return null; }
private static bool IsOperationPath(ODataPath path) { if (path == null) { return false; } foreach (ODataPathSegment segment in path.Segments) { switch (segment.SegmentKind) { case ODataSegmentKinds._Action: case ODataSegmentKinds._Function: case ODataSegmentKinds._UnboundAction: case ODataSegmentKinds._UnboundFunction: return true; } } return false; }
/// <summary> /// Gets the property and structured type from <see cref="ODataPath"/>. /// TODO: The logic implenetation is not good and do need refactor it later. /// </summary> /// <param name="path">The OData path.</param> /// <returns>The property, structured type and the name.</returns> internal static (IEdmProperty, IEdmStructuredType, string) GetPropertyAndStructuredTypeFromPath(this ODataPath path) { if (path == null) { return(null, null, string.Empty); } IEdmStructuredType structuredType = null; string typeCast = string.Empty; IEnumerable <ODataPathSegment> reverseSegments = path.Reverse(); foreach (var segment in reverseSegments) { if (segment is NavigationPropertySegment navigationPathSegment) { IEdmProperty property = navigationPathSegment.NavigationProperty; if (structuredType == null) { structuredType = navigationPathSegment.NavigationProperty.ToEntityType(); } string name = navigationPathSegment.NavigationProperty.Name + typeCast; return(property, structuredType, name); } if (segment is OperationSegment operationSegment) { if (structuredType == null) { structuredType = operationSegment.EdmType as IEdmStructuredType; } string name = operationSegment.Operations.First().FullName() + typeCast; return(null, structuredType, name); } if (segment is PropertySegment propertyAccessPathSegment) { IEdmProperty property = propertyAccessPathSegment.Property; if (structuredType == null) { structuredType = property.Type.GetElementType() as IEdmStructuredType; } string name = property.Name + typeCast; return(property, structuredType, name); } if (segment is EntitySetSegment entitySetSegment) { if (structuredType == null) { structuredType = entitySetSegment.EntitySet.EntityType(); } string name = entitySetSegment.EntitySet.Name + typeCast; return(null, structuredType, name); } if (segment is SingletonSegment singletonSegment) { if (structuredType == null) { structuredType = singletonSegment.Singleton.EntityType(); } string name = singletonSegment.Singleton.Name + typeCast; return(null, structuredType, name); } if (segment is TypeSegment typeSegment) { structuredType = typeSegment.EdmType.AsElementType() as IEdmStructuredType; typeCast = "/" + structuredType; } else if (segment is KeySegment || segment is CountSegment) { // do nothing, just go to next segment, what about if meet OperationSegment? } else { // if we meet any other segments, just return (null, null, string.Empty); break; } } return(null, null, string.Empty); }