Remove string.Split from routing

This change removes the call to string.Split and a few substrings, and
replaces it with a tokenizer API. The tokenizer isn't really optimized
right now for compute - it should probably be an iterator- but it's a
significant improvement on what we're doing.
This commit is contained in:
Ryan Nowak 2015-10-02 10:51:12 -07:00
parent 2f8dba6659
commit 371d4e62da
6 changed files with 587 additions and 137 deletions

View File

@ -0,0 +1,136 @@
// 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;
namespace Microsoft.AspNet.Routing.Internal
{
public struct PathSegment : IEquatable<PathSegment>, IEquatable<string>
{
private readonly string _path;
private readonly int _start;
private readonly int _length;
private string _segment;
public PathSegment(string path, int start, int length)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
if (start < 1 || start >= path.Length)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (length < 0 || start + length > path.Length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
_path = path;
_start = start;
_length = length;
_segment = null;
}
public int Length => _length;
public string GetRemainingPath()
{
if (_path == null)
{
return string.Empty;
}
return _path.Substring(_start);
}
public override bool Equals(object obj)
{
var other = obj as PathSegment?;
if (other == null)
{
return false;
}
else
{
return Equals(other.Value);
}
}
public override int GetHashCode()
{
if (_path == null)
{
return 0;
}
return StringComparer.OrdinalIgnoreCase.GetHashCode(ToString());
}
public override string ToString()
{
if (_path == null)
{
return string.Empty;
}
if (_segment == null)
{
_segment = _path.Substring(_start, _length);
}
return _segment;
}
public bool Equals(PathSegment other)
{
if (_path == null)
{
return other._path == null;
}
if (other._length != _length)
{
return false;
}
return string.Compare(
_path,
_start,
other._path,
other._start,
_length,
StringComparison.OrdinalIgnoreCase) == 0;
}
public bool Equals(string other)
{
if (_path == null)
{
return other == null;
}
if (other.Length != _length)
{
return false;
}
return string.Compare(_path, _start, other, 0, _length, StringComparison.OrdinalIgnoreCase) == 0;
}
public static bool operator ==(PathSegment x, PathSegment y)
{
return x.Equals(y);
}
public static bool operator !=(PathSegment x, PathSegment y)
{
return !x.Equals(y);
}
}
}

View File

