/// <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)); }
/// <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); }
/// <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)); }