Add more matcher tests
This is a code dump of existing tests for TemplateMatcher and TreeRouter converted to the format of matcher conformance tests. Note that most of the new tests aren't yet supported by our experimental matchers, which don't support many of these advanced features.
This commit is contained in:
parent
5b8db03a57
commit
db95a8c624
|
|
@ -23,6 +23,13 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Route values not supported
|
||||
[Fact]
|
||||
public override Task Match_SingleParameter_WierdNames()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Route values not supported
|
||||
[Theory]
|
||||
[InlineData(null, null, null, null)]
|
||||
|
|
@ -32,10 +39,13 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
internal override Matcher CreateMatcher(params MatcherEndpoint[] endpoints)
|
||||
{
|
||||
var builder = new BarebonesMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
for (int i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
builder.AddEndpoint(endpoints[i]);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,17 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class DfaMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
// Route values not supported
|
||||
[Fact]
|
||||
public override Task Match_SingleParameter_TrailingSlash()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Route values not supported
|
||||
[Theory]
|
||||
[InlineData(null, null, null, null)]
|
||||
public override Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
GC.KeepAlive(new object[] { template, path, keys, values });
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
internal override Matcher CreateMatcher(params MatcherEndpoint[] endpoints)
|
||||
{
|
||||
var builder = new DfaMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
for (int i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
builder.AddEndpoint(endpoints[i]);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit.Sdk;
|
||||
|
|
@ -14,8 +15,21 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
AssertMatch(feature, expected, new RouteValueDictionary());
|
||||
}
|
||||
|
||||
public static void AssertMatch(IEndpointFeature feature, Endpoint expected, bool ignoreValues)
|
||||
{
|
||||
AssertMatch(feature, expected, new RouteValueDictionary(), ignoreValues);
|
||||
}
|
||||
|
||||
public static void AssertMatch(IEndpointFeature feature, Endpoint expected, object values)
|
||||
{
|
||||
AssertMatch(feature, expected, new RouteValueDictionary(values));
|
||||
}
|
||||
|
||||
public static void AssertMatch(IEndpointFeature feature, Endpoint expected, string[] keys, string[] values)
|
||||
{
|
||||
keys = keys ?? Array.Empty<string>();
|
||||
values = values ?? Array.Empty<string>();
|
||||
|
||||
if (keys.Length != values.Length)
|
||||
{
|
||||
throw new XunitException($"Keys and Values must be the same length.");
|
||||
|
|
@ -25,7 +39,11 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
AssertMatch(feature, expected, new RouteValueDictionary(zipped));
|
||||
}
|
||||
|
||||
public static void AssertMatch(IEndpointFeature feature, Endpoint expected, RouteValueDictionary values)
|
||||
public static void AssertMatch(
|
||||
IEndpointFeature feature,
|
||||
Endpoint expected,
|
||||
RouteValueDictionary values,
|
||||
bool ignoreValues = false)
|
||||
{
|
||||
if (feature.Endpoint == null)
|
||||
{
|
||||
|
|
@ -44,14 +62,17 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
$"'{feature.Endpoint.DisplayName}' with values: {FormatRouteValues(feature.Values)}.");
|
||||
}
|
||||
|
||||
// Note: this comparison is intended for unit testing, and is stricter than necessary to make tests
|
||||
// more precise. Route value comparisons in product code are more flexible than a simple .Equals.
|
||||
if (values.Count != feature.Values.Count ||
|
||||
!values.OrderBy(kvp => kvp.Key).SequenceEqual(feature.Values.OrderBy(kvp => kvp.Key)))
|
||||
if (!ignoreValues)
|
||||
{
|
||||
throw new XunitException(
|
||||
$"Was expected to match '{expected.DisplayName}' with values {FormatRouteValues(values)} but matched " +
|
||||
$"values: {FormatRouteValues(feature.Values)}.");
|
||||
// Note: this comparison is intended for unit testing, and is stricter than necessary to make tests
|
||||
// more precise. Route value comparisons in product code are more flexible than a simple .Equals.
|
||||
if (values.Count != feature.Values.Count ||
|
||||
!values.OrderBy(kvp => kvp.Key).SequenceEqual(feature.Values.OrderBy(kvp => kvp.Key)))
|
||||
{
|
||||
throw new XunitException(
|
||||
$"Was expected to match '{expected.DisplayName}' with values {FormatRouteValues(values)} but matched " +
|
||||
$"values: {FormatRouteValues(feature.Values)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,437 @@
|
|||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
// This class includes features that we have not yet implemented in the DFA
|
||||
// and instruction matchers.
|
||||
//
|
||||
// As those matchers add features we can move tests from this class into
|
||||
// MatcherConformanceTest and delete this.
|
||||
public abstract class FullFeaturedMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("/a/{b=15}", "/a/b", new string[] { "b", }, new string[] { "b", })]
|
||||
[InlineData("/a/{b=15}", "/a/", new string[] { "b", }, new string[] { "15", })]
|
||||
[InlineData("/a/{b=15}", "/a", new string[] { "b", }, new string[] { "15", })]
|
||||
[InlineData("/{a}/{b=15}", "/54/b", new string[] { "a", "b", }, new string[] { "54", "b", })]
|
||||
[InlineData("/{a=19}/{b=15}", "/54/b", new string[] { "a", "b", }, new string[] { "54", "b", })]
|
||||
[InlineData("/{a=19}/{b=15}", "/54/", new string[] { "a", "b", }, new string[] { "54", "15", })]
|
||||
[InlineData("/{a=19}/{b=15}", "/54", new string[] { "a", "b", }, new string[] { "54", "15", })]
|
||||
[InlineData("/{a=19}/{b=15}", "/", new string[] { "a", "b", }, new string[] { "19", "15", })]
|
||||
public virtual async Task Match_DefaultValues(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_NonInlineDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint = CreateEndpoint("/a/{b}/{c}", new { b = "17", c = "18", });
|
||||
var matcher = CreateMatcher(endpoint);
|
||||
var (httpContext, feature) = CreateContext("/a");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, new { b = "17", c = "18", });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_ExtraDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint = CreateEndpoint("/a/{b}/{c}", new { b = "17", c = "18", d = "19" });
|
||||
var matcher = CreateMatcher(endpoint);
|
||||
var (httpContext, feature) = CreateContext("/a");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, new { b = "17", c = "18", d = "19" });
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/a/{b=15}", "/54/b")]
|
||||
[InlineData("/a/{b=15}", "/54/")]
|
||||
[InlineData("/a/{b=15}", "/54")]
|
||||
[InlineData("/a/{b=15}", "/a//")]
|
||||
[InlineData("/a/{b=15}", "/54/43/23")]
|
||||
[InlineData("/{a=19}/{b=15}", "/54/b/c")]
|
||||
[InlineData("/a/{b=15}/c", "/a/b")] // Intermediate default values don't act like optional segments
|
||||
[InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a")]
|
||||
[InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b")]
|
||||
[InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c")]
|
||||
[InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d")]
|
||||
public virtual async Task NotMatch_DefaultValues(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/", null, null)]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a", new[] { "a", }, new[] { "a", })]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a/", new[] { "a", }, new[] { "a", })]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a/b", new[] { "a", "b", }, new[] { "a", "b", })]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a/b/", new[] { "a", "b", }, new[] { "a", "b", })]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a/b/c", new[] { "a", "b", "c", }, new[] { "a", "b", "c", })]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a/b/c/", new[] { "a", "b", "c", }, new[] { "a", "b", "c", })]
|
||||
[InlineData("/{c}/{a?}", "/h/i", new[] { "c", "a", }, new[] { "h", "i", })]
|
||||
[InlineData("/{c}/{a?}", "/h/", new[] { "c", }, new[] { "h", })]
|
||||
[InlineData("/{c}/{a?}", "/h", new[] { "c", }, new[] { "h", })]
|
||||
[InlineData("/{c?}/{a?}", "/", null, null)]
|
||||
[InlineData("/{c}/{a?}/{id?}", "/h/i/18", new[] { "c", "a", "id", }, new[] { "h", "i", "18", })]
|
||||
[InlineData("/{c}/{a?}/{id?}", "/h/i", new[] { "c", "a", }, new[] { "h", "i", })]
|
||||
[InlineData("/{c}/{a?}/{id?}", "/h", new[] { "c", }, new[] { "h", })]
|
||||
[InlineData("template/{p:int?}", "/template/5", new[] { "p", }, new[] { "5", })]
|
||||
[InlineData("template/{p:int?}", "/template", null, null)]
|
||||
[InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e", new[] { "b", "d", "f" }, new[] { "b", "d", null, })]
|
||||
[InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e/f", new[] { "b", "d", "f", }, new[] { "b", "d", "f", })]
|
||||
public virtual async Task Match_OptionalParameter(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "///")]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a//")]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a/b//")]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "//b//")]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "///c")]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "///c/")]
|
||||
[InlineData("/{a?}/{b?}/{c?}", "/a/b/c/d")]
|
||||
[InlineData("/a/{b?}/{c?}", "/")]
|
||||
[InlineData("template/{parameter:int?}", "/template/qwer")]
|
||||
public virtual async Task NotMatch_OptionalParameter(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a}/{*b}", "/a", new[] { "a", "b", }, new[] { "a", null, })]
|
||||
[InlineData("/{a}/{*b}", "/a/", new[] { "a", "b", }, new[] { "a", null, })]
|
||||
[InlineData("/{a}/{*b=b}", "/a", new[] { "a", "b", }, new[] { "a", "b", })]
|
||||
[InlineData("/{a}/{*b=b}", "/a/", new[] { "a", "b", }, new[] { "a", "b", })]
|
||||
[InlineData("/{a}/{*b=b}", "/a/hello", new[] { "a", "b", }, new[] { "a", "hello", })]
|
||||
[InlineData("/{a}/{*b=b}", "/a/hello/goodbye", new[] { "a", "b", }, new[] { "a", "hello/goodbye", })]
|
||||
[InlineData("/{a}/{*b=b}", "/a/b//", new[] { "a", "b", }, new[] { "a", "b//", })]
|
||||
[InlineData("/{a}/{*b=b}", "/a/b/c/", new[] { "a", "b", }, new[] { "a", "b/c/", })]
|
||||
[InlineData("/{a=1}/{b=2}/{c=3}/{d=4}", "/a/b/c", new[] { "a", "b", "c", "d", }, new[] { "a", "b", "c", "4", })]
|
||||
[InlineData("a/{*path:regex(10/20/30)}", "/a/10/20/30", new[] { "path", }, new[] { "10/20/30" })]
|
||||
public virtual async Task Match_CatchAllParameter(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a}/{*b=b}", "/a///")]
|
||||
[InlineData("/{a}/{*b=b}", "/a//c/")]
|
||||
public virtual async Task NotMatch_CatchAllParameter(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{p}x{s}", "/xxxxxxxxxx", new[] { "p", "s" }, new[] { "xxxxxxxx", "x", })]
|
||||
[InlineData("{p}xyz{s}", "/xxxxyzxyzxxxxxxyz", new[] { "p", "s" }, new[] { "xxxxyz", "xxxxxxyz", })]
|
||||
[InlineData("{p}xyz{s}", "/abcxxxxyzxyzxxxxxxyzxx", new[] { "p", "s" }, new[] { "abcxxxxyzxyzxxxxx", "xx", })]
|
||||
[InlineData("{p}xyz{s}", "/xyzxyzxyzxyzxyz", new[] { "p", "s" }, new[] { "xyzxyzxyz", "xyz", })]
|
||||
[InlineData("{p}xyz{s}", "/xyzxyzxyzxyzxyz1", new[] { "p", "s" }, new[] { "xyzxyzxyzxyz", "1", })]
|
||||
[InlineData("{p}xyz{s}", "/xyzxyzxyz", new[] { "p", "s" }, new[] { "xyz", "xyz", })]
|
||||
[InlineData("{p}aa{s}", "/aaaaa", new[] { "p", "s" }, new[] { "aa", "a", })]
|
||||
[InlineData("{p}aaa{s}", "/aaaaa", new[] { "p", "s" }, new[] { "a", "a", })]
|
||||
[InlineData("language/{lang=en}-{region=US}", "/language/xx-yy", new[] { "lang", "region" }, new[] { "xx", "yy", })]
|
||||
[InlineData("language/{lang}-{region}", "/language/en-US", new[] { "lang", "region" }, new[] { "en", "US", })]
|
||||
[InlineData("language/{lang}-{region}a", "/language/en-USa", new[] { "lang", "region" }, new[] { "en", "US", })]
|
||||
[InlineData("language/a{lang}-{region}", "/language/aen-US", new[] { "lang", "region" }, new[] { "en", "US", })]
|
||||
[InlineData("language/a{lang}-{region}a", "/language/aen-USa", new[] { "lang", "region" }, new[] { "en", "US", })]
|
||||
[InlineData("language/{lang}-", "/language/en-", new[] { "lang", }, new[] { "en", })]
|
||||
[InlineData("language/a{lang}", "/language/aen", new[] { "lang", }, new[] { "en", })]
|
||||
[InlineData("language/a{lang}a", "/language/aena", new[] { "lang", }, new[] { "en", })]
|
||||
public virtual async Task Match_ComplexSegment(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("language/a{lang}-{region}a", "/language/a-USa")]
|
||||
[InlineData("language/a{lang}-{region}a", "/language/aen-a")]
|
||||
[InlineData("language/{lang=en}-{region=US}", "/language")]
|
||||
[InlineData("language/{lang=en}-{region=US}", "/language/-")]
|
||||
[InlineData("language/{lang=en}-{region=US}", "/language/xx-")]
|
||||
[InlineData("language/{lang=en}-{region=US}", "/language/-xx")]
|
||||
public virtual async Task NotMatch_ComplexSegment(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", new[] { "p1", "p2", }, new[] { "foo", "bar" })]
|
||||
[InlineData("moo/{p1}.{p2?}", "/moo/foo", new[] { "p1", }, new[] { "foo", })]
|
||||
[InlineData("moo/{p1}.{p2?}", "/moo/.foo", new[] { "p1", }, new[] { ".foo", })]
|
||||
[InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", new[] { "p1", "p2", }, new[] { "foo.", "bar" })]
|
||||
[InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", new[] { "p1", "p2", }, new[] { "foo.moo", "bar" })]
|
||||
[InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", new[] { "p1", }, new[] { "moo", })]
|
||||
[InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.foo.bar", new[] { "p1", "p2", }, new[] { "foo", "bar" })]
|
||||
[InlineData("moo/.{p2?}", "/moo/.foo", new[] { "p2", }, new[] { "foo", })]
|
||||
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
|
||||
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", new[] { "p1", "p2", }, new[] { "foo", "moo" })]
|
||||
[InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "/foo/bar", new[] { "p1", "p3" }, new[] { "foo", "bar" })]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", new[] { "p1", "p3" }, new[] { ".foo", "bar" })]
|
||||
[InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", new[] { "p1", "p2", "p3" }, new[] { "foo", "bar", "baz" })]
|
||||
public virtual async Task Match_OptionalSeparator(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
|
||||
[InlineData("moo/{p1}.{p2?}", "/moo/.")]
|
||||
[InlineData("moo/{p1}.{p2}", "/foo.")]
|
||||
[InlineData("moo/{p1}.{p2}", "/foo")]
|
||||
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
|
||||
[InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
|
||||
[InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
|
||||
[InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
|
||||
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
|
||||
[InlineData("moo/.{p2?}", "/moo/.")]
|
||||
[InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
|
||||
public virtual async Task NotMatch_OptionalSeparator(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
// Most of are copied from old routing tests that date back to the VS 2010 era. Enjoy!
|
||||
[Theory]
|
||||
[InlineData("{Controller}.mvc/../{action}", "/Home.mvc/../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
|
||||
[InlineData("{Controller}.mvc/.../{action}", "/Home.mvc/.../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
|
||||
[InlineData("{Controller}.mvc/../../../{action}", "/Home.mvc/../../../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
|
||||
[InlineData("{Controller}.mvc!/{action}", "/Home.mvc!/index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
|
||||
[InlineData("../{Controller}.mvc", "/../Home.mvc", new string[] { "Controller", }, new string[] { "Home", })]
|
||||
[InlineData(@"\{Controller}.mvc", @"/\Home.mvc", new string[] { "Controller", }, new string[] { "Home", })]
|
||||
[InlineData(@"{Controller}.mvc\{id}\{Param1}", @"/Home.mvc\123\p1", new string[] { "Controller", "id", "Param1" }, new string[] { "Home", "123", "p1" })]
|
||||
[InlineData("(Controller).mvc", "/(Controller).mvc", new string[] { }, new string[] { })]
|
||||
[InlineData("Controller.mvc/ ", "/Controller.mvc/ ", new string[] { }, new string[] { })]
|
||||
[InlineData("Controller.mvc ", "/Controller.mvc ", new string[] { }, new string[] { })]
|
||||
public virtual async Task Match_WierdCharacterCases(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("template/5", "template/{parameter:int}")]
|
||||
[InlineData("template/5", "template/{parameter}")]
|
||||
[InlineData("template/5", "template/{*parameter:int}")]
|
||||
[InlineData("template/5", "template/{*parameter}")]
|
||||
[InlineData("template/{parameter}", "template/{parameter:alpha}")] // constraint doesn't match
|
||||
[InlineData("template/{parameter:int}", "template/{parameter}")]
|
||||
[InlineData("template/{parameter:int}", "template/{*parameter:int}")]
|
||||
[InlineData("template/{parameter:int}", "template/{*parameter}")]
|
||||
[InlineData("template/{parameter}", "template/{*parameter:int}")]
|
||||
[InlineData("template/{parameter}", "template/{*parameter}")]
|
||||
[InlineData("template/{*parameter:int}", "template/{*parameter}")]
|
||||
public virtual async Task Match_SelectEndpoint_BasedOnPrecedence(string template1, string template2)
|
||||
{
|
||||
// Arrange
|
||||
var expected = CreateEndpoint(template1);
|
||||
var other = CreateEndpoint(template2);
|
||||
var path = "/template/5";
|
||||
|
||||
// Arrange
|
||||
var matcher = CreateMatcher(other, expected);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, expected, ignoreValues: true);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("template/5", "template/{parameter:int}")]
|
||||
[InlineData("template/5", "template/{parameter}")]
|
||||
[InlineData("template/5", "template/{*parameter:int}")]
|
||||
[InlineData("template/5", "template/{*parameter}")]
|
||||
[InlineData("template/{parameter:int}", "template/{parameter}")]
|
||||
[InlineData("template/{parameter:int}", "template/{*parameter:int}")]
|
||||
[InlineData("template/{parameter:int}", "template/{*parameter}")]
|
||||
[InlineData("template/{parameter}", "template/{*parameter:int}")]
|
||||
[InlineData("template/{parameter}", "template/{*parameter}")]
|
||||
[InlineData("template/{*parameter:int}", "template/{*parameter}")]
|
||||
[InlineData("template/5", "template/5")]
|
||||
[InlineData("template/{parameter:int}", "template/{parameter:int}")]
|
||||
[InlineData("template/{parameter}", "template/{parameter}")]
|
||||
[InlineData("template/{*parameter:int}", "template/{*parameter:int}")]
|
||||
[InlineData("template/{*parameter}", "template/{*parameter}")]
|
||||
public virtual async Task Match_SelectEndpoint_BasedOnOrder(string template1, string template2)
|
||||
{
|
||||
// Arrange
|
||||
var expected = CreateEndpoint(template1, order: 0);
|
||||
var other = CreateEndpoint(template2, order: 1);
|
||||
var path = "/template/5";
|
||||
|
||||
// Arrange
|
||||
var matcher = CreateMatcher(other, expected);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, expected, ignoreValues: true);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/", "")]
|
||||
[InlineData("/Literal1", "Literal1")]
|
||||
[InlineData("/Literal1/Literal2", "Literal1/Literal2")]
|
||||
[InlineData("/Literal1/Literal2/Literal3", "Literal1/Literal2/Literal3")]
|
||||
[InlineData("/Literal1/Literal2/Literal3/4", "Literal1/Literal2/Literal3/{*constrainedCatchAll:int}")]
|
||||
[InlineData("/Literal1/Literal2/Literal3/Literal4", "Literal1/Literal2/Literal3/{*catchAll}")]
|
||||
[InlineData("/1", "{constrained1:int}")]
|
||||
[InlineData("/1/2", "{constrained1:int}/{constrained2:int}")]
|
||||
[InlineData("/1/2/3", "{constrained1:int}/{constrained2:int}/{constrained3:int}")]
|
||||
[InlineData("/1/2/3/4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}")]
|
||||
[InlineData("/1/2/3/CatchAll4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}")]
|
||||
[InlineData("/parameter1", "{parameter1}")]
|
||||
[InlineData("/parameter1/parameter2", "{parameter1}/{parameter2}")]
|
||||
[InlineData("/parameter1/parameter2/parameter3", "{parameter1}/{parameter2}/{parameter3}")]
|
||||
[InlineData("/parameter1/parameter2/parameter3/4", "{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}")]
|
||||
[InlineData("/parameter1/parameter2/parameter3/CatchAll4", "{parameter1}/{parameter2}/{parameter3}/{*catchAll}")]
|
||||
public virtual async Task Match_IntegrationTest_MultipleEndpoints(string path, string expectedTemplate)
|
||||
{
|
||||
// Arrange
|
||||
var templates = new[]
|
||||
{
|
||||
"",
|
||||
"Literal1",
|
||||
"Literal1/Literal2",
|
||||
"Literal1/Literal2/Literal3",
|
||||
"Literal1/Literal2/Literal3/{*constrainedCatchAll:int}",
|
||||
"Literal1/Literal2/Literal3/{*catchAll}",
|
||||
"{constrained1:int}",
|
||||
"{constrained1:int}/{constrained2:int}",
|
||||
"{constrained1:int}/{constrained2:int}/{constrained3:int}",
|
||||
"{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}",
|
||||
"{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}",
|
||||
"{parameter1}",
|
||||
"{parameter1}/{parameter2}",
|
||||
"{parameter1}/{parameter2}/{parameter3}",
|
||||
"{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}",
|
||||
"{parameter1}/{parameter2}/{parameter3}/{*catchAll}",
|
||||
};
|
||||
|
||||
var endpoints = templates.Select((t) => CreateEndpoint(t)).ToArray();
|
||||
var expected = endpoints[Array.IndexOf(templates, expectedTemplate)];
|
||||
|
||||
var matcher = CreateMatcher(endpoints);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, expected, ignoreValues: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class InstructionMatcherConformanceTest : MatcherConformanceTest
|
||||
{
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
internal override Matcher CreateMatcher(params MatcherEndpoint[] endpoints)
|
||||
{
|
||||
var builder = new InstructionMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
for (int i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
builder.AddEndpoint(endpoints[i]);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public abstract partial class MatcherConformanceTest
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public abstract partial class MatcherConformanceTest
|
||||
{
|
||||
[Fact]
|
||||
public virtual async Task Match_EmptyRoute()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/");
|
||||
var (httpContext, feature) = CreateContext("/");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleLiteralSegment()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext("/simple");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleLiteralSegment_TrailingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext("/simple/");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/simple")]
|
||||
[InlineData("/sImpLe")]
|
||||
[InlineData("/SIMPLE")]
|
||||
public virtual async Task Match_SingleLiteralSegment_CaseInsensitive(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/Simple");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Some matchers will optimize for the ASCII case
|
||||
[Theory]
|
||||
[InlineData("/SÏmple", "/SÏmple")]
|
||||
[InlineData("/ab\uD834\uDD1Ecd", "/ab\uD834\uDD1Ecd")] // surrogate pair
|
||||
public virtual async Task Match_SingleLiteralSegment_Unicode(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers should operate on the decoded representation - a matcher that calls
|
||||
// `httpContext.Request.Path.ToString()` will break this test.
|
||||
[Theory]
|
||||
[InlineData("/S%mple", "/S%mple")]
|
||||
[InlineData("/S\\imple", "/S\\imple")] // surrogate pair
|
||||
public virtual async Task Match_SingleLiteralSegment_PercentEncoded(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/")]
|
||||
[InlineData("/imple")]
|
||||
[InlineData("/siple")]
|
||||
[InlineData("/simple1")]
|
||||
[InlineData("/simple/not-simple")]
|
||||
[InlineData("/simple/a/b/c")]
|
||||
public virtual async Task NotMatch_SingleLiteralSegment(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("simple")]
|
||||
[InlineData("/simple")]
|
||||
[InlineData("~/simple")]
|
||||
public virtual async Task Match_Sanitizies_Template(string template)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext("/simple");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers do their own 'splitting' of the path into segments, so including
|
||||
// some extra variation here
|
||||
[Theory]
|
||||
[InlineData("/a/b", "/a/b")]
|
||||
[InlineData("/a/b", "/A/B")]
|
||||
[InlineData("/a/b", "/a/b/")]
|
||||
[InlineData("/a/b/c", "/a/b/c")]
|
||||
[InlineData("/a/b/c", "/a/b/c/")]
|
||||
[InlineData("/a/b/c/d", "/a/b/c/d")]
|
||||
[InlineData("/a/b/c/d", "/a/b/c/d/")]
|
||||
public virtual async Task Match_MultipleLiteralSegments(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers do their own 'splitting' of the path into segments, so including
|
||||
// some extra variation here
|
||||
[Theory]
|
||||
[InlineData("/a/b", "/")]
|
||||
[InlineData("/a/b", "/a")]
|
||||
[InlineData("/a/b", "/a/")]
|
||||
[InlineData("/a/b", "/a//")]
|
||||
[InlineData("/a/b", "/aa/")]
|
||||
[InlineData("/a/b", "/a/bb")]
|
||||
[InlineData("/a/b", "/a/bb/")]
|
||||
[InlineData("/a/b/c", "/aa/b/c")]
|
||||
[InlineData("/a/b/c", "/a/bb/c/")]
|
||||
[InlineData("/a/b/c", "/a/b/cab")]
|
||||
[InlineData("/a/b/c", "/d/b/c/")]
|
||||
[InlineData("/a/b/c", "//b/c")]
|
||||
[InlineData("/a/b/c", "/a/b//")]
|
||||
[InlineData("/a/b/c", "/a/b/c/d")]
|
||||
[InlineData("/a/b/c", "/a/b/c/d/e")]
|
||||
public virtual async Task NotMatch_MultipleLiteralSegments(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleParameter()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext("/14");
|
||||
var values = new RouteValueDictionary(new { p = "14", });
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleParameter_TrailingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext("/14/");
|
||||
var values = new RouteValueDictionary(new { p = "14", });
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleParameter_WierdNames()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/foo/{ }/{.!$%}/{dynamic.data}");
|
||||
var (httpContext, feature) = CreateContext("/foo/space/weirdmatch/matcherid");
|
||||
var values = new RouteValueDictionary()
|
||||
{
|
||||
{ " ", "space" },
|
||||
{ ".!$%", "weirdmatch" },
|
||||
{ "dynamic.data", "matcherid" },
|
||||
};
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/")]
|
||||
[InlineData("/a/b")]
|
||||
[InlineData("/a/b/c")]
|
||||
[InlineData("//")]
|
||||
public virtual async Task NotMatch_SingleParameter(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a}/b", "/54/b", new string[] { "a", }, new string[] { "54", })]
|
||||
[InlineData("/{a}/b", "/54/b/", new string[] { "a", }, new string[] { "54", })]
|
||||
[InlineData("/{a}/{b}", "/54/73", new string[] { "a", "b" }, new string[] { "54", "73", })]
|
||||
[InlineData("/a/{b}/c", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
||||
[InlineData("/a/{b}/c/", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
||||
[InlineData("/{a}/b/{c}", "/54/b/c", new string[] { "a", "c", }, new string[] { "54", "c", })]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c", new string[] { "a", "b", "c", }, new string[] { "54", "b", "c", })]
|
||||
public virtual async Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a}/b", "/54/bb")]
|
||||
[InlineData("/{a}/b", "/54/b/17")]
|
||||
[InlineData("/{a}/b", "/54/b//")]
|
||||
[InlineData("/{a}/{b}", "//73")]
|
||||
[InlineData("/{a}/{b}", "/54//")]
|
||||
[InlineData("/{a}/{b}", "/54/73/18")]
|
||||
[InlineData("/a/{b}/c", "/aa/b/c")]
|
||||
[InlineData("/a/{b}/c", "/a/b/cc")]
|
||||
[InlineData("/a/{b}/c", "/a/b/c/d")]
|
||||
[InlineData("/{a}/b/{c}", "/54/bb/c")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c/d")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c//")]
|
||||
[InlineData("/{a}/{b}/{c}", "//b/c/")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54//c/")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b//")]
|
||||
public virtual async Task NotMatch_MultipleParameters(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,289 +2,14 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public abstract class MatcherConformanceTest
|
||||
public abstract partial class MatcherConformanceTest
|
||||
{
|
||||
internal abstract Matcher CreateMatcher(MatcherEndpoint endpoint);
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleLiteralSegment()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext("/simple");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleLiteralSegment_TrailingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext("/simple/");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/simple")]
|
||||
[InlineData("/sImpLe")]
|
||||
[InlineData("/SIMPLE")]
|
||||
public virtual async Task Match_SingleLiteralSegment_CaseInsensitive(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/Simple");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Some matchers will optimize for the ASCII case
|
||||
[Theory]
|
||||
[InlineData("/SÏmple", "/SÏmple")]
|
||||
[InlineData("/ab\uD834\uDD1Ecd", "/ab\uD834\uDD1Ecd")] // surrogate pair
|
||||
public virtual async Task Match_SingleLiteralSegment_Unicode(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers should operate on the decoded representation - a matcher that calls
|
||||
// `httpContext.Request.Path.ToString()` will break this test.
|
||||
[Theory]
|
||||
[InlineData("/S%mple", "/S%mple")]
|
||||
[InlineData("/S\\imple", "/S\\imple")] // surrogate pair
|
||||
public virtual async Task Match_SingleLiteralSegment_PercentEncoded(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("/")]
|
||||
[InlineData("/imple")]
|
||||
[InlineData("/siple")]
|
||||
[InlineData("/simple1")]
|
||||
[InlineData("/simple/not-simple")]
|
||||
[InlineData("/simple/a/b/c")]
|
||||
public virtual async Task NotMatch_SingleLiteralSegment(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/simple");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("simple")]
|
||||
[InlineData("/simple")]
|
||||
[InlineData("~/simple")]
|
||||
public virtual async Task Match_Sanitizies_Template(string template)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext("/simple");
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers do their own 'splitting' of the path into segments, so including
|
||||
// some extra variation here
|
||||
[Theory]
|
||||
[InlineData("/a/b", "/a/b")]
|
||||
[InlineData("/a/b", "/A/B")]
|
||||
[InlineData("/a/b", "/a/b/")]
|
||||
[InlineData("/a/b/c", "/a/b/c")]
|
||||
[InlineData("/a/b/c", "/a/b/c/")]
|
||||
[InlineData("/a/b/c/d", "/a/b/c/d")]
|
||||
[InlineData("/a/b/c/d", "/a/b/c/d/")]
|
||||
public virtual async Task Match_MultipleLiteralSegments(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint);
|
||||
}
|
||||
|
||||
// Matchers do their own 'splitting' of the path into segments, so including
|
||||
// some extra variation here
|
||||
[Theory]
|
||||
[InlineData("/a/b", "/")]
|
||||
[InlineData("/a/b", "/a")]
|
||||
[InlineData("/a/b", "/a/")]
|
||||
[InlineData("/a/b", "/a//")]
|
||||
[InlineData("/a/b", "/aa/")]
|
||||
[InlineData("/a/b", "/a/bb")]
|
||||
[InlineData("/a/b", "/a/bb/")]
|
||||
[InlineData("/a/b/c", "/aa/b/c")]
|
||||
[InlineData("/a/b/c", "/a/bb/c/")]
|
||||
[InlineData("/a/b/c", "/a/b/cab")]
|
||||
[InlineData("/a/b/c", "/d/b/c/")]
|
||||
[InlineData("/a/b/c", "//b/c")]
|
||||
[InlineData("/a/b/c", "/a/b//")]
|
||||
[InlineData("/a/b/c", "/a/b/c/d")]
|
||||
[InlineData("/a/b/c", "/a/b/c/d/e")]
|
||||
public virtual async Task NotMatch_MultipleLiteralSegments(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleParameter()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext("/14");
|
||||
var values = new RouteValueDictionary(new { p = "14", });
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task Match_SingleParameter_TrailingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext("/14/");
|
||||
var values = new RouteValueDictionary(new { p = "14", });
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/")]
|
||||
[InlineData("/a/b")]
|
||||
[InlineData("/a/b/c")]
|
||||
[InlineData("//")]
|
||||
public virtual async Task NotMatch_SingleParameter(string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher("/{p}");
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/subscriptions/{subscriptionId}/providers/Microsoft.Insights/metricAlerts", "/subscriptions/foo/providers/Microsoft.Insights/metricAlerts", new string[] { "subscriptionId", }, new string[] { "foo", })]
|
||||
[InlineData("/{a}/b", "/54/b", new string[] { "a", }, new string[] {"54", })]
|
||||
[InlineData("/{a}/b", "/54/b/", new string[] { "a", }, new string[] { "54", })]
|
||||
[InlineData("/{a}/{b}", "/54/73", new string[] { "a", "b" }, new string[] { "54", "73", })]
|
||||
[InlineData("/a/{b}/c", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
||||
[InlineData("/a/{b}/c/", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
||||
[InlineData("/{a}/b/{c}", "/54/b/c", new string[] { "a", "c", }, new string[] { "54", "c", })]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c", new string[] { "a", "b", "c", }, new string[] { "54", "b", "c", })]
|
||||
public virtual async Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/{a}/b", "/54/bb")]
|
||||
[InlineData("/{a}/b", "/54/b/17")]
|
||||
[InlineData("/{a}/b", "/54/b//")]
|
||||
[InlineData("/{a}/{b}", "//73")]
|
||||
[InlineData("/{a}/{b}", "/54//")]
|
||||
[InlineData("/{a}/{b}", "/54/73/18")]
|
||||
[InlineData("/a/{b}/c", "/aa/b/c")]
|
||||
[InlineData("/a/{b}/c", "/a/b/cc")]
|
||||
[InlineData("/a/{b}/c", "/a/b/c/d")]
|
||||
[InlineData("/{a}/b/{c}", "/54/bb/c")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c/d")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b/c//")]
|
||||
[InlineData("/{a}/{b}/{c}", "//b/c/")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54//c/")]
|
||||
[InlineData("/{a}/{b}/{c}", "/54/b//")]
|
||||
public virtual async Task NotMatch_MultipleParameters(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var (matcher, endpoint) = CreateMatcher(template);
|
||||
var (httpContext, feature) = CreateContext(path);
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, feature);
|
||||
|
||||
// Assert
|
||||
DispatcherAssert.AssertNotMatch(feature);
|
||||
}
|
||||
internal abstract Matcher CreateMatcher(params MatcherEndpoint[] endpoints);
|
||||
|
||||
internal static (HttpContext httpContext, IEndpointFeature feature) CreateContext(string path)
|
||||
{
|
||||
|
|
@ -307,14 +32,17 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
internal static MatcherEndpoint CreateEndpoint(string template)
|
||||
internal static MatcherEndpoint CreateEndpoint(
|
||||
string template,
|
||||
object defaults = null,
|
||||
int? order = null)
|
||||
{
|
||||
return new MatcherEndpoint(
|
||||
MatcherEndpoint.EmptyInvoker,
|
||||
template,
|
||||
null,
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
defaults,
|
||||
order ?? 0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"endpoint: " + template,
|
||||
address: null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class RouteMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private readonly RouteCollection _routes = new RouteCollection();
|
||||
private readonly IInlineConstraintResolver _constraintResolver;
|
||||
private readonly List<Entry> _entries;
|
||||
|
||||
public RouteMatcherBuilder()
|
||||
{
|
||||
_constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()));
|
||||
_entries = new List<Entry>();
|
||||
}
|
||||
|
||||
public override void AddEndpoint(MatcherEndpoint endpoint)
|
||||
|
|
@ -23,12 +27,66 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
c.Features.Get<IEndpointFeature>().Endpoint = endpoint;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_routes.Add(new Route(handler, endpoint.Template, _constraintResolver));
|
||||
|
||||
// MatcherEndpoint.Values contains the default values parsed from the template
|
||||
// as well as those specified with a literal. We need to separate those
|
||||
// for legacy cases.
|
||||
var defaults = new RouteValueDictionary(endpoint.Values);
|
||||
for (var i = 0; i < endpoint.ParsedTemlate.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = endpoint.ParsedTemlate.Parameters[i];
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
defaults.Remove(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
_entries.Add(new Entry()
|
||||
{
|
||||
Endpoint = endpoint,
|
||||
Route = new Route(
|
||||
handler,
|
||||
endpoint.Template,
|
||||
defaults,
|
||||
new Dictionary<string, object>(),
|
||||
new RouteValueDictionary(),
|
||||
_constraintResolver),
|
||||
});
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new RouteMatcher(_routes);
|
||||
_entries.Sort();
|
||||
var routes = new RouteCollection();
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
routes.Add(_entries[i].Route);
|
||||
}
|
||||
|
||||
return new RouteMatcher(routes);
|
||||
}
|
||||
|
||||
private struct Entry : IComparable<Entry>
|
||||
{
|
||||
public MatcherEndpoint Endpoint;
|
||||
public Route Route;
|
||||
|
||||
public int CompareTo(Entry other)
|
||||
{
|
||||
var comparison = Endpoint.Order.CompareTo(other.Endpoint.Order);
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
comparison = RoutePrecedence.ComputeInbound(Endpoint.ParsedTemlate).CompareTo(RoutePrecedence.ComputeInbound(other.Endpoint.ParsedTemlate));
|
||||
if (comparison != 0)
|
||||
{
|
||||
return comparison;
|
||||
}
|
||||
|
||||
return Endpoint.Template.CompareTo(other.Endpoint.Template);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class RouteMatcherConformanceTest : MatcherConformanceTest
|
||||
public class RouteMatcherConformanceTest : FullFeaturedMatcherConformanceTest
|
||||
{
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
internal override Matcher CreateMatcher(params MatcherEndpoint[] endpoints)
|
||||
{
|
||||
var builder = new RouteMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
for (int i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
builder.AddEndpoint(endpoints[i]);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
|
||||
// Assert
|
||||
Assert.Equal(endpointWithConstraint, endpointFeature.Endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
|
@ -34,7 +35,25 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
_inner.MapInbound(handler, TemplateParser.Parse(endpoint.Template), "default", 0);
|
||||
// MatcherEndpoint.Values contains the default values parsed from the template
|
||||
// as well as those specified with a literal. We need to separate those
|
||||
// for legacy cases.
|
||||
var defaults = new RouteValueDictionary(endpoint.Values);
|
||||
for (var i = 0; i < endpoint.ParsedTemlate.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = endpoint.ParsedTemlate.Parameters[i];
|
||||
if (parameter.DefaultValue == null && defaults.ContainsKey(parameter.Name))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The TreeRouter does not support non-inline default values.");
|
||||
}
|
||||
}
|
||||
|
||||
_inner.MapInbound(
|
||||
handler,
|
||||
endpoint.ParsedTemlate,
|
||||
routeName: null,
|
||||
order: endpoint.Order);
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,33 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class TreeRouterMatcherConformanceTest : MatcherConformanceTest
|
||||
public class TreeRouterMatcherConformanceTest : FullFeaturedMatcherConformanceTest
|
||||
{
|
||||
internal override Matcher CreateMatcher(MatcherEndpoint endpoint)
|
||||
// TreeRouter doesn't support non-inline default values.
|
||||
[Fact]
|
||||
public override Task Match_NonInlineDefaultValues()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// TreeRouter doesn't support non-inline default values.
|
||||
[Fact]
|
||||
public override Task Match_ExtraDefaultValues()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
internal override Matcher CreateMatcher(params MatcherEndpoint[] endpoints)
|
||||
{
|
||||
var builder = new TreeRouterMatcherBuilder();
|
||||
builder.AddEndpoint(endpoint);
|
||||
for (var i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
builder.AddEndpoint(endpoints[i]);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue