Use RoutePatternMatcher logic in TemplateMatcher (#484)
This commit is contained in:
parent
485278bf0d
commit
eebc7db2ca
|
|
@ -209,10 +209,23 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
if (pathSegment.IsSimple && !pathSegment.Parts[0].IsParameter)
|
if (pathSegment.IsSimple && !pathSegment.Parts[0].IsParameter)
|
||||||
{
|
{
|
||||||
// This is a literal segment, so we need to match the text, or the route isn't a match.
|
// This is a literal segment, so we need to match the text, or the route isn't a match.
|
||||||
var part = pathSegment.Parts[0];
|
if (pathSegment.Parts[0].IsLiteral)
|
||||||
if (!stringSegment.Equals(part.RawText, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return false;
|
var part = (RoutePatternLiteral)pathSegment.Parts[0];
|
||||||
|
|
||||||
|
if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var part = (RoutePatternSeparator)pathSegment.Parts[0];
|
||||||
|
|
||||||
|
if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
|
else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
|
||||||
|
|
@ -301,12 +314,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var separator = (RoutePatternSeparator)routeSegment.Parts[indexOfLastSegment - 1];
|
||||||
if (requestSegment.EndsWith(
|
if (requestSegment.EndsWith(
|
||||||
routeSegment.Parts[indexOfLastSegment - 1].RawText,
|
separator.Content,
|
||||||
StringComparison.OrdinalIgnoreCase))
|
StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return MatchComplexSegmentCore(
|
return MatchComplexSegmentCore(
|
||||||
routeSegment,
|
routeSegment,
|
||||||
|
|
@ -367,10 +379,24 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var indexOfLiteral = requestSegment.LastIndexOf(
|
int indexOfLiteral;
|
||||||
part.RawText,
|
if (part.IsLiteral)
|
||||||
|
{
|
||||||
|
var literal = (RoutePatternLiteral)part;
|
||||||
|
indexOfLiteral = requestSegment.LastIndexOf(
|
||||||
|
literal.Content,
|
||||||
startIndex,
|
startIndex,
|
||||||
StringComparison.OrdinalIgnoreCase);
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var literal = (RoutePatternSeparator)part;
|
||||||
|
indexOfLiteral = requestSegment.LastIndexOf(
|
||||||
|
literal.Content,
|
||||||
|
startIndex,
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
if (indexOfLiteral == -1)
|
if (indexOfLiteral == -1)
|
||||||
{
|
{
|
||||||
// If we couldn't find this literal index, this segment cannot match
|
// If we couldn't find this literal index, this segment cannot match
|
||||||
|
|
@ -382,7 +408,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// This check is related to the check we do at the very end of this function.
|
// This check is related to the check we do at the very end of this function.
|
||||||
if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1))
|
if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1))
|
||||||
{
|
{
|
||||||
if ((indexOfLiteral + part.RawText.Length) != requestSegment.Length)
|
if (part is RoutePatternLiteral literal && ((indexOfLiteral + literal.Content.Length) != requestSegment.Length))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (part is RoutePatternSeparator separator && ((indexOfLiteral + separator.Content.Length) != requestSegment.Length))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -422,7 +452,16 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
parameterStartIndex = newLastIndex + lastLiteral.RawText.Length;
|
if (lastLiteral.IsLiteral)
|
||||||
|
{
|
||||||
|
var literal = (RoutePatternLiteral)lastLiteral;
|
||||||
|
parameterStartIndex = newLastIndex + literal.Content.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var separator = (RoutePatternSeparator)lastLiteral;
|
||||||
|
parameterStartIndex = newLastIndex + separator.Content.Length;
|
||||||
|
}
|
||||||
parameterTextLength = lastIndex - parameterStartIndex;
|
parameterTextLength = lastIndex - parameterStartIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using Microsoft.AspNetCore.Dispatcher;
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.AspNetCore.Dispatcher.Internal;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
|
|
@ -19,6 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
private readonly object[] _defaultValues;
|
private readonly object[] _defaultValues;
|
||||||
|
|
||||||
private static readonly char[] Delimiters = new char[] { SeparatorChar };
|
private static readonly char[] Delimiters = new char[] { SeparatorChar };
|
||||||
|
private RoutePatternMatcher _routePatternMatcher;
|
||||||
|
|
||||||
public TemplateMatcher(
|
public TemplateMatcher(
|
||||||
RouteTemplate template,
|
RouteTemplate template,
|
||||||
|
|
@ -57,6 +56,9 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
_defaultValues[i] = value;
|
_defaultValues[i] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var routePattern = Template.ToRoutePattern();
|
||||||
|
_routePatternMatcher = new RoutePatternMatcher(routePattern, Defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RouteValueDictionary Defaults { get; }
|
public RouteValueDictionary Defaults { get; }
|
||||||
|
|
@ -70,387 +72,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
throw new ArgumentNullException(nameof(values));
|
throw new ArgumentNullException(nameof(values));
|
||||||
}
|
}
|
||||||
|
|
||||||
var i = 0;
|
return _routePatternMatcher.TryMatch(path, values);
|
||||||
var pathTokenizer = new PathTokenizer(path);
|
|
||||||
|
|
||||||
// Perf: We do a traversal of the request-segments + route-segments twice.
|
|
||||||
//
|
|
||||||
// For most segment-types, we only really need to any work on one of the two passes.
|
|
||||||
//
|
|
||||||
// On the first pass, we're just looking to see if there's anything that would disqualify us from matching.
|
|
||||||
// The most common case would be a literal segment that doesn't match.
|
|
||||||
//
|
|
||||||
// On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values'
|
|
||||||
// and start capturing strings.
|
|
||||||
foreach (var pathSegment in pathTokenizer)
|
|
||||||
{
|
|
||||||
if (pathSegment.Length == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var routeSegment = Template.GetSegment(i++);
|
|
||||||
if (routeSegment == null && pathSegment.Length > 0)
|
|
||||||
{
|
|
||||||
// If routeSegment is null, then we're out of route segments. All we can match is the empty
|
|
||||||
// string.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (routeSegment.IsSimple && routeSegment.Parts[0].IsLiteral)
|
|
||||||
{
|
|
||||||
// This is a literal segment, so we need to match the text, or the route isn't a match.
|
|
||||||
var part = routeSegment.Parts[0];
|
|
||||||
if (!pathSegment.Equals(part.Text, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll)
|
|
||||||
{
|
|
||||||
// Nothing to validate for a catch-all - it can match any string, including the empty string.
|
|
||||||
//
|
|
||||||
// Also, a catch-all has to be the last part, so we're done.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter)
|
|
||||||
{
|
|
||||||
// For a parameter, validate that it's a has some length, or we have a default, or it's optional.
|
|
||||||
var part = routeSegment.Parts[0];
|
|
||||||
if (pathSegment.Length == 0 &&
|
|
||||||
!_hasDefaultValue[i] &&
|
|
||||||
!part.IsOptional)
|
|
||||||
{
|
|
||||||
// There's no value for this parameter, the route can't match.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Assert(!routeSegment.IsSimple);
|
|
||||||
|
|
||||||
// Don't attempt to validate a complex segment at this point other than being non-emtpy,
|
|
||||||
// do it in the second pass.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i < Template.Segments.Count; i++)
|
|
||||||
{
|
|
||||||
// We've matched the request path so far, but still have remaining route segments. These need
|
|
||||||
// to be all single-part parameter segments with default values or else they won't match.
|
|
||||||
var routeSegment = Template.GetSegment(i);
|
|
||||||
Debug.Assert(routeSegment != null);
|
|
||||||
|
|
||||||
if (!routeSegment.IsSimple)
|
|
||||||
{
|
|
||||||
// If the segment is a complex segment, it MUST contain literals, and we've parsed the full
|
|
||||||
// path so far, so it can't match.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var part = routeSegment.Parts[0];
|
|
||||||
if (part.IsLiteral)
|
|
||||||
{
|
|
||||||
// If the segment is a simple literal - which need the URL to provide a value, so we don't match.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.IsCatchAll)
|
|
||||||
{
|
|
||||||
// Nothing to validate for a catch-all - it can match any string, including the empty string.
|
|
||||||
//
|
|
||||||
// Also, a catch-all has to be the last part, so we're done.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, this is a simple segment with a parameter. We need it to be optional, or for the
|
|
||||||
// defaults to have a value.
|
|
||||||
Debug.Assert(routeSegment.IsSimple && part.IsParameter);
|
|
||||||
if (!_hasDefaultValue[i] && !part.IsOptional)
|
|
||||||
{
|
|
||||||
// There's no default for this (non-optional) parameter so it can't match.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point we've very likely got a match, so start capturing values for real.
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
foreach (var requestSegment in pathTokenizer)
|
|
||||||
{
|
|
||||||
var routeSegment = Template.GetSegment(i++);
|
|
||||||
|
|
||||||
if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll)
|
|
||||||
{
|
|
||||||
// A catch-all captures til the end of the string.
|
|
||||||
var part = routeSegment.Parts[0];
|
|
||||||
var captured = requestSegment.Buffer.Substring(requestSegment.Offset);
|
|
||||||
if (captured.Length > 0)
|
|
||||||
{
|
|
||||||
values[part.Name] = captured;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
|
|
||||||
values[part.Name] = _defaultValues[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// A catch-all has to be the last part, so we're done.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter)
|
|
||||||
{
|
|
||||||
// A simple parameter captures the whole segment, or a default value if nothing was
|
|
||||||
// provided.
|
|
||||||
var part = routeSegment.Parts[0];
|
|
||||||
if (requestSegment.Length > 0)
|
|
||||||
{
|
|
||||||
values[part.Name] = requestSegment.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_hasDefaultValue[i])
|
|
||||||
{
|
|
||||||
values[part.Name] = _defaultValues[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!routeSegment.IsSimple)
|
|
||||||
{
|
|
||||||
if (!MatchComplexSegment(routeSegment, requestSegment.ToString(), Defaults, values))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i < Template.Segments.Count; i++)
|
|
||||||
{
|
|
||||||
// We've matched the request path so far, but still have remaining route segments. We already know these
|
|
||||||
// are simple parameters that either have a default, or don't need to produce a value.
|
|
||||||
var routeSegment = Template.GetSegment(i);
|
|
||||||
Debug.Assert(routeSegment != null);
|
|
||||||
Debug.Assert(routeSegment.IsSimple);
|
|
||||||
|
|
||||||
var part = routeSegment.Parts[0];
|
|
||||||
Debug.Assert(part.IsParameter);
|
|
||||||
|
|
||||||
// It's ok for a catch-all to produce a null value
|
|
||||||
if (_hasDefaultValue[i] || part.IsCatchAll)
|
|
||||||
{
|
|
||||||
// Don't replace an existing value with a null.
|
|
||||||
var defaultValue = _defaultValues[i];
|
|
||||||
if (defaultValue != null || !values.ContainsKey(part.Name))
|
|
||||||
{
|
|
||||||
values[part.Name] = defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy all remaining default values to the route data
|
|
||||||
foreach (var kvp in Defaults)
|
|
||||||
{
|
|
||||||
if (!values.ContainsKey(kvp.Key))
|
|
||||||
{
|
|
||||||
values.Add(kvp.Key, kvp.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool MatchComplexSegment(
|
|
||||||
TemplateSegment routeSegment,
|
|
||||||
string requestSegment,
|
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
|
||||||
RouteValueDictionary values)
|
|
||||||
{
|
|
||||||
var indexOfLastSegment = routeSegment.Parts.Count - 1;
|
|
||||||
|
|
||||||
// We match the request to the template starting at the rightmost parameter
|
|
||||||
// If the last segment of template is optional, then request can match the
|
|
||||||
// template with or without the last parameter. So we start with regular matching,
|
|
||||||
// but if it doesn't match, we start with next to last parameter. Example:
|
|
||||||
// Template: {p1}/{p2}.{p3?}. If the request is one/two.three it will match right away
|
|
||||||
// giving p3 value of three. But if the request is one/two, we start matching from the
|
|
||||||
// rightmost giving p3 the value of two, then we end up not matching the segment.
|
|
||||||
// In this case we start again from p2 to match the request and we succeed giving
|
|
||||||
// the value two to p2
|
|
||||||
if (routeSegment.Parts[indexOfLastSegment].IsOptional &&
|
|
||||||
routeSegment.Parts[indexOfLastSegment - 1].IsOptionalSeperator)
|
|
||||||
{
|
|
||||||
if (MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (requestSegment.EndsWith(
|
|
||||||
routeSegment.Parts[indexOfLastSegment - 1].Text,
|
|
||||||
StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MatchComplexSegmentCore(
|
|
||||||
routeSegment,
|
|
||||||
requestSegment,
|
|
||||||
Defaults,
|
|
||||||
values,
|
|
||||||
indexOfLastSegment - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool MatchComplexSegmentCore(
|
|
||||||
TemplateSegment routeSegment,
|
|
||||||
string requestSegment,
|
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
|
||||||
RouteValueDictionary values,
|
|
||||||
int indexOfLastSegmentUsed)
|
|
||||||
{
|
|
||||||
Debug.Assert(routeSegment != null);
|
|
||||||
Debug.Assert(routeSegment.Parts.Count > 1);
|
|
||||||
|
|
||||||
// Find last literal segment and get its last index in the string
|
|
||||||
var lastIndex = requestSegment.Length;
|
|
||||||
|
|
||||||
TemplatePart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value
|
|
||||||
TemplatePart lastLiteral = null; // Keeps track of the left-most literal we've encountered
|
|
||||||
|
|
||||||
var outValues = new RouteValueDictionary();
|
|
||||||
|
|
||||||
while (indexOfLastSegmentUsed >= 0)
|
|
||||||
{
|
|
||||||
var newLastIndex = lastIndex;
|
|
||||||
|
|
||||||
var part = routeSegment.Parts[indexOfLastSegmentUsed];
|
|
||||||
if (part.IsParameter)
|
|
||||||
{
|
|
||||||
// Hold on to the parameter so that we can fill it in when we locate the next literal
|
|
||||||
parameterNeedsValue = part;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Assert(part.IsLiteral || part.IsOptionalSeperator);
|
|
||||||
lastLiteral = part;
|
|
||||||
|
|
||||||
var startIndex = lastIndex - 1;
|
|
||||||
// If we have a pending parameter subsegment, we must leave at least one character for that
|
|
||||||
if (parameterNeedsValue != null)
|
|
||||||
{
|
|
||||||
startIndex--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startIndex < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexOfLiteral = requestSegment.LastIndexOf(
|
|
||||||
part.Text,
|
|
||||||
startIndex,
|
|
||||||
StringComparison.OrdinalIgnoreCase);
|
|
||||||
if (indexOfLiteral == -1)
|
|
||||||
{
|
|
||||||
// If we couldn't find this literal index, this segment cannot match
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the first subsegment is a literal, it must match at the right-most extent of the request URI.
|
|
||||||
// Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/".
|
|
||||||
// This check is related to the check we do at the very end of this function.
|
|
||||||
if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1))
|
|
||||||
{
|
|
||||||
if ((indexOfLiteral + part.Text.Length) != requestSegment.Length)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newLastIndex = indexOfLiteral;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((parameterNeedsValue != null) &&
|
|
||||||
(((lastLiteral != null) && (part.IsLiteral)) || (indexOfLastSegmentUsed == 0)))
|
|
||||||
{
|
|
||||||
// If we have a pending parameter that needs a value, grab that value
|
|
||||||
|
|
||||||
int parameterStartIndex;
|
|
||||||
int parameterTextLength;
|
|
||||||
|
|
||||||
if (lastLiteral == null)
|
|
||||||
{
|
|
||||||
if (indexOfLastSegmentUsed == 0)
|
|
||||||
{
|
|
||||||
parameterStartIndex = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
parameterStartIndex = newLastIndex;
|
|
||||||
Debug.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above");
|
|
||||||
}
|
|
||||||
parameterTextLength = lastIndex;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If we're getting a value for a parameter that is somewhere in the middle of the segment
|
|
||||||
if ((indexOfLastSegmentUsed == 0) && (part.IsParameter))
|
|
||||||
{
|
|
||||||
parameterStartIndex = 0;
|
|
||||||
parameterTextLength = lastIndex;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
parameterStartIndex = newLastIndex + lastLiteral.Text.Length;
|
|
||||||
parameterTextLength = lastIndex - parameterStartIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameterValueString = requestSegment.Substring(parameterStartIndex, parameterTextLength);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(parameterValueString))
|
|
||||||
{
|
|
||||||
// If we're here that means we have a segment that contains multiple sub-segments.
|
|
||||||
// For these segments all parameters must have non-empty values. If the parameter
|
|
||||||
// has an empty value it's not a match.
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If there's a value in the segment for this parameter, use the subsegment value
|
|
||||||
outValues.Add(parameterNeedsValue.Name, parameterValueString);
|
|
||||||
}
|
|
||||||
|
|
||||||
parameterNeedsValue = null;
|
|
||||||
lastLiteral = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = newLastIndex;
|
|
||||||
indexOfLastSegmentUsed--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of
|
|
||||||
// the string since the parameter will have consumed all the remaining text anyway. If the last subsegment
|
|
||||||
// is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching
|
|
||||||
// the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire*
|
|
||||||
// request URI in order for it to be a match.
|
|
||||||
// This check is related to the check we do earlier in this function for LiteralSubsegments.
|
|
||||||
if (lastIndex == 0 || routeSegment.Parts[0].IsParameter)
|
|
||||||
{
|
|
||||||
foreach (var item in outValues)
|
|
||||||
{
|
|
||||||
values.Add(item.Key, item.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue