/// <summary>
        /// Selects the best action given the provided route context and list of candidate actions.
        /// </summary>
        /// <param name="context">The current <see cref="RouteContext">route context</see> to evaluate.</param>
        /// <param name="candidates">The <see cref="IReadOnlyList{T}">read-only list</see> of candidate <see cref="ActionDescriptor">actions</see> to select from.</param>
        /// <returns>The best candidate <see cref="ActionDescriptor">action</see> or <c>null</c> if no candidate matches.</returns>
        public override ActionDescriptor?SelectBestCandidate(RouteContext context, IReadOnlyList <ActionDescriptor> candidates)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (candidates == null)
            {
                throw new ArgumentNullException(nameof(candidates));
            }

            var httpContext         = context.HttpContext;
            var odataRouteCandidate = httpContext.ODataFeature().Path != null;

            if (!odataRouteCandidate)
            {
                return(base.SelectBestCandidate(context, candidates));
            }

            if (IsRequestedApiVersionAmbiguous(context, out var apiVersion))
            {
                return(null);
            }

            var matches          = EvaluateActionConstraints(context, candidates);
            var selectionContext = new ActionSelectionContext(httpContext, matches, apiVersion);
            var bestActions      = SelectBestActions(selectionContext);
            var finalMatches     = bestActions.Select(action => new ActionCandidate(action))
                                   .OrderByDescending(candidate => candidate.FilteredParameters.Count)
                                   .ThenByDescending(candidate => candidate.TotalParameterCount)
                                   .Take(1)
                                   .Select(candidate => candidate.Action)
                                   .ToArray();
            var feature         = httpContext.Features.Get <IApiVersioningFeature>();
            var selectionResult = feature.SelectionResult;

            feature.RequestedApiVersion = selectionContext.RequestedVersion;
            selectionResult.AddCandidates(candidates);

            if (finalMatches.Length == 0)
            {
                return(null);
            }

            selectionResult.AddMatches(finalMatches);

            return(RoutePolicy.Evaluate(context, selectionResult));
        }
示例#2
0
        /// <summary>
        /// Selects the best action given the provided route context and list of candidate actions.
        /// </summary>
        /// <param name="context">The current <see cref="RouteContext">route context</see> to evaluate.</param>
        /// <param name="candidates">The <see cref="IReadOnlyList{T}">read-only list</see> of candidate <see cref="ActionDescriptor">actions</see> to select from.</param>
        /// <returns>The best candidate <see cref="ActionDescriptor">action</see> or <c>null</c> if no candidate matches.</returns>
        public override ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList <ActionDescriptor> candidates)
        {
            Arg.NotNull(context, nameof(context));
            Arg.NotNull(candidates, nameof(candidates));

            var httpContext         = context.HttpContext;
            var odataRouteCandidate = httpContext.ODataFeature().Path != null;

            if (!odataRouteCandidate)
            {
                return(base.SelectBestCandidate(context, candidates));
            }

            if (IsRequestedApiVersionAmbiguous(context, out var apiVersion))
            {
                return(null);
            }

            var matches          = EvaluateActionConstraints(context, candidates);
            var selectionContext = new ActionSelectionContext(httpContext, matches, apiVersion);
            var bestActions      = SelectBestActions(selectionContext);
            var finalMatch       = bestActions.Select(action => new ActionCandidate(action))
                                   .OrderByDescending(candidate => candidate.FilteredParameters.Count)
                                   .ThenByDescending(candidate => candidate.TotalParameterCount)
                                   .FirstOrDefault()?.Action;
            IReadOnlyList <ActionDescriptor> finalMatches = finalMatch == null?Array.Empty <ActionDescriptor>() : new[] { finalMatch };
            var feature         = httpContext.Features.Get <IApiVersioningFeature>();
            var selectionResult = feature.SelectionResult;

            feature.RequestedApiVersion = selectionContext.RequestedVersion;
            selectionResult.AddCandidates(candidates);

            if (finalMatches.Count == 0)
            {
                return(null);
            }

            selectionResult.AddMatches(finalMatches);

            var bestCandidate = RoutePolicy.Evaluate(context, selectionResult);

            if (bestCandidate is ControllerActionDescriptor controllerAction)
            {
                context.RouteData.Values[ActionKey] = controllerAction.ActionName;
            }

            return(bestCandidate);
        }
