/// <summary> /// Initializes a new instance of the <see cref="PathTemplateSegmentTemplate"/> class. /// </summary> /// <param name="segment">The path template segment to be parsed as a template.</param> public PathTemplateSegmentTemplate(PathTemplateSegment segment) { if (segment == null) { throw Error.ArgumentNull("segment"); } TemplateSegment = segment; string value; SegmentName = segment.TranslatePathTemplateSegment(out value); PropertyName = value; TreatPropertyNameAsParameterName = false; if (RoutingConventionHelpers.IsRouteParameter(PropertyName)) { PropertyName = PropertyName.Substring(1, PropertyName.Length - 2); TreatPropertyNameAsParameterName = true; if (String.IsNullOrEmpty(PropertyName)) { Error.Format(SRResources.EmptyParameterAlias, PropertyName, segment.LiteralText); } } }
/// <inheritdoc/> public override bool TryMatch(ODataPathSegment pathSegment, IDictionary <string, object> values) { OperationImportSegment other = pathSegment as OperationImportSegment; if (other == null) { return(false); } IEdmOperationImport operationImport = Segment.OperationImports.First(); IEdmOperationImport otherImport = other.OperationImports.First(); // for unbound action, just compare the action import if (operationImport.IsActionImport() && otherImport.IsActionImport()) { return(operationImport == otherImport); } else if (operationImport.IsFunctionImport() && otherImport.IsFunctionImport()) { // but for unbound function, we should compare the parameter names and // process the parameter values into odata routes. if (operationImport.Name != otherImport.Name) { return(false); } IDictionary <string, object> parameterValues = new Dictionary <string, object>(); foreach (var parameter in other.Parameters) { object value = other.GetParameterValue(parameter.Name); parameterValues[parameter.Name] = value; } if (RoutingConventionHelpers.TryMatch(ParameterMappings, parameterValues, values)) { foreach (var operationSegmentParameter in other.Parameters) { string name = operationSegmentParameter.Name; object value = parameterValues[name]; RoutingConventionHelpers.AddFunctionParameters((IEdmFunction)otherImport.Operation, name, value, values, values, ParameterMappings); } return(true); } } return(false); }
/// <summary> /// Initializes a new instance of the <see cref="OperationSegmentTemplate"/> class. /// </summary> /// <param name="segment">The operation segment</param> public OperationSegmentTemplate(OperationSegment segment) { if (segment == null) { throw Error.ArgumentNull("segment"); } Segment = segment; IEdmOperation operation = Segment.Operations.First(); if (operation.IsFunction()) { ParameterMappings = RoutingConventionHelpers.BuildParameterMappings(segment.Parameters, operation.FullName()); } }
/// <inheritdoc/> public override bool TryMatch(ODataPathSegment pathSegment, IDictionary <string, object> values) { OperationSegment other = pathSegment as OperationSegment; if (other == null) { return(false); } IEdmOperation operation = Segment.Operations.First(); IEdmOperation otherOperation = other.Operations.First(); if (operation.IsAction() && otherOperation.IsAction()) { return(operation == otherOperation); } else if (operation.IsFunction() && otherOperation.IsFunction()) { if (operation.FullName() != otherOperation.FullName()) { return(false); } IDictionary <string, object> parameterValues = new Dictionary <string, object>(); foreach (var parameter in other.Parameters) { object value = other.GetParameterValue(parameter.Name); parameterValues[parameter.Name] = value; } if (RoutingConventionHelpers.TryMatch(ParameterMappings, parameterValues, values)) { foreach (var operationSegmentParameter in other.Parameters) { string name = operationSegmentParameter.Name; object value = parameterValues[name]; RoutingConventionHelpers.AddFunctionParameters((IEdmFunction)otherOperation, name, value, values, values, ParameterMappings); } return(true); } } return(false); }
internal static IDictionary <string, string> BuildKeyMappings(IEnumerable <KeyValuePair <string, object> > keys) { Contract.Assert(keys != null); Dictionary <string, string> parameterMappings = new Dictionary <string, string>(); foreach (KeyValuePair <string, object> key in keys) { string nameInRouteData; UriTemplateExpression uriTemplateExpression = key.Value as UriTemplateExpression; if (uriTemplateExpression != null) { nameInRouteData = uriTemplateExpression.LiteralText.Trim(); } else { // just for easy construct the key segment template // it must start with "{" and end with "}" nameInRouteData = key.Value as string; } if (nameInRouteData == null || !RoutingConventionHelpers.IsRouteParameter(nameInRouteData)) { throw new ODataException( Error.Format(SRResources.KeyTemplateMustBeInCurlyBraces, key.Value, key.Key)); } nameInRouteData = nameInRouteData.Substring(1, nameInRouteData.Length - 2); if (String.IsNullOrEmpty(nameInRouteData)) { throw new ODataException( Error.Format(SRResources.EmptyKeyTemplate, key.Value, key.Key)); } parameterMappings[key.Key] = nameInRouteData; } return(parameterMappings); }
/// <summary> /// Initializes a new instance of the <see cref="DynamicSegmentTemplate"/> class. /// </summary> /// <param name="segment">The open property segment</param> public DynamicSegmentTemplate(DynamicPathSegment segment) { if (segment == null) { throw Error.ArgumentNull("segment"); } Segment = segment; PropertyName = segment.Identifier; TreatPropertyNameAsParameterName = false; if (RoutingConventionHelpers.IsRouteParameter(PropertyName)) { PropertyName = PropertyName.Substring(1, PropertyName.Length - 2); TreatPropertyNameAsParameterName = true; if (String.IsNullOrEmpty(PropertyName)) { throw new ODataException( Error.Format(SRResources.EmptyParameterAlias, PropertyName, segment.Identifier)); } } }
/// <inheritdoc /> public ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList <ActionDescriptor> candidates) { RouteData routeData = context.RouteData; ODataPath odataPath = context.HttpContext.ODataFeature().Path; IDictionary <string, object> routingConventionsStore = context.HttpContext.ODataFeature().RoutingConventionsStore; if (odataPath != null && routeData.Values.ContainsKey(ODataRouteConstants.Action)) { // Get the available parameter names from the route data. Ignore case of key names. // Remove route prefix and other non-parameter values from availableKeys IList <string> availableKeys = routeData.Values.Keys .Where((key) => !RoutingConventionHelpers.IsRouteParameter(key) && key != ODataRouteConstants.Action && key != ODataRouteConstants.ODataPath && key != ODataRouteConstants.MethodInfo) .Select(k => k.ToLowerInvariant()) .ToList(); int availableKeysCount = 0; if (routingConventionsStore.ContainsKey(ODataRouteConstants.KeyCount)) { availableKeysCount = (int)routingConventionsStore[ODataRouteConstants.KeyCount]; } // Filter out types we know how to bind out of the parameter lists. These values // do not show up in RouteData() but will bind properly later. IEnumerable <ActionIdAndParameters> considerCandidates = candidates .Select(c => new ActionIdAndParameters( id: c.Id, parameterCount: c.Parameters.Count, filteredParameters: c.Parameters.Where(p => p.ParameterType != typeof(ODataPath) && !ODataQueryParameterBindingAttribute.ODataQueryParameterBinding.IsODataQueryOptions(p.ParameterType) && !IsParameterFromQuery(c as ControllerActionDescriptor, p.Name)), descriptor: c)); // retrieve the optional parameters routeData.Values.TryGetValue(ODataRouteConstants.OptionalParameters, out object wrapper); ODataOptionalParameter optionalWrapper = wrapper as ODataOptionalParameter; // Find the action with the all matched parameters from available keys including // matches with no parameters and matches with parameters bound to the body. // Ordered first by the total number of matched // parameters followed by the total number of parameters. Ignore case of // parameter names. The first one is the best match. // // Assume key,relatedKey exist in RouteData. 1st one wins: // Method(ODataPath,ODataQueryOptions) vs Method(ODataPath). // Method(key,ODataQueryOptions) vs Method(key). // Method(key,ODataQueryOptions) vs Method(key). // Method(key,relatedKey) vs Method(key). // Method(key,relatedKey,ODataPath) vs Method(key,relatedKey). List <ActionIdAndParameters> matchedCandidates = considerCandidates .Where(c => TryMatch(context, c.ActionDescriptor, c.FilteredParameters, availableKeys, optionalWrapper, c.TotalParameterCount, availableKeysCount)) .OrderByDescending(c => c.FilteredParameters.Count) .ThenByDescending(c => c.TotalParameterCount) .ToList(); // if there are still multiple candidate actions at this point, let's try some tie-breakers if (matchedCandidates.Count > 1) { // prioritize actions with explicit [ODataRoute] (i.e. attribute routing) ActionIdAndParameters bestCandidate = matchedCandidates.FirstOrDefault(candidate => candidate.ActionDescriptor is ControllerActionDescriptor action && action.MethodInfo.GetCustomAttributes <ODataRouteAttribute>().Any()); if (bestCandidate != null) { return(bestCandidate.ActionDescriptor); } // Next in priority, actions which explicitly declare the request method // e.g. using [AcceptVerbs("POST")], [HttpPost], etc. bestCandidate = matchedCandidates.FirstOrDefault(candidate => ActionAcceptsMethod(candidate.ActionDescriptor as ControllerActionDescriptor, context.HttpContext.Request.Method)); if (bestCandidate != null) { return(bestCandidate.ActionDescriptor); } // also priorize actions that have the exact number of parameters as available keys // this helps disambiguate between overloads of actions that implement actions // e.g. DoSomething(int key) vs DoSomething(), if there are no availableKeys, the // selector could still think that the `int key` param will come from the request body // and end up returning DoSomething(int key) instead of DoSomething() bestCandidate = matchedCandidates.FirstOrDefault(candidate => candidate.FilteredParameters.Count() == availableKeysCount); if (bestCandidate != null) { return(bestCandidate.ActionDescriptor); } } return(matchedCandidates.Select(c => c.ActionDescriptor).FirstOrDefault()); } return(_innerSelector.SelectBestCandidate(context, candidates)); }