/// <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);
                }
            }
        }
Example #2
0
        /// <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);
        }
Example #5
0
        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);
        }
Example #6
0
        /// <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));
                }
            }
        }
Example #7
0
        /// <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));
        }