136 lines
4.6 KiB
C#
136 lines
4.6 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
namespace Microsoft.AspNetCore.Routing.Matching
|
|
{
|
|
internal class DefaultEndpointSelector : EndpointSelector
|
|
{
|
|
public override Task SelectAsync(
|
|
HttpContext httpContext,
|
|
EndpointSelectorContext context,
|
|
CandidateSet candidateSet)
|
|
{
|
|
if (httpContext == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(httpContext));
|
|
}
|
|
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (candidateSet == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(candidateSet));
|
|
}
|
|
|
|
// Fast path: We can specialize for trivial numbers of candidates since there can
|
|
// be no ambiguities
|
|
switch (candidateSet.Count)
|
|
{
|
|
case 0:
|
|
{
|
|
// Do nothing
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
if (candidateSet.IsValidCandidate(0))
|
|
{
|
|
ref var state = ref candidateSet[0];
|
|
context.Endpoint = state.Endpoint;
|
|
context.RouteValues = state.Values;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// Slow path: There's more than one candidate (to say nothing of validity) so we
|
|
// have to process for ambiguities.
|
|
ProcessFinalCandidates(httpContext, context, candidateSet);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private static void ProcessFinalCandidates(
|
|
HttpContext httpContext,
|
|
EndpointSelectorContext context,
|
|
CandidateSet candidateSet)
|
|
{
|
|
Endpoint endpoint = null;
|
|
RouteValueDictionary values = null;
|
|
int? foundScore = null;
|
|
for (var i = 0; i < candidateSet.Count; i++)
|
|
{
|
|
ref var state = ref candidateSet[i];
|
|
var isValid = candidateSet.IsValidCandidate(i);
|
|
if (isValid && foundScore == null)
|
|
{
|
|
// This is the first match we've seen - speculatively assign it.
|
|
endpoint = state.Endpoint;
|
|
values = state.Values;
|
|
foundScore = state.Score;
|
|
}
|
|
else if (isValid && foundScore < state.Score)
|
|
{
|
|
// This candidate is lower priority than the one we've seen
|
|
// so far, we can stop.
|
|
//
|
|
// Don't worry about the 'null < state.Score' case, it returns false.
|
|
break;
|
|
}
|
|
else if (isValid && foundScore == state.Score)
|
|
{
|
|
// This is the second match we've found of the same score, so there
|
|
// must be an ambiguity.
|
|
//
|
|
// Don't worry about the 'null == state.Score' case, it returns false.
|
|
|
|
ReportAmbiguity(candidateSet);
|
|
|
|
// Unreachable, ReportAmbiguity always throws.
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
if (endpoint != null)
|
|
{
|
|
context.Endpoint = endpoint;
|
|
context.RouteValues = values;
|
|
}
|
|
}
|
|
|
|
private static void ReportAmbiguity(CandidateSet candidates)
|
|
{
|
|
// If we get here it's the result of an ambiguity - we're OK with this
|
|
// being a littler slower and more allocatey.
|
|
var matches = new List<Endpoint>();
|
|
for (var i = 0; i < candidates.Count; i++)
|
|
{
|
|
if (candidates.IsValidCandidate(i))
|
|
{
|
|
matches.Add(candidates[i].Endpoint);
|
|
}
|
|
}
|
|
|
|
var message = Resources.FormatAmbiguousEndpoints(
|
|
Environment.NewLine,
|
|
string.Join(Environment.NewLine, matches.Select(e => e.DisplayName)));
|
|
throw new AmbiguousMatchException(message);
|
|
}
|
|
}
|
|
}
|