示例#3
0
        /// <inheritdoc />
        public override ActionDescriptor?SelectBestCandidate(RouteContext context, IReadOnlyList <ActionDescriptor> candidates)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (candidates == null)
            {
                throw new ArgumentNullException(nameof(candidates));
            }

            var httpContext         = context.HttpContext;
            var odata               = httpContext.ODataFeature();
            var odataRouteCandidate = odata.Path != null;

            if (!odataRouteCandidate)
            {
                return(base.SelectBestCandidate(context, candidates));
            }

            if (IsRequestedApiVersionAmbiguous(context, out var apiVersion))
            {
                return(null);
            }

            var matches          = EvaluateActionConstraints(context, candidates);
            var selectionContext = new ActionSelectionContext(httpContext, matches, apiVersion);
            var bestActions      = SelectBestActions(selectionContext);
            var finalMatches     = Array.Empty <ActionDescriptor>();

            if (bestActions.Count > 0)
            {
                // REF: https://github.com/OData/WebApi/blob/master/src/Microsoft.AspNetCore.OData/Routing/ODataActionSelector.cs
                var routeValues        = context.RouteData.Values;
                var conventionsStore   = odata.RoutingConventionsStore ?? new Dictionary <string, object>(capacity: 0);
                var availableKeys      = new HashSet <string>(routeValues.Keys.Where(IsAvailableKey), StringComparer.OrdinalIgnoreCase);
                var availableKeysCount = conventionsStore.TryGetValue(ODataRouteConstants.KeyCount, out var v) ? (int)v : 0;
                var possibleCandidates = bestActions.Select(candidate => new ActionIdAndParameters(candidate, ParameterHasRegisteredModelBinder));
                var optionalParameters = routeValues.TryGetValue(ODataRouteConstants.OptionalParameters, out var wrapper) ?
                                         GetOptionalParameters(wrapper) :
                                         Array.Empty <IEdmOptionalParameter>();
                var matchedCandidates = possibleCandidates
                                        .Where(c => TryMatch(httpContext, c, availableKeys, conventionsStore, optionalParameters, availableKeysCount))
                                        .OrderByDescending(c => c.FilteredParameters.Count)
                                        .ThenByDescending(c => c.TotalParameterCount)
                                        .ToArray();

                if (matchedCandidates.Length == 1)
                {
                    finalMatches = new[] { matchedCandidates[0].Action };
                }
                else if (matchedCandidates.Length > 1)
                {
                    var results = matchedCandidates.Where(c => ActionAcceptsMethod(c.Action, httpContext.Request.Method)).ToArray();

                    finalMatches = results.Length switch
                    {
                        0 => matchedCandidates.Where(c => c.FilteredParameters.Count == availableKeysCount).Select(c => c.Action).ToArray(),
                        1 => new[] { results[0].Action },
                        _ => results.Where(c => c.FilteredParameters.Count == availableKeysCount).Select(c => c.Action).ToArray(),
                    };
                }
            }

            var feature         = httpContext.Features.Get <IApiVersioningFeature>();
            var selectionResult = feature.SelectionResult;

            feature.RequestedApiVersion = selectionContext.RequestedVersion;
            selectionResult.AddCandidates(candidates);

            if (finalMatches.Length > 0)
            {
                selectionResult.AddMatches(finalMatches);
            }
            else
            {
                // OData endpoint routing calls back through IActionSelector. if endpoint routing is enabled
                // then the answer is final; proceed to route policy. if classic routing, it's possible the
                // IActionSelector will be entered again
                if (!UsingEndpointRouting)
                {
                    return(null);
                }
            }

            return(RoutePolicy.Evaluate(context, selectionResult));
        }