@ -0,0 +1,204 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Routing.Internal
{
public struct PathTokenizer : IReadOnlyList<PathSegment>
{
private readonly string _path;
private int _count;
public PathTokenizer(PathString path)
{
_path = path.Value;
_count = -1;
}
public int Count
{
get
{
if (_count == -1)
{
// We haven't computed the real count of segments yet.
if (_path.Length == 0)
{
// The empty string has length of 0.
_count = 0;
return _count;
}
// A string of length 1 must be "/" - all PathStrings start with '/'
if (_path.Length == 1)
{
// We treat this as empty - there's nothing to parse here for routing, because routing ignores
// a trailing slash.
Debug.Assert(_path[0] == '/');
_count = 0;
return _count;
}
// This is a non-trival PathString
_count = 1;
// Since a non-empty PathString must begin with a `/`, we can just count the number of occurrences
// of `/` to find the number of segments. However, we don't look at the last character, because
// routing ignores a trailing slash.
for (var i = 1; i < _path.Length - 1; i++)
{
if (_path[i] == '/')
{
_count++;
}
}
}
return _count;
}
}
public PathSegment this[int index]
{
get
{
if (index >= Count)
{
throw new IndexOutOfRangeException();
}
var currentSegmentIndex = 0;
var currentSegmentStart = 1;
// Skip the first `/`.
var delimiterIndex = 1;
while ((delimiterIndex = _path.IndexOf('/', delimiterIndex)) != -1)
{
if (currentSegmentIndex++ == index)
{
return new PathSegment(_path, currentSegmentStart, delimiterIndex - currentSegmentStart);
}
else
{
currentSegmentStart = delimiterIndex + 1;
delimiterIndex++;
}
}
// If we get here we're at the end of the string. The implementation of .Count should protect us
// from these cases.
Debug.Assert(_path[_path.Length - 1] != '/');
Debug.Assert(currentSegmentIndex == index);
return new PathSegment(_path, currentSegmentStart, _path.Length - currentSegmentStart);
}
}
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator<PathSegment> IEnumerable<PathSegment>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public struct Enumerator : IEnumerator<PathSegment>
{
private readonly string _path;
private int _index;
private int _length;
public Enumerator(PathTokenizer tokenizer)
{
_path = tokenizer._path;
_index = -1;
_length = -1;
}
public PathSegment Current
{
get
{
return new PathSegment(_path, _index, _length);
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public void Dispose()
{
}
public bool MoveNext()
{
if (_path == null || _path.Length <= 1)
{
return false;
}
if (_index == -1)
{
// Skip the first `/`.
_index = 1;
}
else
{
// Skip to the end of the previous segment + the separator.
_index += _length + 1;
}
if (_index >= _path.Length)
{
// We're at the end
return false;
}
var delimiterIndex = _path.IndexOf('/', _index);
if (delimiterIndex != -1)
{
_length = delimiterIndex - _index;
return true;
}
// We might have some trailing text after the last separator.
if (_path[_path.Length - 1] == '/')
{
// If the last char is a '/' then it's just a trailing slash, we don't have another segment.
return false;
}
else
{
_length = _path.Length - _index;
return true;
}
}
public void Reset()
{
_index = -1;
_length = -1;
}
}
}
}

View File

@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Internal;
namespace Microsoft.AspNet.Routing.Template
{
@ -36,18 +38,12 @@ namespace Microsoft.AspNet.Routing.Template
public RouteTemplate Template { get; private set; }
public IDictionary<string, object> Match(string requestPath)
public IDictionary<string, object> Match(PathString path)
{
if (requestPath == null)
{
throw new ArgumentNullException(nameof(requestPath));
}
var requestSegments = requestPath.Split(Delimiters);
var values = new RouteValueDictionary();
for (var i = 0; i < requestSegments.Length; i++)
var requestSegments = new PathTokenizer(path);
for (var i = 0; i < requestSegments.Count; i++)
{
var routeSegment = Template.Segments.Count > i ? Template.Segments[i] : null;
var requestSegment = requestSegments[i];
@ -67,7 +63,7 @@ namespace Microsoft.AspNet.Routing.Template
var part = routeSegment.Parts[0];
if (part.IsLiteral)
{
if (!string.Equals(part.Text, requestSegment, StringComparison.OrdinalIgnoreCase))
if (!requestSegment.Equals(part.Text))
{
return null;
}
@ -78,7 +74,7 @@ namespace Microsoft.AspNet.Routing.Template
if (part.IsCatchAll)
{
var captured = string.Join(SeparatorString, requestSegments, i, requestSegments.Length - i);
var captured = requestSegment.GetRemainingPath();
if (captured.Length > 0)
{
values.Add(part.Name, captured);
@ -99,7 +95,7 @@ namespace Microsoft.AspNet.Routing.Template
{
if (requestSegment.Length > 0)
{
values.Add(part.Name, requestSegment);
values.Add(part.Name, requestSegment.ToString());
}
else
{
@ -124,14 +120,14 @@ namespace Microsoft.AspNet.Routing.Template
}
else
{
if (!MatchComplexSegment(routeSegment, requestSegment, Defaults, values))
if (!MatchComplexSegment(routeSegment, requestSegment.ToString(), Defaults, values))
{
return null;
}
}
}
for (var i = requestSegments.Length; i < Template.Segments.Count; i++)
for (var i = requestSegments.Count; i < Template.Segments.Count; i++)
{
// We've matched the request path so far, but still have remaining route segments. These need
// to be all single-part parameter segments with default values or else they won't match.
@ -179,10 +175,11 @@ namespace Microsoft.AspNet.Routing.Template
return values;
}
private bool MatchComplexSegment(TemplateSegment routeSegment,
string requestSegment,
IReadOnlyDictionary<string, object> defaults,
RouteValueDictionary values)
private bool MatchComplexSegment(
TemplateSegment routeSegment,
string requestSegment,
IReadOnlyDictionary<string, object> defaults,
RouteValueDictionary values)
{
var indexOfLastSegment = routeSegment.Parts.Count - 1;
@ -225,11 +222,12 @@ namespace Microsoft.AspNet.Routing.Template
}
}
private bool MatchComplexSegmentCore(TemplateSegment routeSegment,
string requestSegment,
IReadOnlyDictionary<string, object> defaults,
RouteValueDictionary values,
int indexOfLastSegmentUsed)
private bool MatchComplexSegmentCore(
TemplateSegment routeSegment,
string requestSegment,
IReadOnlyDictionary<string, object> defaults,
RouteValueDictionary values,
int indexOfLastSegmentUsed)
{
Debug.Assert(routeSegment != null);
Debug.Assert(routeSegment.Parts.Count > 1);
@ -269,9 +267,10 @@ namespace Microsoft.AspNet.Routing.Template
return false;
}
var indexOfLiteral = requestSegment.LastIndexOf(part.Text,
startIndex,
StringComparison.OrdinalIgnoreCase);
var indexOfLiteral = requestSegment.LastIndexOf(
part.Text,
startIndex,
StringComparison.OrdinalIgnoreCase);
if (indexOfLiteral == -1)
{
// If we couldn't find this literal index, this segment cannot match

View File

@ -115,13 +115,7 @@ namespace Microsoft.AspNet.Routing.Template
EnsureLoggers(context.HttpContext);
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
requestPath = requestPath.Substring(1);
}
var requestPath = context.HttpContext.Request.Path;
var values = _matcher.Match(requestPath);
if (values == null)

View File

@ -0,0 +1,116 @@
// 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.AspNet.Http;
using Xunit;
namespace Microsoft.AspNet.Routing.Internal
{
public class PathTokenizerTest
{
public static TheoryData<string, PathSegment[]> TokenizationData
{
get
{
return new TheoryData<string, PathSegment[]>
{
{ string.Empty, new PathSegment[] { } },
{ "/", new PathSegment[] { } },
{ "//", new PathSegment[] { new PathSegment("//", 1, 0) } },
{
"///",
new PathSegment[]
{
new PathSegment("///", 1, 0),
new PathSegment("///", 2, 0),
}
},
{
"////",
new PathSegment[]
{
new PathSegment("////", 1, 0),
new PathSegment("////", 2, 0),
new PathSegment("////", 3, 0),
}
},
{ "/zero", new PathSegment[] { new PathSegment("/zero", 1, 4) } },
{ "/zero/", new PathSegment[] { new PathSegment("/zero/", 1, 4) } },
{
"/zero/one",
new PathSegment[]
{
new PathSegment("/zero/one", 1, 4),
new PathSegment("/zero/one", 6, 3),
}
},
{
"/zero/one/",
new PathSegment[]
{
new PathSegment("/zero/one/", 1, 4),
new PathSegment("/zero/one/", 6, 3),
}
},
{
"/zero/one/two",
new PathSegment[]
{
new PathSegment("/zero/one/two", 1, 4),
new PathSegment("/zero/one/two", 6, 3),
new PathSegment("/zero/one/two", 10, 3),
}
},
{
"/zero/one/two/",
new PathSegment[]
{
new PathSegment("/zero/one/two/", 1, 4),
new PathSegment("/zero/one/two/", 6, 3),
new PathSegment("/zero/one/two/", 10, 3),
}
},
};
}
}
[Theory]
[MemberData(nameof(TokenizationData))]
public void PathTokenizer_Count(string path, PathSegment[] expectedSegments)
{
// Arrange
var tokenizer = new PathTokenizer(new PathString(path));
// Act
var count = tokenizer.Count;
// Assert
Assert.Equal(expectedSegments.Length, count);
}
[Theory]
[MemberData(nameof(TokenizationData))]
public void PathTokenizer_Indexer(string path, PathSegment[] expectedSegments)
{
// Arrange
var tokenizer = new PathTokenizer(new PathString(path));
// Act & Assert
for (var i = 0; i < expectedSegments.Length; i++)
{
Assert.Equal(expectedSegments[i], tokenizer[i]);
}
}
[Theory]
[MemberData(nameof(TokenizationData))]
public void PathTokenizer_Enumerator(string path, PathSegment[] expectedSegments)
{
// Arrange
var tokenizer = new PathTokenizer(new PathString(path));
// Act & Assert
Assert.Equal<PathSegment>(expectedSegments, tokenizer);
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.OptionsModel;
using Xunit;
@ -19,7 +20,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{controller}/{action}/{id}");
// Act
var match = matcher.Match("Bank/DoAction/123");
var match = matcher.Match("/Bank/DoAction/123");
// Assert
Assert.NotNull(match);
@ -35,7 +36,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{controller}/{action}/{id}");
// Act
var match = matcher.Match("Bank/DoAction");
var match = matcher.Match("/Bank/DoAction");
// Assert
Assert.Null(match);
@ -48,7 +49,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
// Act
var rd = matcher.Match("Bank/DoAction");
var rd = matcher.Match("/Bank/DoAction");
// Assert
Assert.Equal("Bank", rd["controller"]);
@ -63,7 +64,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
// Act
var rd = matcher.Match("Bank");
var rd = matcher.Match("/Bank");
// Assert
Assert.Null(rd);
@ -76,7 +77,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
// Act
var rd = matcher.Match("moo/111/bar/222");
var rd = matcher.Match("/moo/111/bar/222");
// Assert
Assert.Equal("111", rd["p1"]);
@ -90,7 +91,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
// Act
var rd = matcher.Match("moo/111/bar/");
var rd = matcher.Match("/moo/111/bar/");
// Assert
Assert.Equal("111", rd["p1"]);
@ -98,10 +99,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
}
[Theory]
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", "123-456-7890")] // ssn
[InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", "asd@assds.com")] // email
[InlineData(@"{p1:regex(([}}])\w+)}", "}sda")] // Not balanced }
[InlineData(@"{p1:regex(([{{)])\w+)}", "})sda")] // Not balanced {
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", "/123-456-7890")] // ssn
[InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", "/asd@assds.com")] // email
[InlineData(@"{p1:regex(([}}])\w+)}", "/}sda")] // Not balanced }
[InlineData(@"{p1:regex(([{{)])\w+)}", "/})sda")] // Not balanced {
public void MatchRoute_RegularExpression_Valid(
string template,
string path)
@ -117,19 +118,19 @@ namespace Microsoft.AspNet.Routing.Template.Tests
}
[Theory]
[InlineData("moo/{p1}.{p2?}", "moo/foo.bar", "foo", "bar")]
[InlineData("moo/{p1?}", "moo/foo", "foo", null)]
[InlineData("moo/{p1?}", "moo", null, null)]
[InlineData("moo/{p1}.{p2?}", "moo/foo", "foo", null)]
[InlineData("moo/{p1}.{p2?}", "moo/foo..bar", "foo.", "bar")]
[InlineData("moo/{p1}.{p2?}", "moo/foo.moo.bar", "foo.moo", "bar")]
[InlineData("moo/{p1}.{p2}", "moo/foo.bar", "foo", "bar")]
[InlineData("moo/foo.{p1}.{p2?}", "moo/foo.moo.bar", "moo", "bar")]
[InlineData("moo/foo.{p1}.{p2?}", "moo/foo.moo", "moo", null)]
[InlineData("moo/.{p2?}", "moo/.foo", null, "foo")]
[InlineData("moo/.{p2?}", "moo", null, null)]
[InlineData("moo/{p1}.{p2?}", "moo/....", "..", ".")]
[InlineData("moo/{p1}.{p2?}", "moo/.bar", ".bar", null)]
[InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", "foo", "bar")]
[InlineData("moo/{p1?}", "/moo/foo", "foo", null)]
[InlineData("moo/{p1?}", "/moo", null, null)]
[InlineData("moo/{p1}.{p2?}", "/moo/foo", "foo", null)]
[InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", "foo.", "bar")]
[InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", "foo.moo", "bar")]
[InlineData("moo/{p1}.{p2}", "/moo/foo.bar", "foo", "bar")]
[InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", "moo", "bar")]
[InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", "moo", null)]
[InlineData("moo/.{p2?}", "/moo/.foo", null, "foo")]
[InlineData("moo/.{p2?}", "/moo", null, null)]
[InlineData("moo/{p1}.{p2?}", "/moo/....", "..", ".")]
[InlineData("moo/{p1}.{p2?}", "/moo/.bar", ".bar", null)]
public void MatchRoute_OptionalParameter_FollowedByPeriod_Valid(
string template,
string path,
@ -154,13 +155,13 @@ namespace Microsoft.AspNet.Routing.Template.Tests
}
[Theory]
[InlineData("moo/{p1}.{p2}.{p3?}", "moo/foo.moo.bar", "foo", "moo", "bar")]
[InlineData("moo/{p1}.{p2}.{p3?}", "moo/foo.moo", "foo", "moo", null)]
[InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "moo/foo.moo.bar", "foo", "moo", "bar")]
[InlineData("{p1}.{p2?}/{p3}", "foo.moo/bar", "foo", "moo", "bar")]
[InlineData("{p1}.{p2?}/{p3}", "foo/bar", "foo", null, "bar")]
[InlineData("{p1}.{p2?}/{p3}", ".foo/bar", ".foo", null, "bar")]
[InlineData("{p1}/{p2}/{p3?}", "foo/bar/baz", "foo", "bar", "baz")]
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
[InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
[InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
[InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
[InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
[InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", "foo", "bar", "baz")]
public void MatchRoute_OptionalParameter_FollowedByPeriod_3Parameters_Valid(
string template,
string path,
@ -189,18 +190,18 @@ namespace Microsoft.AspNet.Routing.Template.Tests
}
[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")]
[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 void MatchRoute_OptionalParameter_FollowedByPeriod_Invalid(string template, string path)
{
// Arrange
@ -220,7 +221,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("moo/bar");
// Act
var rd = matcher.Match("moo/bar");
var rd = matcher.Match("/moo/bar");
// Assert
Assert.NotNull(rd);
@ -234,7 +235,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("moo/bars");
// Act
var rd = matcher.Match("moo/bar");
var rd = matcher.Match("/moo/bar");
// Assert
Assert.Null(rd);
@ -247,7 +248,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("moo/bar");
// Act
var rd = matcher.Match("moo/bar/");
var rd = matcher.Match("/moo/bar/");
// Assert
Assert.NotNull(rd);
@ -261,7 +262,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("moo/bar/");
// Act
var rd = matcher.Match("moo/bar");
var rd = matcher.Match("/moo/bar");
// Assert
Assert.NotNull(rd);
@ -275,7 +276,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{p2}/");
// Act
var rd = matcher.Match("moo/bar");
var rd = matcher.Match("/moo/bar");
// Assert
Assert.NotNull(rd);
@ -290,7 +291,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{p2}/baz");
// Act
var rd = matcher.Match("moo/bar/boo");
var rd = matcher.Match("/moo/bar/boo");
// Assert
Assert.Null(rd);
@ -303,7 +304,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}");
// Act
var rd = matcher.Match("moo/bar");
var rd = matcher.Match("/moo/bar");
// Assert
Assert.Null(rd);
@ -316,21 +317,21 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("DEFAULT.ASPX");
// Act
var rd = matcher.Match("default.aspx");
var rd = matcher.Match("/default.aspx");
// Assert
Assert.NotNull(rd);
}
[Theory]
[InlineData("{prefix}x{suffix}", "xxxxxxxxxx")]
[InlineData("{prefix}xyz{suffix}", "xxxxyzxyzxxxxxxyz")]
[InlineData("{prefix}xyz{suffix}", "abcxxxxyzxyzxxxxxxyzxx")]
[InlineData("{prefix}xyz{suffix}", "xyzxyzxyzxyzxyz")]
[InlineData("{prefix}xyz{suffix}", "xyzxyzxyzxyzxyz1")]
[InlineData("{prefix}xyz{suffix}", "xyzxyzxyz")]
[InlineData("{prefix}aa{suffix}", "aaaaa")]
[InlineData("{prefix}aaa{suffix}", "aaaaa")]
[InlineData("{prefix}x{suffix}", "/xxxxxxxxxx")]
[InlineData("{prefix}xyz{suffix}", "/xxxxyzxyzxxxxxxyz")]
[InlineData("{prefix}xyz{suffix}", "/abcxxxxyzxyzxxxxxxyzxx")]
[InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz")]
[InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz1")]
[InlineData("{prefix}xyz{suffix}", "/xyzxyzxyz")]
[InlineData("{prefix}aa{suffix}", "/aaaaa")]
[InlineData("{prefix}aaa{suffix}", "/aaaaa")]
public void VerifyRouteMatchesWithContext(string template, string path)
{
var matcher = CreateMatcher(template);
@ -349,7 +350,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{p2}", new { p2 = (string)null, foo = "bar" });
// Act
var rd = matcher.Match("v1");
var rd = matcher.Match("/v1");
// Assert
Assert.NotNull(rd);
@ -368,7 +369,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
new { controller = "blog", action = "showpost", m = (string)null, d = (string)null });
// Act
var rd = matcher.Match("date/2007/08");
var rd = matcher.Match("/date/2007/08");
// Assert
Assert.NotNull(rd);
@ -385,7 +386,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/{lang}-{region}",
"language/en-US",
"/language/en-US",
null,
new RouteValueDictionary(new { lang = "en", region = "US" }));
}
@ -395,7 +396,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/{lang}-{region}a",
"language/en-USa",
"/language/en-USa",
null,
new RouteValueDictionary(new { lang = "en", region = "US" }));
}
@ -405,7 +406,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/a{lang}-{region}",
"language/aen-US",
"/language/aen-US",
null,
new RouteValueDictionary(new { lang = "en", region = "US" }));
}
@ -415,7 +416,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/a{lang}-{region}a",
"language/aen-USa",
"/language/aen-USa",
null,
new RouteValueDictionary(new { lang = "en", region = "US" }));
}
@ -425,7 +426,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/a{lang}-{region}a",
"language/a-USa",
"/language/a-USa",
null,
null);
}
@ -435,7 +436,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/a{lang}-{region}a",
"language/aen-a",
"/language/aen-a",
null,
null);
}
@ -445,7 +446,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/{lang}",
"language/en",
"/language/en",
null,
new RouteValueDictionary(new { lang = "en" }));
}
@ -455,7 +456,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/{lang}",
"language/",
"/language/",
null,
null);
}
@ -465,7 +466,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/{lang}",
"language",
"/language",
null,
null);
}
@ -475,7 +476,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/{lang}-",
"language/en-",
"/language/en-",
null,
new RouteValueDictionary(new { lang = "en" }));
}
@ -485,7 +486,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/a{lang}",
"language/aen",
"/language/aen",
null,
new RouteValueDictionary(new { lang = "en" }));
}
@ -495,7 +496,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/a{lang}a",
"language/aena",
"/language/aena",
null,
new RouteValueDictionary(new { lang = "en" }));
}
@ -505,7 +506,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{controller}.mvc/{action}/{id}",
"home.mvc/index",
"/home.mvc/index",
new RouteValueDictionary(new { action = "Index", id = (string)null }),
new RouteValueDictionary(new { controller = "home", action = "index", id = (string)null }));
}
@ -515,7 +516,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"language/{lang}-{region}",
"language/-",
"/language/-",
new RouteValueDictionary(new { lang = "xx", region = "yy" }),
null);
}
@ -525,7 +526,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{Controller}..mvc/{id}/{Param1}",
"Home..mvc/123/p1",
"/Home..mvc/123/p1",
null,
new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
}
@ -535,7 +536,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{Controller}.mvc/../{action}",
"Home.mvc/../index",
"/Home.mvc/../index",
null,
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
}
@ -545,7 +546,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{Controller}.mvc/.../{action}",
"Home.mvc/.../index",
"/Home.mvc/.../index",
null,
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
}
@ -555,7 +556,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{Controller}.mvc/../../../{action}",
"Home.mvc/../../../index",
"/Home.mvc/../../../index",
null,
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
}
@ -565,7 +566,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{Controller}.mvc!/{action}",
"Home.mvc!/index",
"/Home.mvc!/index",
null,
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
}
@ -575,7 +576,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"../{Controller}.mvc",
"../Home.mvc",
"/../Home.mvc",
null,
new RouteValueDictionary(new { Controller = "Home" }));
}
@ -585,7 +586,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
@"\{Controller}.mvc",
@"\Home.mvc",
@"/\Home.mvc",
null,
new RouteValueDictionary(new { Controller = "Home" }));
}
@ -595,7 +596,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
@"{Controller}.mvc\{id}\{Param1}",
@"Home.mvc\123\p1",
@"/Home.mvc\123\p1",
null,
new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
}
@ -605,7 +606,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
@"(Controller).mvc",
@"(Controller).mvc",
@"/(Controller).mvc",
null,
new RouteValueDictionary());
}
@ -615,7 +616,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
@"Controller.mvc/ ",
@"Controller.mvc/ ",
@"/Controller.mvc/ ",
null,
new RouteValueDictionary());
}
@ -625,7 +626,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
@"Controller.mvc ",
@"Controller.mvc ",
@"/Controller.mvc ",
null,
new RouteValueDictionary());
}
@ -636,7 +637,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
// DevDiv Bugs 189892: UrlRouting: Catch all parameter cannot capture url segments that contain the "."
RunTest(
"Home/ShowPilot/{missionId}/{*name}",
"Home/ShowPilot/777/12345./foobar",
"/Home/ShowPilot/777/12345./foobar",
new RouteValueDictionary(new
{
controller = "Home",
@ -654,7 +655,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{*p2}");
// Act
var rd = matcher.Match("v1/v2/v3");
var rd = matcher.Match("/v1/v2/v3");
// Assert
Assert.NotNull(rd);
@ -670,7 +671,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{*p2}");
// Act
var rd = matcher.Match("v1/");
var rd = matcher.Match("/v1/");
// Assert
Assert.NotNull(rd);
@ -686,7 +687,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{*p2}");
// Act
var rd = matcher.Match("v1");
var rd = matcher.Match("/v1");
// Assert
Assert.NotNull(rd);
@ -702,7 +703,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
// Act
var rd = matcher.Match("v1");
var rd = matcher.Match("/v1");
// Assert
Assert.NotNull(rd);
@ -718,7 +719,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
// Act
var rd = matcher.Match("v1/hello/whatever");
var rd = matcher.Match("/v1/hello/whatever");
// Assert
Assert.NotNull(rd);
@ -733,7 +734,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
RunTest(
"foo",
"fooBAR",
"/fooBAR",
null,
null);
}
@ -744,7 +745,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
RunTest(
"foo",
"BARfoo",
"/BARfoo",
null,
null);
}
@ -755,7 +756,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
RunTest(
"foo",
"BARfooBAR",
"/BARfooBAR",
null,
null);
}
@ -766,7 +767,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
RunTest(
"foo",
"foo",
"/foo",
null,
new RouteValueDictionary());
}
@ -776,7 +777,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"foo/{ }/{.!$%}/{dynamic.data}/{op.tional}",
"foo/space/weird/orderid",
"/foo/space/weird/orderid",
new RouteValueDictionary() { { " ", "not a space" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } },
new RouteValueDictionary() { { " ", "space" }, { ".!$%", "weird" }, { "dynamic.data", "orderid" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } });
}
@ -786,7 +787,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{controller}/{language}-{locale}",
"foo",
"/foo",
new RouteValueDictionary(new { language = "en", locale = "US" }),
null);
}
@ -796,7 +797,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{controller}/{language}-{locale}",
"foo/xx-",
"/foo/xx-",
new RouteValueDictionary(new { language = "en", locale = "US" }),
null);
}
@ -806,7 +807,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{controller}/{language}-{locale}",
"foo/-yy",
"/foo/-yy",
new RouteValueDictionary(new { language = "en", locale = "US" }),
null);
}
@ -816,7 +817,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
RunTest(
"{controller}/{language}-{locale}",
"foo/xx-yy",
"/foo/xx-yy",
new RouteValueDictionary(new { language = "en", locale = "US" }),
new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } });
}
@ -826,7 +827,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
// Arrange
var route = CreateMatcher("{controller}/{action?}");
var url = "Home/Index";
var url = "/Home/Index";
// Act
var match = route.Match(url);
@ -843,7 +844,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
// Arrange
var route = CreateMatcher("{controller}/{action?}");
var url = "Home";
var url = "/Home";
// Act
var match = route.Match(url);
@ -891,7 +892,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
// Arrange
var route = CreateMatcher("{controller}/{action?}/{id?}");
var url = "Home/Index";
var url = "/Home/Index";
// Act
var match = route.Match(url);
@ -923,7 +924,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
defaults ?? new Dictionary<string, object>());
// Act
var match = matcher.Match(path);
var match = matcher.Match(new PathString(path));
// Assert
if (expected == null)