diff --git a/benchmarkapps/Benchmarks/Benchmarks.csproj b/benchmarkapps/Benchmarks/Benchmarks.csproj
index b85c439bc7..3c5753cc64 100644
--- a/benchmarkapps/Benchmarks/Benchmarks.csproj
+++ b/benchmarkapps/Benchmarks/Benchmarks.csproj
@@ -3,10 +3,11 @@
netcoreapp2.2
$(BenchmarksTargetFramework)
+ true
-
+
@@ -14,7 +15,7 @@
-
+
diff --git a/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs b/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs
index 1351a20689..f8c739eab1 100644
--- a/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs
+++ b/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs
@@ -5,6 +5,7 @@ using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matchers;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
namespace Benchmarks
@@ -31,10 +32,8 @@ namespace Benchmarks
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
},
- template: "/plaintext",
- defaults: new RouteValueDictionary(),
+ routePattern: RoutePatternFactory.Parse("/plaintext"),
requiredValues: new RouteValueDictionary(),
- nonInlineMatchProcessorReferences: null,
order: 0,
metadata: EndpointMetadataCollection.Empty,
displayName: "Plaintext"),
diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs
index fb12d507a7..ada2c02857 100644
--- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs
+++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.EndpointConstraints;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Routing.Matchers
@@ -42,11 +43,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers
}
return new MatcherEndpoint(
- (next) => (context) => Task.CompletedTask,
- template,
+ MatcherEndpoint.EmptyInvoker,
+ RoutePatternFactory.Parse(template),
new RouteValueDictionary(),
- new RouteValueDictionary(),
- new List(),
0,
new EndpointMetadataCollection(metadata),
template);
diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/TrivialMatcher.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/TrivialMatcher.cs
index 6784f2b190..0483a0b5a9 100644
--- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/TrivialMatcher.cs
+++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/TrivialMatcher.cs
@@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
}
var path = httpContext.Request.Path.Value;
- if (string.Equals(_endpoint.Template, path, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
{
feature.Endpoint = _endpoint;
feature.Values = new RouteValueDictionary();
@@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
// This is here so this can be tested alongside DFA matcher.
internal CandidateSet SelectCandidates(string path, ReadOnlySpan segments)
{
- if (string.Equals(_endpoint.Template, path, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
{
return _candidates;
}
diff --git a/samples/DispatcherSample.Web/Startup.cs b/samples/DispatcherSample.Web/Startup.cs
index 9f8747310f..4cbebd1e89 100644
--- a/samples/DispatcherSample.Web/Startup.cs
+++ b/samples/DispatcherSample.Web/Startup.cs
@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matchers;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
namespace DispatcherSample.Web
@@ -39,10 +40,8 @@ namespace DispatcherSample.Web
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
},
- "/",
+ RoutePatternFactory.Parse("/"),
new RouteValueDictionary(),
- new RouteValueDictionary(),
- new List(),
0,
EndpointMetadataCollection.Empty,
"Home"),
@@ -55,10 +54,8 @@ namespace DispatcherSample.Web
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
},
- "/plaintext",
- new RouteValueDictionary(),
- new RouteValueDictionary(),
- new List(),
+ RoutePatternFactory.Parse("/plaintext"),
+ new RouteValueDictionary(),
0,
EndpointMetadataCollection.Empty,
"Plaintext"),
@@ -69,10 +66,8 @@ namespace DispatcherSample.Web
response.ContentType = "text/plain";
return response.WriteAsync("WithConstraints");
},
- "/withconstraints/{id:endsWith(_001)}",
+ RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"),
new RouteValueDictionary(),
- new RouteValueDictionary(),
- new List(),
0,
EndpointMetadataCollection.Empty,
"withconstraints"),
@@ -83,10 +78,8 @@ namespace DispatcherSample.Web
response.ContentType = "text/plain";
return response.WriteAsync("withoptionalconstraints");
},
- "/withoptionalconstraints/{id:endsWith(_001)?}",
+ RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"),
new RouteValueDictionary(),
- new RouteValueDictionary(),
- new List(),
0,
EndpointMetadataCollection.Empty,
"withoptionalconstraints"),
diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs
index cef493c3db..a52382e50d 100644
--- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs
+++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs
@@ -86,8 +86,8 @@ namespace Microsoft.AspNetCore.Routing
var templateBinder = new TemplateBinder(
UrlEncoder.Default,
_uriBuildingContextPool,
- endpoint.ParsedTemplate,
- endpoint.Defaults);
+ new RouteTemplate(endpoint.RoutePattern),
+ new RouteValueDictionary(endpoint.RoutePattern.Defaults));
var templateValuesResult = templateBinder.GetValues(ambientValues, explicitValues);
if (templateValuesResult == null)
@@ -110,20 +110,19 @@ namespace Microsoft.AspNetCore.Routing
{
throw new ArgumentNullException(nameof(routeValues));
}
-
- for (var i = 0; i < endpoint.MatchProcessorReferences.Count; i++)
+
+ foreach (var kvp in endpoint.RoutePattern.Constraints)
{
- var matchProcessorReference = endpoint.MatchProcessorReferences[i];
- var parameter = endpoint.ParsedTemplate.GetParameter(matchProcessorReference.ParameterName);
- if (parameter != null && parameter.IsOptional && !routeValues.ContainsKey(parameter.Name))
+ var parameter = endpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok
+ var constraintReferences = kvp.Value;
+ for (var i = 0; i < constraintReferences.Count; i++)
{
- continue;
- }
-
- var matchProcessor = _matchProcessorFactory.Create(matchProcessorReference);
- if (!matchProcessor.ProcessOutbound(httpContext, routeValues))
- {
- return false;
+ var constraintReference = constraintReferences[i];
+ var matchProcessor = _matchProcessorFactory.Create(parameter, constraintReference);
+ if (!matchProcessor.ProcessOutbound(httpContext, routeValues))
+ {
+ return false;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/DefaultMatchProcessorFactory.cs b/src/Microsoft.AspNetCore.Routing/Matchers/DefaultMatchProcessorFactory.cs
index f4acb68f78..d515df57da 100644
--- a/src/Microsoft.AspNetCore.Routing/Matchers/DefaultMatchProcessorFactory.cs
+++ b/src/Microsoft.AspNetCore.Routing/Matchers/DefaultMatchProcessorFactory.cs
@@ -20,145 +20,113 @@ namespace Microsoft.AspNetCore.Routing.Matchers
_serviceProvider = serviceProvider;
}
- public override MatchProcessor Create(MatchProcessorReference matchProcessorReference)
+ public override MatchProcessor Create(string parameterName, IRouteConstraint value, bool optional)
{
- if (matchProcessorReference == null)
+ if (value == null)
{
- throw new ArgumentNullException(nameof(matchProcessorReference));
+ throw new ArgumentNullException(nameof(value));
}
- if (matchProcessorReference.MatchProcessor != null)
+ return InitializeMatchProcessor(parameterName, optional, value, argument: null);
+ }
+
+ public override MatchProcessor Create(string parameterName, MatchProcessor value, bool optional)
+ {
+ if (value == null)
{
- return matchProcessorReference.MatchProcessor;
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ return InitializeMatchProcessor(parameterName, optional, value, argument: null);
+ }
+
+ public override MatchProcessor Create(string parameterName, string value, bool optional)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
}
// Example:
// {productId:regex(\d+)}
//
// ParameterName: productId
- // ConstraintText: regex(\d+)
- // ConstraintName: regex
- // ConstraintArgument: \d+
+ // value: regex(\d+)
+ // name: regex
+ // argument: \d+
+ (var name, var argument) = Parse(value);
- (var constraintName, var constraintArgument) = Parse(matchProcessorReference.ConstraintText);
-
- if (!_options.ConstraintMap.TryGetValue(constraintName, out var constraintType))
+ if (!_options.ConstraintMap.TryGetValue(name, out var type))
{
- throw new InvalidOperationException(
- $"No constraint has been registered with name '{constraintName}'.");
+ throw new InvalidOperationException(Resources.FormatRoutePattern_ConstraintReferenceNotFound(
+ name,
+ typeof(RouteOptions),
+ nameof(RouteOptions.ConstraintMap)));
}
- var processor = ResolveMatchProcessor(
- matchProcessorReference.ParameterName,
- matchProcessorReference.Optional,
- constraintType,
- constraintArgument);
-
- if (processor != null)
+ if (typeof(MatchProcessor).IsAssignableFrom(type))
{
- return processor;
+ var matchProcessor = (MatchProcessor)_serviceProvider.GetRequiredService(type);
+ return InitializeMatchProcessor(parameterName, optional, matchProcessor, argument);
}
- if (!typeof(IRouteConstraint).IsAssignableFrom(constraintType))
+ if (typeof(IRouteConstraint).IsAssignableFrom(type))
{
- throw new InvalidOperationException(
- Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
- constraintType,
- constraintName,
- typeof(IRouteConstraint).Name));
+ var constraint = DefaultInlineConstraintResolver.CreateConstraint(type, argument);
+ return InitializeMatchProcessor(parameterName, optional, constraint, argument);
}
- try
- {
- return CreateMatchProcessorFromRouteConstraint(
- matchProcessorReference.ParameterName,
- matchProcessorReference.Optional,
- constraintType,
- constraintArgument);
- }
- catch (RouteCreationException)
- {
- throw;
- }
- catch (Exception exception)
- {
- throw new InvalidOperationException(
- $"An error occurred while trying to create an instance of route constraint '{constraintType.FullName}'.",
- exception);
- }
+ var message = Resources.FormatRoutePattern_InvalidStringConstraintReference(
+ type,
+ name,
+ typeof(IRouteConstraint),
+ typeof(MatchProcessor));
+ throw new InvalidOperationException(message);
}
- private MatchProcessor CreateMatchProcessorFromRouteConstraint(
+ private MatchProcessor InitializeMatchProcessor(
string parameterName,
bool optional,
- Type constraintType,
- string constraintArgument)
+ IRouteConstraint constraint,
+ string argument)
+ {
+ var matchProcessor = (MatchProcessor)new RouteConstraintMatchProcessor(parameterName, constraint);
+ return InitializeMatchProcessor(parameterName, optional, matchProcessor, argument);
+ }
+
+ private MatchProcessor InitializeMatchProcessor(
+ string parameterName,
+ bool optional,
+ MatchProcessor matchProcessor,
+ string argument)
{
- var routeConstraint = DefaultInlineConstraintResolver.CreateConstraint(constraintType, constraintArgument);
- var matchProcessor = new MatchProcessorReference(parameterName, routeConstraint).MatchProcessor;
if (optional)
{
matchProcessor = new OptionalMatchProcessor(matchProcessor);
}
- matchProcessor.Initialize(parameterName, constraintArgument);
-
+ matchProcessor.Initialize(parameterName, argument);
return matchProcessor;
}
- private MatchProcessor ResolveMatchProcessor(
- string parameterName,
- bool optional,
- Type constraintType,
- string constraintArgument)
+ private (string name, string argument) Parse(string text)
{
- if (constraintType == null)
+ string name;
+ string argument;
+ var indexOfFirstOpenParens = text.IndexOf('(');
+ if (indexOfFirstOpenParens >= 0 && text.EndsWith(")", StringComparison.Ordinal))
{
- throw new ArgumentNullException(nameof(constraintType));
- }
-
- if (!typeof(MatchProcessor).IsAssignableFrom(constraintType))
- {
- // Since a constraint type could be of type IRouteConstraint, do not throw
- return null;
- }
-
- var registeredProcessor = _serviceProvider.GetRequiredService(constraintType);
- if (registeredProcessor is MatchProcessor matchProcessor)
- {
- if (optional)
- {
- matchProcessor = new OptionalMatchProcessor(matchProcessor);
- }
-
- matchProcessor.Initialize(parameterName, constraintArgument);
- return matchProcessor;
- }
- else
- {
- throw new InvalidOperationException(
- $"Registered constraint type '{constraintType}' is not of type '{typeof(MatchProcessor)}'.");
- }
- }
-
- private (string constraintName, string constraintArgument) Parse(string constraintText)
- {
- string constraintName;
- string constraintArgument;
- var indexOfFirstOpenParens = constraintText.IndexOf('(');
- if (indexOfFirstOpenParens >= 0 && constraintText.EndsWith(")", StringComparison.Ordinal))
- {
- constraintName = constraintText.Substring(0, indexOfFirstOpenParens);
- constraintArgument = constraintText.Substring(
+ name = text.Substring(0, indexOfFirstOpenParens);
+ argument = text.Substring(
indexOfFirstOpenParens + 1,
- constraintText.Length - indexOfFirstOpenParens - 2);
+ text.Length - indexOfFirstOpenParens - 2);
}
else
{
- constraintName = constraintText;
- constraintArgument = null;
+ name = text;
+ argument = null;
}
- return (constraintName, constraintArgument);
+ return (name, argument);
}
}
}
diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/DfaMatcherBuilder.cs b/src/Microsoft.AspNetCore.Routing/Matchers/DfaMatcherBuilder.cs
index 36f4b2c6fe..ab9936ed06 100644
--- a/src/Microsoft.AspNetCore.Routing/Matchers/DfaMatcherBuilder.cs
+++ b/src/Microsoft.AspNetCore.Routing/Matchers/DfaMatcherBuilder.cs
@@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
for (var i = 0; i < _entries.Count; i++)
{
var entry = _entries[i];
- maxDepth = Math.Max(maxDepth, entry.Pattern.Segments.Count);
+ maxDepth = Math.Max(maxDepth, entry.RoutePattern.PathSegments.Count);
work.Add((entry, new List() { root, }));
}
@@ -88,9 +88,10 @@ namespace Microsoft.AspNetCore.Routing.Matchers
for (var j = 0; j < parents.Count; j++)
{
var parent = parents[j];
- if (segment.IsSimple && segment.Parts[0].IsLiteral)
+ var part = segment.Parts[0];
+ if (segment.IsSimple && part is RoutePatternLiteralPart literalPart)
{
- var literal = segment.Parts[0].Text;
+ var literal = literalPart.Content;
if (!parent.Literals.TryGetValue(literal, out var next))
{
next = new DfaNode()
@@ -103,7 +104,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
nextParents.Add(next);
}
- else if (segment.IsSimple && segment.Parts[0].IsCatchAll)
+ else if (segment.IsSimple && part is RoutePatternParameterPart parameterPart && parameterPart.IsCatchAll)
{
// A catch all should traverse all literal nodes as well as parameter nodes
// we don't need to create the parameter node here because of ordering
@@ -134,7 +135,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
parent.CatchAll.Matches.Add(entry);
}
- else if (segment.IsSimple && segment.Parts[0].IsParameter)
+ else if (segment.IsSimple && part.IsParameter)
{
if (parent.Parameters == null)
{
@@ -182,20 +183,20 @@ namespace Microsoft.AspNetCore.Routing.Matchers
return root;
}
- private TemplateSegment GetCurrentSegment(MatcherBuilderEntry entry, int depth)
+ private RoutePatternPathSegment GetCurrentSegment(MatcherBuilderEntry entry, int depth)
{
- if (depth < entry.Pattern.Segments.Count)
+ if (depth < entry.RoutePattern.PathSegments.Count)
{
- return entry.Pattern.Segments[depth];
+ return entry.RoutePattern.PathSegments[depth];
}
- if (entry.Pattern.Segments.Count == 0)
+ if (entry.RoutePattern.PathSegments.Count == 0)
{
return null;
}
- var lastSegment = entry.Pattern.Segments[entry.Pattern.Segments.Count - 1];
- if (lastSegment.IsSimple && lastSegment.Parts[0].IsCatchAll)
+ var lastSegment = entry.RoutePattern.PathSegments[entry.RoutePattern.PathSegments.Count - 1];
+ if (lastSegment.IsSimple && lastSegment.Parts[0] is RoutePatternParameterPart parameterPart && parameterPart.IsCatchAll)
{
return lastSegment;
}
@@ -305,63 +306,68 @@ namespace Microsoft.AspNetCore.Routing.Matchers
var captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
(string parameterName, int segmentIndex, int slotIndex) catchAll = default;
- foreach (var kvp in entry.Endpoint.Defaults)
+ foreach (var kvp in entry.Endpoint.RoutePattern.Defaults)
{
assignments.Add(kvp.Key, assignments.Count);
slots.Add(kvp);
}
- for (var i = 0; i < entry.Pattern.Segments.Count; i++)
+ for (var i = 0; i < entry.Endpoint.RoutePattern.PathSegments.Count; i++)
{
- var segment = entry.Pattern.Segments[i];
+ var segment = entry.Endpoint.RoutePattern.PathSegments[i];
if (!segment.IsSimple)
{
continue;
}
- var part = segment.Parts[0];
- if (!part.IsParameter)
+ var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
+ if (parameterPart == null)
{
continue;
}
- if (!assignments.TryGetValue(part.Name, out var slotIndex))
+ if (!assignments.TryGetValue(parameterPart.Name, out var slotIndex))
{
slotIndex = assignments.Count;
- assignments.Add(part.Name, slotIndex);
+ assignments.Add(parameterPart.Name, slotIndex);
- var hasDefaultValue = part.DefaultValue != null || part.IsCatchAll;
- slots.Add(hasDefaultValue ? new KeyValuePair(part.Name, part.DefaultValue) : default);
+ var hasDefaultValue = parameterPart.Default != null || parameterPart.IsCatchAll;
+ slots.Add(hasDefaultValue ? new KeyValuePair(parameterPart.Name, parameterPart.Default) : default);
}
- if (part.IsCatchAll)
+ if (parameterPart.IsCatchAll)
{
- catchAll = (part.Name, i, slotIndex);
+ catchAll = (parameterPart.Name, i, slotIndex);
}
else
{
- captures.Add((part.Name, i, slotIndex));
+ captures.Add((parameterPart.Name, i, slotIndex));
}
}
var complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
- for (var i = 0; i < entry.Pattern.Segments.Count; i++)
+ for (var i = 0; i < entry.RoutePattern.PathSegments.Count; i++)
{
- var segment = entry.Pattern.Segments[i];
+ var segment = entry.RoutePattern.PathSegments[i];
if (segment.IsSimple)
{
continue;
}
- complexSegments.Add((segment.ToRoutePatternPathSegment(), i));
+ complexSegments.Add((segment, i));
}
var matchProcessors = new List();
- for (var i = 0; i < entry.Endpoint.MatchProcessorReferences.Count; i++)
+ foreach (var kvp in entry.Endpoint.RoutePattern.Constraints)
{
- var reference = entry.Endpoint.MatchProcessorReferences[i];
- var processor = _matchProcessorFactory.Create(reference);
- matchProcessors.Add(processor);
+ var parameter = entry.Endpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok
+ var constraintReferences = kvp.Value;
+ for (var i = 0; i < constraintReferences.Count; i++)
+ {
+ var constraintReference = constraintReferences[i];
+ var matchProcessor = _matchProcessorFactory.Create(parameter, constraintReference);
+ matchProcessors.Add(matchProcessor);
+ }
}
return new Candidate(
@@ -405,25 +411,25 @@ namespace Microsoft.AspNetCore.Routing.Matchers
private static bool HasAdditionalRequiredSegments(MatcherBuilderEntry entry, int depth)
{
- for (var i = depth; i < entry.Pattern.Segments.Count; i++)
+ for (var i = depth; i < entry.RoutePattern.PathSegments.Count; i++)
{
- var segment = entry.Pattern.Segments[i];
+ var segment = entry.RoutePattern.PathSegments[i];
if (!segment.IsSimple)
{
// Complex segments always require more processing
return true;
}
- var part = segment.Parts[0];
- if (part.IsLiteral)
+ var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
+ if (parameterPart == null)
{
+ // It's a literal
return true;
}
- if (!part.IsOptional &&
- !part.IsCatchAll &&
- part.DefaultValue == null &&
- !entry.Endpoint.Defaults.ContainsKey(part.Name))
+ if (!parameterPart.IsOptional &&
+ !parameterPart.IsCatchAll &&
+ parameterPart.Default == null)
{
return true;
}
diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/MatchProcessorFactory.cs b/src/Microsoft.AspNetCore.Routing/Matchers/MatchProcessorFactory.cs
index 95624ea420..e21d3bd6ea 100644
--- a/src/Microsoft.AspNetCore.Routing/Matchers/MatchProcessorFactory.cs
+++ b/src/Microsoft.AspNetCore.Routing/Matchers/MatchProcessorFactory.cs
@@ -1,10 +1,46 @@
// 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.Diagnostics;
+using Microsoft.AspNetCore.Routing.Patterns;
+
namespace Microsoft.AspNetCore.Routing.Matchers
{
internal abstract class MatchProcessorFactory
{
- public abstract MatchProcessor Create(MatchProcessorReference matchProcessorReference);
+ public abstract MatchProcessor Create(string parameterName, string value, bool optional);
+
+ public abstract MatchProcessor Create(string parameterName, IRouteConstraint value, bool optional);
+
+ public abstract MatchProcessor Create(string parameterName, MatchProcessor value, bool optional);
+
+ public MatchProcessor Create(RoutePatternParameterPart parameter, RoutePatternConstraintReference reference)
+ {
+ if (reference == null)
+ {
+ throw new ArgumentNullException(nameof(reference));
+ }
+
+ Debug.Assert(reference.MatchProcessor != null || reference.Constraint != null || reference.Content != null);
+
+ if (reference.MatchProcessor != null)
+ {
+ return Create(parameter?.Name, reference.MatchProcessor, parameter?.IsOptional ?? false);
+ }
+
+ if (reference.Constraint != null)
+ {
+ return Create(parameter?.Name, reference.Constraint, parameter?.IsOptional ?? false);
+ }
+
+ if (reference.Content != null)
+ {
+ return Create(parameter?.Name, reference.Content, parameter?.IsOptional ?? false);
+ }
+
+ // Unreachable
+ throw new NotSupportedException();
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/MatchProcessorReference.cs b/src/Microsoft.AspNetCore.Routing/Matchers/MatchProcessorReference.cs
deleted file mode 100644
index 6266bb22e1..0000000000
--- a/src/Microsoft.AspNetCore.Routing/Matchers/MatchProcessorReference.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-// 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 Microsoft.AspNetCore.Http;
-
-namespace Microsoft.AspNetCore.Routing.Matchers
-{
- public sealed class MatchProcessorReference
- {
- // Example:
- // api/products/{productId:regex(\d+)}
- //
- // ParameterName = productId
- // ConstraintText = regex(\d+)
- // ConstraintArgument = \d+
-
- public MatchProcessorReference(string parameterName, string constraintText)
- {
- ParameterName = parameterName;
- ConstraintText = constraintText;
- }
-
- public MatchProcessorReference(string parameterName, bool optional, string constraintText)
- {
- ParameterName = parameterName;
- Optional = optional;
- ConstraintText = constraintText;
- }
-
- public MatchProcessorReference(string parameterName, MatchProcessor matchProcessor)
- {
- ParameterName = parameterName;
- MatchProcessor = matchProcessor;
- }
-
- internal MatchProcessor MatchProcessor { get; private set; }
-
- internal string ConstraintText { get; private set; }
-
- internal string ParameterName { get; private set; }
-
- internal bool Optional { get; private set; }
-
- public MatchProcessorReference(string parameterName, IRouteConstraint routeConstraint)
- : this(parameterName, new RouteConstraintMatchProcessorAdapter(parameterName, routeConstraint))
- {
- }
-
- private class RouteConstraintMatchProcessorAdapter : MatchProcessor
- {
- public string ParameterName { get; private set; }
-
- public IRouteConstraint RouteConstraint { get; }
-
- public RouteConstraintMatchProcessorAdapter(string parameterName, IRouteConstraint routeConstraint)
- {
- ParameterName = parameterName;
- RouteConstraint = routeConstraint;
- }
-
- public override void Initialize(string parameterName, string constraintArgument)
- {
- }
-
- public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary routeValues)
- {
- return RouteConstraint.Match(
- httpContext,
- NullRouter.Instance,
- ParameterName,
- routeValues,
- RouteDirection.IncomingRequest);
- }
-
- public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values)
- {
- return RouteConstraint.Match(
- httpContext,
- NullRouter.Instance,
- ParameterName,
- values,
- RouteDirection.UrlGeneration);
- }
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/MatcherBuilderEntry.cs b/src/Microsoft.AspNetCore.Routing/Matchers/MatcherBuilderEntry.cs
index a9e3a6d3b8..d1aed77693 100644
--- a/src/Microsoft.AspNetCore.Routing/Matchers/MatcherBuilderEntry.cs
+++ b/src/Microsoft.AspNetCore.Routing/Matchers/MatcherBuilderEntry.cs
@@ -2,8 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Linq;
-using Microsoft.AspNetCore.Routing.EndpointConstraints;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template;
namespace Microsoft.AspNetCore.Routing.Matchers
@@ -14,14 +13,14 @@ namespace Microsoft.AspNetCore.Routing.Matchers
{
Endpoint = endpoint;
- Precedence = RoutePrecedence.ComputeInbound(endpoint.ParsedTemplate);
+ Precedence = RoutePrecedence.ComputeInbound(endpoint.RoutePattern);
}
public MatcherEndpoint Endpoint { get; }
public int Order => Endpoint.Order;
- public RouteTemplate Pattern => Endpoint.ParsedTemplate;
+ public RoutePattern RoutePattern => Endpoint.RoutePattern;
public decimal Precedence { get; }
@@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
return comparison;
}
- return Pattern.TemplateText.CompareTo(other.Pattern.TemplateText);
+ return RoutePattern.RawText.CompareTo(other.RoutePattern.RawText);
}
public bool PriorityEquals(MatcherBuilderEntry other)
diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs b/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs
index 591c98bac4..4311a28ee7 100644
--- a/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs
+++ b/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing.Template;
+using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Matchers
{
@@ -18,25 +18,11 @@ namespace Microsoft.AspNetCore.Routing.Matchers
public MatcherEndpoint(
Func invoker,
- string template,
- RouteValueDictionary defaults,
+ RoutePattern routePattern,
RouteValueDictionary requiredValues,
int order,
EndpointMetadataCollection metadata,
string displayName)
- : this(invoker, template, defaults, requiredValues, new List(), order, metadata, displayName)
- {
- }
-
- public MatcherEndpoint(
- Func invoker,
- string template,
- RouteValueDictionary defaults,
- RouteValueDictionary requiredValues,
- List nonInlineMatchProcessorReferences,
- int order,
- EndpointMetadataCollection metadata,
- string displayName)
: base(metadata, displayName)
{
if (invoker == null)
@@ -44,89 +30,24 @@ namespace Microsoft.AspNetCore.Routing.Matchers
throw new ArgumentNullException(nameof(invoker));
}
- if (template == null)
+ if (routePattern == null)
{
- throw new ArgumentNullException(nameof(template));
+ throw new ArgumentNullException(nameof(routePattern));
}
Invoker = invoker;
- Order = order;
-
- Template = template;
- ParsedTemplate = TemplateParser.Parse(template);
-
+ RoutePattern = routePattern;
RequiredValues = requiredValues;
- var mergedDefaults = GetDefaults(ParsedTemplate, defaults);
- Defaults = mergedDefaults;
-
- var mergedReferences = MergeMatchProcessorReferences(ParsedTemplate, nonInlineMatchProcessorReferences);
- MatchProcessorReferences = mergedReferences.AsReadOnly();
+ Order = order;
}
+ public Func Invoker { get; }
+
public int Order { get; }
- public Func Invoker { get; }
- public string Template { get; }
- public RouteValueDictionary Defaults { get; }
-
+
// Values required by an endpoint for it to be successfully matched on link generation
- public RouteValueDictionary RequiredValues { get; }
+ public IReadOnlyDictionary RequiredValues { get; }
- // Todo: needs review
- public RouteTemplate ParsedTemplate { get; }
-
- public IReadOnlyList MatchProcessorReferences { get; }
-
- // Merge inline and non inline defaults into one
- private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate, RouteValueDictionary nonInlineDefaults)
- {
- var result = nonInlineDefaults == null ? new RouteValueDictionary() : new RouteValueDictionary(nonInlineDefaults);
-
- foreach (var parameter in parsedTemplate.Parameters)
- {
- if (parameter.DefaultValue != null)
- {
- if (result.ContainsKey(parameter.Name))
- {
- throw new InvalidOperationException(
- Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(
- parameter.Name));
- }
- else
- {
- result.Add(parameter.Name, parameter.DefaultValue);
- }
- }
- }
-
- return result;
- }
-
- private List MergeMatchProcessorReferences(
- RouteTemplate parsedTemplate,
- List nonInlineReferences)
- {
- var matchProcessorReferences = new List();
-
- if (nonInlineReferences != null)
- {
- matchProcessorReferences.AddRange(nonInlineReferences);
- }
-
- foreach (var parameter in parsedTemplate.Parameters)
- {
- if (parameter.InlineConstraints != null)
- {
- foreach (var constraint in parameter.InlineConstraints)
- {
- matchProcessorReferences.Add(
- new MatchProcessorReference(
- parameter.Name,
- optional: parameter.IsOptional,
- constraintText: constraint.Constraint));
- }
- }
- }
- return matchProcessorReferences;
- }
+ public RoutePattern RoutePattern { get; }
}
}
diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/RouteConstraintMatchProcessor.cs b/src/Microsoft.AspNetCore.Routing/Matchers/RouteConstraintMatchProcessor.cs
new file mode 100644
index 0000000000..16fd9d0a84
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Routing/Matchers/RouteConstraintMatchProcessor.cs
@@ -0,0 +1,61 @@
+// 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 Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Routing.Matchers
+{
+ internal class RouteConstraintMatchProcessor : MatchProcessor
+ {
+ public RouteConstraintMatchProcessor(string parameterName, IRouteConstraint constraint)
+ {
+ ParameterName = parameterName;
+ Constraint = constraint;
+ }
+
+ public string ParameterName { get; }
+
+ public IRouteConstraint Constraint { get; }
+
+ public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary values)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ return Constraint.Match(
+ httpContext,
+ NullRouter.Instance,
+ ParameterName,
+ values,
+ RouteDirection.IncomingRequest);
+ }
+
+ public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ return Constraint.Match(
+ httpContext,
+ NullRouter.Instance,
+ ParameterName,
+ values,
+ RouteDirection.UrlGeneration);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs b/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs
index c5b1a008e1..885f77481c 100644
--- a/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs
+++ b/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs
@@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
case null:
state = ParseState.End;
var constraintText = text.Substring(startIndex, currentIndex - startIndex);
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
break;
case ')':
// Only consume a ')' token if
@@ -135,18 +135,18 @@ namespace Microsoft.AspNetCore.Routing.Patterns
case null:
state = ParseState.End;
constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
break;
case ':':
state = ParseState.Start;
constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
startIndex = currentIndex + 1;
break;
case '=':
state = ParseState.End;
constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
break;
}
break;
@@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
if (indexOfClosingParantheses == -1)
{
constraintText = text.Substring(startIndex, currentIndex - startIndex);
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
if (currentChar == ':')
{
@@ -190,14 +190,14 @@ namespace Microsoft.AspNetCore.Routing.Patterns
var constraintText = text.Substring(startIndex, currentIndex - startIndex);
if (constraintText.Length > 0)
{
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
}
break;
case ':':
constraintText = text.Substring(startIndex, currentIndex - startIndex);
if (constraintText.Length > 0)
{
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
}
startIndex = currentIndex + 1;
break;
@@ -209,7 +209,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
constraintText = text.Substring(startIndex, currentIndex - startIndex);
if (constraintText.Length > 0)
{
- constraints.Add(RoutePatternFactory.Constraint(parameterName, constraintText));
+ constraints.Add(RoutePatternFactory.Constraint(constraintText));
}
currentIndex--;
break;
diff --git a/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternConstraintReference.cs b/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternConstraintReference.cs
index 272282a63f..0d8c9810bc 100644
--- a/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternConstraintReference.cs
+++ b/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternConstraintReference.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
+using Microsoft.AspNetCore.Routing.Matchers;
namespace Microsoft.AspNetCore.Routing.Patterns
{
@@ -11,18 +12,21 @@ namespace Microsoft.AspNetCore.Routing.Patterns
[DebuggerDisplay("{DebuggerToString()}")]
public sealed class RoutePatternConstraintReference
{
- internal RoutePatternConstraintReference(string parameterName, string content)
+ internal RoutePatternConstraintReference(string content)
{
- ParameterName = parameterName;
Content = content;
}
- internal RoutePatternConstraintReference(string parameterName, IRouteConstraint constraint)
+ internal RoutePatternConstraintReference(IRouteConstraint constraint)
{
- ParameterName = parameterName;
Constraint = constraint;
}
+ internal RoutePatternConstraintReference(MatchProcessor matchProcessor)
+ {
+ MatchProcessor = matchProcessor;
+ }
+
///
/// Gets the constraint text.
///
@@ -34,9 +38,9 @@ namespace Microsoft.AspNetCore.Routing.Patterns
public IRouteConstraint Constraint { get; }
///
- /// Gets the parameter name associated with the constraint.
+ /// Gets a pre-existing that was used to construct this reference.
///
- public string ParameterName { get; }
+ public MatchProcessor MatchProcessor { get; }
private string DebuggerToString()
{
diff --git a/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs b/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs
index e653845f87..de0b11e889 100644
--- a/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs
+++ b/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Routing.Constraints;
+using Microsoft.AspNetCore.Routing.Matchers;
namespace Microsoft.AspNetCore.Routing.Patterns
{
@@ -158,7 +159,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
{
updatedConstraints.Add(kvp.Key, new List()
{
- Constraint(kvp.Key, kvp.Value),
+ Constraint(kvp.Value),
});
}
}
@@ -450,55 +451,73 @@ namespace Microsoft.AspNetCore.Routing.Patterns
return new RoutePatternParameterPart(parameterName, @default, parameterKind, constraints.ToArray());
}
- public static RoutePatternConstraintReference Constraint(string parameterName, object constraint)
+ public static RoutePatternConstraintReference Constraint(object constraint)
{
// Similar to RouteConstraintBuilder
if (constraint is IRouteConstraint routeConstraint)
{
- return ConstraintCore(parameterName, routeConstraint);
+ return ConstraintCore(routeConstraint);
+ }
+ else if (constraint is MatchProcessor matchProcessor)
+ {
+ return ConstraintCore(matchProcessor);
}
else if (constraint is string content)
{
- return ConstraintCore(parameterName, new RegexRouteConstraint("^(" + content + ")$"));
+ return ConstraintCore(new RegexRouteConstraint("^(" + content + ")$"));
}
else
{
- throw new InvalidOperationException(Resources.FormatConstraintMustBeStringOrConstraint(
- parameterName,
- constraint,
- typeof(IRouteConstraint)));
+ throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
+ constraint ?? "null",
+ typeof(IRouteConstraint),
+ typeof(MatchProcessor)));
}
}
- public static RoutePatternConstraintReference Constraint(string parameterName, IRouteConstraint constraint)
+ public static RoutePatternConstraintReference Constraint(IRouteConstraint constraint)
{
if (constraint == null)
{
throw new ArgumentNullException(nameof(constraint));
}
- return ConstraintCore(parameterName, constraint);
+ return ConstraintCore(constraint);
}
- public static RoutePatternConstraintReference Constraint(string parameterName, string constraint)
+ public static RoutePatternConstraintReference Constraint(MatchProcessor matchProcessor)
+ {
+ if (matchProcessor == null)
+ {
+ throw new ArgumentNullException(nameof(matchProcessor));
+ }
+
+ return ConstraintCore(matchProcessor);
+ }
+
+ public static RoutePatternConstraintReference Constraint(string constraint)
{
if (string.IsNullOrEmpty(constraint))
{
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(constraint));
}
- return ConstraintCore(parameterName, constraint);
+ return ConstraintCore(constraint);
}
-
- private static RoutePatternConstraintReference ConstraintCore(string parameterName, IRouteConstraint constraint)
+ private static RoutePatternConstraintReference ConstraintCore(string constraint)
{
- return new RoutePatternConstraintReference(parameterName, constraint);
+ return new RoutePatternConstraintReference(constraint);
}
- private static RoutePatternConstraintReference ConstraintCore(string parameterName, string constraint)
+ private static RoutePatternConstraintReference ConstraintCore(IRouteConstraint constraint)
{
- return new RoutePatternConstraintReference(parameterName, constraint);
+ return new RoutePatternConstraintReference(constraint);
+ }
+
+ private static RoutePatternConstraintReference ConstraintCore(MatchProcessor matchProcessor)
+ {
+ return new RoutePatternConstraintReference(matchProcessor);
}
}
}
diff --git a/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
index b3f0ee2d2e..9623f5c460 100644
--- a/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
@@ -458,6 +458,62 @@ namespace Microsoft.AspNetCore.Routing
internal static string FormatConstraintMustBeStringOrConstraint(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("ConstraintMustBeStringOrConstraint"), p0, p1, p2);
+ ///
+ /// Invalid constraint '{0}'. A constraint must be of type 'string', '{1}', or '{2}'.
+ ///
+ internal static string RoutePattern_InvalidConstraintReference
+ {
+ get => GetString("RoutePattern_InvalidConstraintReference");
+ }
+
+ ///
+ /// Invalid constraint '{0}'. A constraint must be of type 'string', '{1}', or '{2}'.
+ ///
+ internal static string FormatRoutePattern_InvalidConstraintReference(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("RoutePattern_InvalidConstraintReference"), p0, p1, p2);
+
+ ///
+ /// Invalid constraint '{0}' for parameter '{1}'. A constraint must be of type 'string', '{2}', or '{3}'.
+ ///
+ internal static string RoutePattern_InvalidParameterConstraintReference
+ {
+ get => GetString("RoutePattern_InvalidParameterConstraintReference");
+ }
+
+ ///
+ /// Invalid constraint '{0}' for parameter '{1}'. A constraint must be of type 'string', '{2}', or '{3}'.
+ ///
+ internal static string FormatRoutePattern_InvalidParameterConstraintReference(object p0, object p1, object p2, object p3)
+ => string.Format(CultureInfo.CurrentCulture, GetString("RoutePattern_InvalidParameterConstraintReference"), p0, p1, p2, p3);
+
+ ///
+ /// The constraint reference '{0}' could not be resolved to a type. Register the constraint type with '{1}.{2}'.
+ ///
+ internal static string RoutePattern_ConstraintReferenceNotFound
+ {
+ get => GetString("RoutePattern_ConstraintReferenceNotFound");
+ }
+
+ ///
+ /// The constraint reference '{0}' could not be resolved to a type. Register the constraint type with '{1}.{2}'.
+ ///
+ internal static string FormatRoutePattern_ConstraintReferenceNotFound(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("RoutePattern_ConstraintReferenceNotFound"), p0, p1, p2);
+
+ ///
+ /// Invalid constraint type '{0}' registered as '{1}'. A constraint type must either implement '{2}', or inherit from '{3}'.
+ ///
+ internal static string RoutePattern_InvalidStringConstraintReference
+ {
+ get => GetString("RoutePattern_InvalidStringConstraintReference");
+ }
+
+ ///
+ /// Invalid constraint type '{0}' registered as '{1}'. A constraint type must either implement '{2}', or inherit from '{3}'.
+ ///
+ internal static string FormatRoutePattern_InvalidStringConstraintReference(object p0, object p1, object p2, object p3)
+ => string.Format(CultureInfo.CurrentCulture, GetString("RoutePattern_InvalidStringConstraintReference"), p0, p1, p2, p3);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNetCore.Routing/Resources.resx b/src/Microsoft.AspNetCore.Routing/Resources.resx
index f5db3ec8d0..b0a3772b2c 100644
--- a/src/Microsoft.AspNetCore.Routing/Resources.resx
+++ b/src/Microsoft.AspNetCore.Routing/Resources.resx
@@ -213,4 +213,16 @@
The constraint entry '{0}' - '{1}' must have a string value or be of a type which implements '{2}'.
+
+ Invalid constraint '{0}'. A constraint must be of type 'string', '{1}', or '{2}'.
+
+
+ Invalid constraint '{0}' for parameter '{1}'. A constraint must be of type 'string', '{2}', or '{3}'.
+
+
+ The constraint reference '{0}' could not be resolved to a type. Register the constraint type with '{1}.{2}'.
+
+
+ Invalid constraint type '{0}' registered as '{1}'. A constraint type must either implement '{2}', or inherit from '{3}'.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs b/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs
index d9add483aa..d12456817d 100644
--- a/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs
+++ b/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs
@@ -120,13 +120,13 @@ namespace Microsoft.AspNetCore.Routing
{
Handler = NullRouter.Instance,
Order = endpoint.Order,
- Precedence = RoutePrecedence.ComputeOutbound(endpoint.ParsedTemplate),
- RequiredLinkValues = endpoint.RequiredValues,
- RouteTemplate = endpoint.ParsedTemplate,
+ Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
+ RequiredLinkValues = new RouteValueDictionary(endpoint.RequiredValues),
+ RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
Data = endpoint,
RouteName = routeNameMetadata?.Name,
};
- entry.Defaults = endpoint.Defaults;
+ entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
return entry;
}
diff --git a/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs b/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs
index 663f455923..672ceacfdc 100644
--- a/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs
+++ b/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs
@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Linq;
+using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template
{
@@ -36,6 +37,24 @@ namespace Microsoft.AspNetCore.Routing.Template
return precedence;
}
+ // See description on ComputeInbound(RouteTemplate)
+ internal static decimal ComputeInbound(RoutePattern routePattern)
+ {
+ var precedence = 0m;
+
+ for (var i = 0; i < routePattern.PathSegments.Count; i++)
+ {
+ var segment = routePattern.PathSegments[i];
+
+ var digit = ComputeInboundPrecedenceDigit(segment);
+ Debug.Assert(digit >= 0 && digit < 10);
+
+ precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
+ }
+
+ return precedence;
+ }
+
// Compute the precedence for generating a url
// e.g.: /api/template == 5.5
// /api/template/{id} == 5.53
@@ -60,6 +79,26 @@ namespace Microsoft.AspNetCore.Routing.Template
return precedence;
}
+ // see description on ComputeOutbound(RouteTemplate)
+ internal static decimal ComputeOutbound(RoutePattern routePattern)
+ {
+ // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
+ // and 4 results in a combined precedence of 2.14 (decimal).
+ var precedence = 0m;
+
+ for (var i = 0; i < routePattern.PathSegments.Count; i++)
+ {
+ var segment = routePattern.PathSegments[i];
+
+ var digit = ComputeOutboundPrecedenceDigit(segment);
+ Debug.Assert(digit >= 0 && digit < 10);
+
+ precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
+ }
+
+ return precedence;
+ }
+
// Segments have the following order:
// 5 - Literal segments
// 4 - Multi-part segments && Constrained parameter segments
@@ -92,6 +131,38 @@ namespace Microsoft.AspNetCore.Routing.Template
}
}
+ // See description on ComputeOutboundPrecedenceDigit(TemplateSegment segment)
+ private static int ComputeOutboundPrecedenceDigit(RoutePatternPathSegment pathSegment)
+ {
+ if (pathSegment.Parts.Count > 1)
+ {
+ return 4;
+ }
+
+ var part = pathSegment.Parts[0];
+ if (part.IsLiteral)
+ {
+ return 5;
+ }
+ else if (part is RoutePatternParameterPart parameterPart)
+ {
+ Debug.Assert(parameterPart != null);
+ var digit = parameterPart.IsCatchAll ? 1 : 3;
+
+ if (parameterPart.Constraints.Count > 0)
+ {
+ digit++;
+ }
+
+ return digit;
+ }
+ else
+ {
+ // Unreachable
+ throw new NotSupportedException();
+ }
+ }
+
// Segments have the following order:
// 1 - Literal segments
// 2 - Constrained parameter segments / Multi-part segments
@@ -127,5 +198,40 @@ namespace Microsoft.AspNetCore.Routing.Template
return digit;
}
}
+
+ // see description on ComputeInboundPrecedenceDigit(TemplateSegment segment)
+ private static int ComputeInboundPrecedenceDigit(RoutePatternPathSegment pathSegment)
+ {
+ if (pathSegment.Parts.Count > 1)
+ {
+ // Multi-part segments should appear after literal segments and along with parameter segments
+ return 2;
+ }
+
+ var part = pathSegment.Parts[0];
+ // Literal segments always go first
+ if (part.IsLiteral)
+ {
+ return 1;
+ }
+ else if (part is RoutePatternParameterPart parameterPart)
+ {
+ var digit = parameterPart.IsCatchAll ? 5 : 3;
+
+ // If there is a route constraint for the parameter, reduce order by 1
+ // Constrained parameters end up with order 2, Constrained catch alls end up with order 4
+ if (parameterPart.Constraints.Count > 0)
+ {
+ digit--;
+ }
+
+ return digit;
+ }
+ else
+ {
+ // Unreachable
+ throw new NotSupportedException();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs b/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs
index c8ba0b319c..f42cbfa011 100644
--- a/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs
+++ b/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs
@@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Routing.Template
RoutePatternParameterKind.Optional :
RoutePatternParameterKind.Standard;
- var constraints = InlineConstraints.Select(c => new RoutePatternConstraintReference(Name, c.Constraint));
+ var constraints = InlineConstraints.Select(c => new RoutePatternConstraintReference(c.Constraint));
return RoutePatternFactory.ParameterPart(Name, DefaultValue, kind, constraints);
}
}
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs
index 6f5978af52..74a975b2cd 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing.Matchers;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Primitives;
using Xunit;
@@ -147,19 +148,15 @@ namespace Microsoft.AspNetCore.Routing
private MatcherEndpoint CreateEndpoint(
string template,
- object defaultValues = null,
+ object defaults = null,
object requiredValues = null,
int order = 0,
string routeName = null)
{
- var defaults = defaultValues == null ? new RouteValueDictionary() : new RouteValueDictionary(defaultValues);
- var required = requiredValues == null ? new RouteValueDictionary() : new RouteValueDictionary(requiredValues);
-
return new MatcherEndpoint(
- next => (httpContext) => Task.CompletedTask,
- template,
- defaults,
- required,
+ MatcherEndpoint.EmptyInvoker,
+ RoutePatternFactory.Parse(template, defaults, constraints: null),
+ new RouteValueDictionary(requiredValues),
order,
EndpointMetadataCollection.Empty,
null);
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
index 66397121d3..ff6d0ac25c 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.EndpointFinders;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matchers;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool;
@@ -254,12 +255,11 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var context = CreateRouteValuesContext(new { p1 = "abcd" });
var linkGenerator = CreateLinkGenerator();
- var matchProcessorReferences = new List();
- matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}")));
+
var endpoint = CreateEndpoint(
"{p1}/{p2}",
new { p2 = "catchall" },
- matchProcessorReferences: matchProcessorReferences);
+ constraints: new { p2 = "\\d{4}" });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@@ -279,12 +279,11 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" });
var linkGenerator = CreateLinkGenerator();
- var matchProcessorReferences = new List();
- matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}")));
+
var endpoint = CreateEndpoint(
"{p1}/{p2}",
new { p2 = "catchall" },
- matchProcessorReferences);
+ new { p2 = new RegexRouteConstraint("\\d{4}"), });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@@ -305,12 +304,11 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var context = CreateRouteValuesContext(new { p1 = "abcd" });
var linkGenerator = CreateLinkGenerator();
- var matchProcessorReferences = new List();
- matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}")));
+
var endpoint = CreateEndpoint(
"{p1}/{*p2}",
new { p2 = "catchall" },
- matchProcessorReferences: matchProcessorReferences);
+ new { p2 = new RegexRouteConstraint("\\d{4}") });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@@ -330,12 +328,11 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" });
var linkGenerator = CreateLinkGenerator();
- var matchProcessorReferences = new List();
- matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}")));
+
var endpoint = CreateEndpoint(
"{p1}/{*p2}",
new { p2 = "catchall" },
- matchProcessorReferences);
+ new { p2 = new RegexRouteConstraint("\\d{4}") });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@@ -370,11 +367,8 @@ namespace Microsoft.AspNetCore.Routing
var endpoint = CreateEndpoint(
"{p1}/{p2}",
- defaultValues: new { p2 = "catchall" },
- matchProcessorReferences: new List
- {
- new MatchProcessorReference("p2", target.Object)
- });
+ defaults: new { p2 = "catchall" },
+ constraints: new { p2 = target.Object });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@@ -400,11 +394,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
template: "slug/Home/Store",
- defaultValues: new { controller = "Home", action = "Store" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("c", constraint)
- });
+ defaults: new { controller = "Home", action = "Store" },
+ constraints: new { c = constraint });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store" },
@@ -437,11 +428,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
template: "slug/Home/Store",
- defaultValues: new { controller = "Home", action = "Store", otherthing = "17" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("c", constraint)
- });
+ defaults: new { controller = "Home", action = "Store", otherthing = "17" },
+ constraints: new { c = constraint });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store" },
@@ -471,11 +459,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
template: "slug/{controller}/{action}",
- defaultValues: new { action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("c", constraint)
- });
+ defaults: new { action = "Index" },
+ constraints: new { c = constraint, });
var context = CreateRouteValuesContext(
suppliedValues: new { controller = "Shopping" },
@@ -506,11 +491,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
template: "slug/Home/Store",
- defaultValues: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("c", constraint)
- });
+ defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
+ constraints: new { c = constraint, });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store", thirdthing = "13" },
@@ -537,12 +519,10 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
- template: "Home/Index/{id}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("id", "int")
- });
+ template: "Home/Index/{id:int}",
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new { });
+
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 4 });
@@ -564,11 +544,9 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
template: "Home/Index/{id}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("id", "int")
- });
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new {id = "int"});
+
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
@@ -590,12 +568,9 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
- template: "Home/Index/{id}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("id", optional: true, "int")
- });
+ template: "Home/Index/{id:int?}",
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new { });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 98 });
@@ -617,11 +592,9 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
template: "Home/Index/{id?}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("id", optional: true, "int")
- });
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new { id = "int" });
+
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" });
@@ -642,12 +615,10 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
- template: "Home/Index/{id}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("id", optional: true, "int")
- });
+ template: "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new { id = "int" });
+
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
@@ -664,18 +635,15 @@ namespace Microsoft.AspNetCore.Routing
}
[Fact]
- public void GetLink_InlineConstraints_CompositeInlineConstraint()
+ public void GetLink_InlineConstraints_MultipleInlineConstraints()
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
- template: "Home/Index/{id}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("id", "int"),
- new MatchProcessorReference("id", "range(1,20)")
- });
+ template: "Home/Index/{id:int:range(1,20)}",
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new { });
+
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 14 });
@@ -696,13 +664,10 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
- template: "Home/Index/{id}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("id", "int"),
- new MatchProcessorReference("id", "range(1,20)")
- });
+ template: "Home/Index/{id:int:range(1,20)}",
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new { });
+
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 50 });
@@ -726,11 +691,9 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
template: "Home/Index/{name}",
- defaultValues: new { controller = "Home", action = "Index" },
- matchProcessorReferences: new List()
- {
- new MatchProcessorReference("name", constraint)
- });
+ defaults: new { controller = "Home", action = "Index" },
+ constraints: new { name = constraint });
+
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" });
@@ -785,50 +748,6 @@ namespace Microsoft.AspNetCore.Routing
Assert.Equal("/Home/Index", link);
}
- [Fact]
- public void GetLink_OptionalParameter_ParameterPresentInValuesAndDefaults()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- template: "{controller}/{action}/{name?}",
- defaultValues: new { name = "default-products" });
- var linkGenerator = CreateLinkGenerator();
- var context = CreateRouteValuesContext(
- suppliedValues: new { action = "Index", controller = "Home", name = "products" });
-
- // Act
- var link = linkGenerator.GetLink(
- httpContext: null,
- new[] { endpoint },
- context.ExplicitValues,
- context.AmbientValues);
-
- // Assert
- Assert.Equal("/Home/Index/products", link);
- }
-
- [Fact]
- public void GetLink_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- template: "{controller}/{action}/{name?}",
- defaultValues: new { name = "products" });
- var linkGenerator = CreateLinkGenerator();
- var context = CreateRouteValuesContext(
- suppliedValues: new { action = "Index", controller = "Home" });
-
- // Act
- var link = linkGenerator.GetLink(
- httpContext: null,
- new[] { endpoint },
- context.ExplicitValues,
- context.AmbientValues);
-
- // Assert
- Assert.Equal("/Home/Index", link);
- }
-
[Fact]
public void GetLink_ParameterNotPresentInTemplate_PresentInValues()
{
@@ -958,7 +877,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
{
// Arrange
- var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}", defaultValues: new { });
+ var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { },
@@ -985,23 +904,15 @@ namespace Microsoft.AspNetCore.Routing
private MatcherEndpoint CreateEndpoint(
string template,
- object defaultValues = null,
- object requiredValues = null,
- List matchProcessorReferences = null,
- int order = 0,
- EndpointMetadataCollection metadata = null)
+ object defaults = null,
+ object constraints = null,
+ int order = 0,
+ EndpointMetadataCollection metadata = null)
{
- var defaults = defaultValues == null ? new RouteValueDictionary() : new RouteValueDictionary(defaultValues);
- var required = requiredValues == null ? new RouteValueDictionary() : new RouteValueDictionary(requiredValues);
- metadata = metadata ?? EndpointMetadataCollection.Empty;
- matchProcessorReferences = matchProcessorReferences ?? new List();
-
return new MatcherEndpoint(
- next => (httpContext) => Task.CompletedTask,
- template,
- defaults,
- required,
- matchProcessorReferences,
+ MatcherEndpoint.EmptyInvoker,
+ RoutePatternFactory.Parse(template, defaults, constraints),
+ new RouteValueDictionary(),
order,
metadata,
null);
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/BarebonesMatcherBuilder.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/BarebonesMatcherBuilder.cs
index ddb0b71dc8..efd9c588ce 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/BarebonesMatcherBuilder.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/BarebonesMatcherBuilder.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template;
using static Microsoft.AspNetCore.Routing.Matchers.BarebonesMatcher;
@@ -22,11 +23,11 @@ namespace Microsoft.AspNetCore.Routing.Matchers
var matchers = new InnerMatcher[_endpoints.Count];
for (var i = 0; i < _endpoints.Count; i++)
{
- var parsed = TemplateParser.Parse(_endpoints[i].Template);
- var segments = parsed.Segments
- .Select(s => s.IsSimple && s.Parts[0].IsLiteral ? s.Parts[0].Text : null)
+ var endpoint = _endpoints[i];
+ var pathSegments = endpoint.RoutePattern.PathSegments
+ .Select(s => s.IsSimple && s.Parts[0] is RoutePatternLiteralPart literalPart ? literalPart.Content : null)
.ToArray();
- matchers[i] = new InnerMatcher(segments, _endpoints[i]);
+ matchers[i] = new InnerMatcher(pathSegments, _endpoints[i]);
}
return new BarebonesMatcher(matchers);
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DataSourceDependentMatcherTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DataSourceDependentMatcherTest.cs
index e165f65cb5..9eb4fa48a7 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DataSourceDependentMatcherTest.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DataSourceDependentMatcherTest.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
using Xunit;
@@ -35,8 +36,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
var endpoint = new MatcherEndpoint(
MatcherEndpoint.EmptyInvoker,
- "a/b/c",
- new RouteValueDictionary(),
+ RoutePatternFactory.Parse("a/b/c"),
new RouteValueDictionary(),
0,
EndpointMetadataCollection.Empty,
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DefaultMatchProcessorFactoryTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DefaultMatchProcessorFactoryTest.cs
index 5207607531..bcacb1a007 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DefaultMatchProcessorFactoryTest.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DefaultMatchProcessorFactoryTest.cs
@@ -4,10 +4,10 @@
using System;
using System.Globalization;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing.Constraints;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
-using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Routing.Matchers
@@ -19,11 +19,153 @@ namespace Microsoft.AspNetCore.Routing.Matchers
{
// Arrange
var factory = GetMatchProcessorFactory();
- var matchProcessorReference = new MatchProcessorReference("id", @"notpresent(\d+)");
- // Act & Assert
+ // Act
var exception = Assert.Throws(
- () => factory.Create(matchProcessorReference));
+ () => factory.Create("id", @"notpresent(\d+)", optional: false));
+
+ // Assert
+ Assert.Equal(
+ $"The constraint reference 'notpresent' could not be resolved to a type. " +
+ $"Register the constraint type with '{typeof(RouteOptions)}.{nameof(RouteOptions.ConstraintMap)}'.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void Create_ThrowsException_OnInvalidType()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("bad", typeof(string));
+
+ var services = new ServiceCollection();
+ services.AddTransient();
+
+ var factory = GetMatchProcessorFactory(options, services);
+
+ // Act
+ var exception = Assert.Throws(
+ () => factory.Create("id", @"bad", optional: false));
+
+ // Assert
+ Assert.Equal(
+ $"Invalid constraint type '{typeof(string)}' registered as 'bad'. " +
+ $"A constraint type must either implement '{typeof(IRouteConstraint)}', or inherit from '{typeof(MatchProcessor)}'.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void Create_CreatesMatchProcessor_FromRoutePattern_String()
+ {
+ // Arrange
+ var factory = GetMatchProcessorFactory();
+
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Standard,
+ constraints: new[] { RoutePatternFactory.Constraint("int"), });
+
+ // Act
+ var matchProcessor = factory.Create(parameter, parameter.Constraints[0]);
+
+ // Assert
+ Assert.IsType(Assert.IsType(matchProcessor).Constraint);
+ }
+
+ [Fact]
+ public void Create_CreatesMatchProcessor_FromRoutePattern_String_Optional()
+ {
+ // Arrange
+ var factory = GetMatchProcessorFactory();
+
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Optional,
+ constraints: new[] { RoutePatternFactory.Constraint("int"), });
+
+ // Act
+ var matchProcessor = factory.Create(parameter, parameter.Constraints[0]);
+
+ // Assert
+ Assert.IsType(matchProcessor);
+ }
+
+ [Fact]
+ public void Create_CreatesMatchProcessor_FromRoutePattern_Constraint()
+ {
+ // Arrange
+ var factory = GetMatchProcessorFactory();
+
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Standard,
+ constraints: new[] { RoutePatternFactory.Constraint(new IntRouteConstraint()), });
+
+ // Act
+ var matchProcessor = factory.Create(parameter, parameter.Constraints[0]);
+
+ // Assert
+ Assert.IsType(Assert.IsType(matchProcessor).Constraint);
+ }
+
+ [Fact]
+ public void Create_CreatesMatchProcessor_FromRoutePattern_Constraint_Optional()
+ {
+ // Arrange
+ var factory = GetMatchProcessorFactory();
+
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Optional,
+ constraints: new[] { RoutePatternFactory.Constraint(new IntRouteConstraint()), });
+
+ // Act
+ var matchProcessor = factory.Create(parameter, parameter.Constraints[0]);
+
+ // Assert
+ Assert.IsType(matchProcessor);
+ }
+
+ [Fact]
+ public void Create_CreatesMatchProcessor_FromRoutePattern_MatchProcessor()
+ {
+ // Arrange
+ var factory = GetMatchProcessorFactory();
+
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Standard,
+ constraints: new[] { RoutePatternFactory.Constraint(new EndsWithStringMatchProcessor()), });
+
+ // Act
+ var matchProcessor = factory.Create(parameter, parameter.Constraints[0]);
+
+ // Assert
+ Assert.IsType(matchProcessor);
+ }
+
+ [Fact]
+ public void Create_CreatesMatchProcessor_FromRoutePattern_MatchProcessor_Optional()
+ {
+ // Arrange
+ var factory = GetMatchProcessorFactory();
+
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Optional,
+ constraints: new[] { RoutePatternFactory.Constraint(new EndsWithStringMatchProcessor()), });
+
+ // Act
+ var matchProcessor = factory.Create(parameter, parameter.Constraints[0]);
+
+ // Assert
+ Assert.IsType(matchProcessor);
}
[Fact]
@@ -31,29 +173,12 @@ namespace Microsoft.AspNetCore.Routing.Matchers
{
// Arrange
var factory = GetMatchProcessorFactory();
- var matchProcessorReference = new MatchProcessorReference("id", "int");
- // Act 1
- var processor = factory.Create(matchProcessorReference);
+ // Act
+ var matchProcessor = factory.Create("id", "int", optional: false);
- // Assert 1
- Assert.NotNull(processor);
-
- // Act 2
- var isMatch = processor.ProcessInbound(
- new DefaultHttpContext(),
- new RouteValueDictionary(new { id = 10 }));
-
- // Assert 2
- Assert.True(isMatch);
-
- // Act 2
- isMatch = processor.ProcessInbound(
- new DefaultHttpContext(),
- new RouteValueDictionary(new { id = "foo" }));
-
- // Assert 2
- Assert.False(isMatch);
+ // Assert
+ Assert.IsType(Assert.IsType(matchProcessor).Constraint);
}
[Fact]
@@ -61,84 +186,50 @@ namespace Microsoft.AspNetCore.Routing.Matchers
{
// Arrange
var factory = GetMatchProcessorFactory();
- var matchProcessorReference = new MatchProcessorReference("id", true, "int");
// Act
- var processor = factory.Create(matchProcessorReference);
+ var matchProcessor = factory.Create("id", "int", optional: true);
// Assert
- Assert.IsType(processor);
+ Assert.IsType(matchProcessor);
}
[Fact]
- public void Create_CreatesMatchProcessor_FromConstraintText_AndCustomMatchProcessor()
+ public void Create_CreatesMatchProcessor_FromConstraintText_AndMatchProcesor()
{
// Arrange
var options = new RouteOptions();
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor));
+
var services = new ServiceCollection();
services.AddTransient();
+
var factory = GetMatchProcessorFactory(options, services);
- var matchProcessorReference = new MatchProcessorReference("id", "endsWith(_001)");
- // Act 1
- var processor = factory.Create(matchProcessorReference);
+ // Act
+ var matchProcessor = factory.Create("id", "endsWith", optional: false);
- // Assert 1
- Assert.NotNull(processor);
-
- // Act 2
- var isMatch = processor.ProcessInbound(
- new DefaultHttpContext(),
- new RouteValueDictionary(new { id = "555_001" }));
-
- // Assert 2
- Assert.True(isMatch);
-
- // Act 2
- isMatch = processor.ProcessInbound(
- new DefaultHttpContext(),
- new RouteValueDictionary(new { id = "444" }));
-
- // Assert 2
- Assert.False(isMatch);
+ // Assert
+ Assert.IsType(matchProcessor);
}
[Fact]
- public void Create_ReturnsMatchProcessor_IfAvailable()
+ public void Create_CreatesMatchProcessor_FromConstraintText_AndMatchProcessor_Optional()
{
// Arrange
- var factory = GetMatchProcessorFactory();
- var matchProcessorReference = new MatchProcessorReference("id", Mock.Of());
- var expected = matchProcessorReference.MatchProcessor;
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor));
+
+ var services = new ServiceCollection();
+ services.AddTransient();
+
+ var factory = GetMatchProcessorFactory(options, services);
// Act
- var processor = factory.Create(matchProcessorReference);
+ var matchProcessor = factory.Create("id", "endsWith", optional: true);
// Assert
- Assert.Same(expected, processor);
- }
-
- [Fact]
- public void Create_ReturnsMatchProcessor_WithSuppliedRouteConstraint()
- {
- // Arrange
- var factory = GetMatchProcessorFactory();
- var constraint = TestRouteConstraint.Create();
- var matchProcessorReference = new MatchProcessorReference("id", constraint);
- var processor = factory.Create(matchProcessorReference);
- var expectedHttpContext = new DefaultHttpContext();
- var expectedValues = new RouteValueDictionary();
-
- // Act
- processor.ProcessInbound(expectedHttpContext, expectedValues);
-
- // Assert
- Assert.Same(expectedHttpContext, constraint.HttpContext);
- Assert.Same(expectedValues, constraint.Values);
- Assert.Equal("id", constraint.RouteKey);
- Assert.Equal(RouteDirection.IncomingRequest, constraint.RouteDirection);
- Assert.Same(NullRouter.Instance, constraint.Route);
+ Assert.IsType(matchProcessor);
}
private DefaultMatchProcessorFactory GetMatchProcessorFactory(
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DfaMatcherBuilderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DfaMatcherBuilderTest.cs
index 5cbc613562..b8a064b59a 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DfaMatcherBuilderTest.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/DfaMatcherBuilderTest.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.EndpointConstraints;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
@@ -575,10 +576,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
public void CreateCandidate_MatchProcessors()
{
// Arrange
- var endpoint = CreateEndpoint("/a/b/c", matchProcessors: new MatchProcessorReference[]
- {
- new MatchProcessorReference("a", new IntRouteConstraint()),
- });
+ var endpoint = CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), });
var builder = CreateDfaMatcherBuilder();
@@ -606,18 +604,14 @@ namespace Microsoft.AspNetCore.Routing.Matchers
}
private MatcherEndpoint CreateEndpoint(
- string template,
+ string template,
object defaults = null,
- IEnumerable matchProcessors = null)
+ object constraints = null)
{
- matchProcessors = matchProcessors ?? Array.Empty();
-
return new MatcherEndpoint(
MatcherEndpoint.EmptyInvoker,
- template,
- new RouteValueDictionary(defaults),
+ RoutePatternFactory.Parse(template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints)),
new RouteValueDictionary(),
- matchProcessors.ToList(),
0,
new EndpointMetadataCollection(Array.Empty