Merge branch 'release' of github.com:aspnet/Routing into release
This commit is contained in:
commit
95b91a60d4
|
|
@ -19,10 +19,10 @@ IF EXIST packages\KoreBuild goto run
|
|||
.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
|
||||
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
|
||||
|
||||
IF "%SKIP_KRE_INSTALL%"=="1" goto run
|
||||
CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86
|
||||
CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86
|
||||
IF "%SKIP_DOTNET_INSTALL%"=="1" goto run
|
||||
CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86
|
||||
CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86
|
||||
|
||||
:run
|
||||
CALL packages\KoreBuild\build\kvm use default -runtime CLR -x86
|
||||
CALL packages\KoreBuild\build\dotnetsdk use default -runtime CLR -x86
|
||||
packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %*
|
||||
|
|
|
|||
4
build.sh
4
build.sh
|
|
@ -28,11 +28,11 @@ if test ! -d packages/KoreBuild; then
|
|||
fi
|
||||
|
||||
if ! type k > /dev/null 2>&1; then
|
||||
source packages/KoreBuild/build/kvm.sh
|
||||
source packages/KoreBuild/build/dotnetsdk.sh
|
||||
fi
|
||||
|
||||
if ! type k > /dev/null 2>&1; then
|
||||
kvm upgrade
|
||||
dotnetsdk upgrade
|
||||
fi
|
||||
|
||||
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"
|
||||
|
|
|
|||
|
|
@ -205,17 +205,17 @@ namespace Microsoft.AspNet.Routing
|
|||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveOptionalParameterInMultiSegment
|
||||
internal static string TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator
|
||||
{
|
||||
get { return GetString("TemplateRoute_CannotHaveOptionalParameterInMultiSegment"); }
|
||||
get { return GetString("TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveOptionalParameterInMultiSegment()
|
||||
internal static string FormatTemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator()
|
||||
{
|
||||
return GetString("TemplateRoute_CannotHaveOptionalParameterInMultiSegment");
|
||||
return GetString("TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -153,8 +153,8 @@
|
|||
<data name="TemplateRoute_CannotHaveConsecutiveSeparators" xml:space="preserve">
|
||||
<value>The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveOptionalParameterInMultiSegment" xml:space="preserve">
|
||||
<value>A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.</value>
|
||||
<data name="TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator" xml:space="preserve">
|
||||
<value>In a path segment that contains more than one section, such as a literal section or a parameter, there can only be one optional parameter. The optional parameter must be the last parameter in the segment and must be preceded by one single period (.).</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
||||
<value>A catch-all parameter cannot be marked optional.</value>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Routing.Template;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
namespace Microsoft.AspNet.Builder
|
||||
{
|
||||
public static class RouteBuilderExtensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using System.Diagnostics;
|
|||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using Microsoft.AspNet.Http.Extensions;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template
|
||||
{
|
||||
|
|
@ -220,14 +220,29 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
// we won't necessarily add it to the URI we generate.
|
||||
if (!context.Buffer(converted))
|
||||
{
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the value is not accepted, it is null or empty value in the
|
||||
// middle of the segment. We accept this if the parameter is an
|
||||
// optional parameter and it is preceded by an optional seperator.
|
||||
// I this case, we need to remove the optional seperator that we
|
||||
// have added to the URI
|
||||
// Example: template = {id}.{format?}. parameters: id=5
|
||||
// In this case after we have generated "5.", we wont find any value
|
||||
// for format, so we remove '.' and generate 5.
|
||||
if (!context.Accept(converted))
|
||||
{
|
||||
return null;
|
||||
if (j != 0 && part.IsOptional && segment.Parts[j - 1].IsOptionalSeperator)
|
||||
{
|
||||
context.Remove(segment.Parts[j - 1].Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -472,6 +487,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return true;
|
||||
}
|
||||
|
||||
public void Remove(string literal)
|
||||
{
|
||||
_uri.Length -= literal.Length;
|
||||
}
|
||||
|
||||
public bool Buffer(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
|
|
|
|||
|
|
@ -168,17 +168,63 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
string requestSegment,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
RouteValueDictionary values)
|
||||
{
|
||||
var indexOfLastSegment = routeSegment.Parts.Count - 1;
|
||||
|
||||
// We match the request to the template starting at the rightmost parameter
|
||||
// If the last segment of template is optional, then request can match the
|
||||
// template with or without the last parameter. So we start with regular matching,
|
||||
// but if it doesn't match, we start with next to last parameter. Example:
|
||||
// Template: {p1}/{p2}.{p3?}. If the request is foo/bar.moo it will match right away
|
||||
// giving p3 value of moo. But if the request is foo/bar, we start matching from the
|
||||
// rightmost giving p3 the value of bar, then we end up not matching the segment.
|
||||
// In this case we start again from p2 to match the request and we succeed giving
|
||||
// the value bar to p2
|
||||
if (routeSegment.Parts[indexOfLastSegment].IsOptional &&
|
||||
routeSegment.Parts[indexOfLastSegment - 1].IsOptionalSeperator)
|
||||
{
|
||||
if (MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requestSegment.EndsWith(routeSegment.Parts[indexOfLastSegment - 1].Text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return MatchComplexSegmentCore(
|
||||
routeSegment,
|
||||
requestSegment,
|
||||
Defaults,
|
||||
values,
|
||||
indexOfLastSegment - 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment);
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchComplexSegmentCore(TemplateSegment routeSegment,
|
||||
string requestSegment,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
RouteValueDictionary values,
|
||||
int indexOfLastSegmentUsed)
|
||||
{
|
||||
Debug.Assert(routeSegment != null);
|
||||
Debug.Assert(routeSegment.Parts.Count > 1);
|
||||
|
||||
// Find last literal segment and get its last index in the string
|
||||
var lastIndex = requestSegment.Length;
|
||||
var indexOfLastSegmentUsed = routeSegment.Parts.Count - 1;
|
||||
|
||||
|
||||
TemplatePart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value
|
||||
TemplatePart lastLiteral = null; // Keeps track of the left-most literal we've encountered
|
||||
|
||||
var outValues = new RouteValueDictionary();
|
||||
|
||||
while (indexOfLastSegmentUsed >= 0)
|
||||
{
|
||||
var newLastIndex = lastIndex;
|
||||
|
|
@ -187,7 +233,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
if (part.IsParameter)
|
||||
{
|
||||
// Hold on to the parameter so that we can fill it in when we locate the next literal
|
||||
parameterNeedsValue = part;
|
||||
parameterNeedsValue = part;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -209,10 +255,10 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
var indexOfLiteral = requestSegment.LastIndexOf(part.Text,
|
||||
startIndex,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
if (indexOfLiteral == -1)
|
||||
if (indexOfLiteral == -1)
|
||||
{
|
||||
// If we couldn't find this literal index, this segment cannot match
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the first subsegment is a literal, it must match at the right-most extent of the request URI.
|
||||
|
|
@ -271,13 +317,14 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
// If we're here that means we have a segment that contains multiple sub-segments.
|
||||
// For these segments all parameters must have non-empty values. If the parameter
|
||||
// has an empty value it's not a match.
|
||||
// has an empty value it's not a match.
|
||||
return false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's a value in the segment for this parameter, use the subsegment value
|
||||
values.Add(parameterNeedsValue.Name, parameterValueString);
|
||||
outValues.Add(parameterNeedsValue.Name, parameterValueString);
|
||||
}
|
||||
|
||||
parameterNeedsValue = null;
|
||||
|
|
@ -294,7 +341,17 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
// the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire*
|
||||
// request URI in order for it to be a match.
|
||||
// This check is related to the check we do earlier in this function for LiteralSubsegments.
|
||||
return (lastIndex == 0) || routeSegment.Parts[0].IsParameter;
|
||||
if (lastIndex == 0 || routeSegment.Parts[0].IsParameter)
|
||||
{
|
||||
foreach (var item in outValues)
|
||||
{
|
||||
values.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
private const char EqualsSign = '=';
|
||||
private const char QuestionMark = '?';
|
||||
private const char Asterisk = '*';
|
||||
private const string PeriodString = ".";
|
||||
|
||||
public static RouteTemplate Parse(string routeTemplate)
|
||||
{
|
||||
|
|
@ -318,18 +319,40 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
}
|
||||
|
||||
// if a segment has multiple parts, then the parameters can't be optional
|
||||
// if a segment has multiple parts, then only the last one parameter can be optional
|
||||
// if it is following a optional seperator.
|
||||
for (var i = 0; i < segment.Parts.Count; i++)
|
||||
{
|
||||
var part = segment.Parts[i];
|
||||
|
||||
if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1)
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CannotHaveOptionalParameterInMultiSegment;
|
||||
return false;
|
||||
// This is the last part
|
||||
if (i == segment.Parts.Count - 1)
|
||||
{
|
||||
Debug.Assert(segment.Parts[i - 1].IsLiteral);
|
||||
|
||||
if (segment.Parts[i - 1].Text == PeriodString)
|
||||
{
|
||||
segment.Parts[i - 1].IsOptionalSeperator = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Error =
|
||||
Resources.TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Error =
|
||||
Resources.TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A segment cannot containt two consecutive parameters
|
||||
// A segment cannot contain two consecutive parameters
|
||||
var isLastSegmentParameter = false;
|
||||
for (var i = 0; i < segment.Parts.Count; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
public bool IsLiteral { get; private set; }
|
||||
public bool IsParameter { get; private set; }
|
||||
public bool IsOptional { get; private set; }
|
||||
public bool IsOptionalSeperator { get; set; }
|
||||
public string Name { get; private set; }
|
||||
public string Text { get; private set; }
|
||||
public object DefaultValue { get; private set; }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
|
||||
"Microsoft.AspNet.WebUtilities": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Http.Core;
|
||||
using Microsoft.AspNet.Routing.Logging;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,131 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
"language/axx-yy");
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> OptionalParamValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new object[][]
|
||||
{
|
||||
// defaults
|
||||
// ambient values
|
||||
// values
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}/{val2}.{val3?}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2"}),
|
||||
new RouteValueDictionary(new {val3 = "someval3"}),
|
||||
new RouteValueDictionary(new {val3 = "someval3"}),
|
||||
"Test/someval1/someval2.someval3",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}/{val2}.{val3?}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2"}),
|
||||
new RouteValueDictionary(new {val3 = "someval3a"}),
|
||||
new RouteValueDictionary(new {val3 = "someval3v"}),
|
||||
"Test/someval1/someval2.someval3v",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}/{val2}.{val3?}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2"}),
|
||||
new RouteValueDictionary(new {val3 = "someval3a"}),
|
||||
new RouteValueDictionary(),
|
||||
"Test/someval1/someval2.someval3a",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}/{val2}.{val3?}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2"}),
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(new {val3 = "someval3v"}),
|
||||
"Test/someval1/someval2.someval3v",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}/{val2}.{val3?}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2"}),
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(),
|
||||
"Test/someval1/someval2",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}.{val2}.{val3}.{val4?}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2" }),
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(new {val4 = "someval4", val3 = "someval3" }),
|
||||
"Test/someval1.someval2.someval3.someval4",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}.{val2}.{val3}.{val4?}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2" }),
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(new {val3 = "someval3" }),
|
||||
"Test/someval1.someval2.someval3",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/.{val2?}",
|
||||
new RouteValueDictionary(new { }),
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(new {val2 = "someval2" }),
|
||||
"Test/.someval2",
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Test/{val1}.{val2}",
|
||||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2" }),
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(new {val3 = "someval3" }),
|
||||
"Test/someval1.someval2?val3=someval3",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("OptionalParamValues")]
|
||||
public void GetVirtualPathWithMultiSegmentWithOptionalParam(
|
||||
string template,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
IDictionary<string, object> ambientValues,
|
||||
IDictionary<string, object> values,
|
||||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TemplateBinder(
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
|
||||
// Act & Assert
|
||||
var result = binder.GetValues(ambientValues: ambientValues, values: values);
|
||||
if (result == null)
|
||||
{
|
||||
if (expected == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
}
|
||||
|
||||
var boundTemplate = binder.BindValues(result.AcceptedValues);
|
||||
if (expected == null)
|
||||
{
|
||||
Assert.Null(boundTemplate);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(boundTemplate);
|
||||
Assert.Equal(expected, boundTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndMatches()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -101,6 +101,103 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
Assert.Equal("default p2", rd["p2"]);
|
||||
}
|
||||
|
||||
[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)]
|
||||
public void MatchRoute_OptionalParameter_FollowedByPeriod_Valid(
|
||||
string template,
|
||||
string path,
|
||||
string p1,
|
||||
string p2)
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher(template);
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match(path);
|
||||
|
||||
// Assert
|
||||
if (p1 != null)
|
||||
{
|
||||
Assert.Equal(p1, rd["p1"]);
|
||||
}
|
||||
if (p2 != null)
|
||||
{
|
||||
Assert.Equal(p2, rd["p2"]);
|
||||
}
|
||||
}
|
||||
|
||||
[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")]
|
||||
public void MatchRoute_OptionalParameter_FollowedByPeriod_3Parameters_Valid(
|
||||
string template,
|
||||
string path,
|
||||
string p1,
|
||||
string p2,
|
||||
string p3)
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher(template);
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match(path);
|
||||
|
||||
// Assert
|
||||
|
||||
Assert.Equal(p1, rd["p1"]);
|
||||
|
||||
if (p2 != null)
|
||||
{
|
||||
Assert.Equal(p2, rd["p2"]);
|
||||
}
|
||||
|
||||
if (p3 != null)
|
||||
{
|
||||
Assert.Equal(p3, rd["p3"]);
|
||||
}
|
||||
}
|
||||
|
||||
[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 void MatchRoute_OptionalParameter_FollowedByPeriod_Invalid(string template, string path)
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher(template);
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match(path);
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteWithOnlyLiterals()
|
||||
{
|
||||
|
|
@ -183,7 +280,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Assert
|
||||
Assert.Null(rd);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void NoMatchLongerUrl()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(
|
||||
TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
|
|
@ -58,7 +59,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(
|
||||
TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
|
|
@ -227,12 +229,275 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2?}";
|
||||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
true,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_ParametersFollowingPeriod()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2}";
|
||||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_ThreeParameters()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2}.{p3?}";
|
||||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
true,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[4]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_ThreeParametersSeperatedByPeriod()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2}.{p3}";
|
||||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[4]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_MiddleSegment()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2?}/{p3}";
|
||||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
true,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null));
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_LastSegment()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}/{p2}.{p3?}";
|
||||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
true,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_PeriodAfterSlash()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p2}/.{p3?}";
|
||||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[1]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{p1?}.{p2?}")]
|
||||
[InlineData("{p1}.{p2?}.{p3}")]
|
||||
[InlineData("{p1?}.{p2}/{p3}")]
|
||||
[InlineData("{p3}.{p1?}.{p2?}")]
|
||||
[InlineData("{p1}-{p2?}")]
|
||||
[InlineData("{p1}..{p2?}")]
|
||||
[InlineData("..{p2?}")]
|
||||
[InlineData("{p1}.abc.{p2?}")]
|
||||
public void Parse_ComplexSegment_OptionalParametersSeperatedByPeriod_Invalid(string template)
|
||||
{
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"In a path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"there can only be one optional parameter. The optional parameter must be the last parameter in the " +
|
||||
"segment and must be preceded by one single period (.)." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_WithRepeatedParameter()
|
||||
{
|
||||
var ex = ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
|
||||
"The route parameter name 'controller' appears more than one time in the route template." + Environment.NewLine + "Parameter name: routeTemplate");
|
||||
"The route parameter name 'controller' appears more than one time in the route template." +
|
||||
Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -246,7 +511,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
@"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
@"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +521,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("123{a}abc{*moo}"),
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter." + Environment.NewLine +
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"cannot contain a catch-all parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +531,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{*p1}/{*p2}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +541,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{*p1}abc{*p2}"),
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter." + Environment.NewLine +
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"cannot contain a catch-all parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +584,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{{p1}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -324,7 +594,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{p1}}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -333,7 +604,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +614,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{*AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +624,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{aa}a}/{z}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +670,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}//{z}"),
|
||||
"The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value." + Environment.NewLine +
|
||||
"The route template separator character '/' cannot appear consecutively. It must be separated by " +
|
||||
"either a parameter or a literal value." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -405,7 +680,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -414,7 +690,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/aa{p1}{p2}"),
|
||||
"A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string." + Environment.NewLine +
|
||||
"A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by " +
|
||||
"a literal string." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -441,7 +718,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foor?bar"),
|
||||
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." + Environment.NewLine +
|
||||
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -462,7 +740,9 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{foorb?}-bar-{z}"),
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter." + Environment.NewLine +
|
||||
"In a path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"there can only be one optional parameter. The optional parameter must be the last parameter in " +
|
||||
"the segment and must be preceded by one single period (.)." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Constraints;
|
||||
using Microsoft.AspNet.Routing.Logging;
|
||||
|
|
@ -210,7 +211,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.True(routeValues.ContainsKey("country"));
|
||||
Assert.Equal("USA", routeValues["country"]);
|
||||
Assert.True(routeValues.ContainsKey("id"));
|
||||
Assert.Equal("5",routeValues["id"]);
|
||||
Assert.Equal("5", routeValues["id"]);
|
||||
|
||||
Assert.True(context.RouteData.Values.ContainsKey("country"));
|
||||
Assert.Equal("USA", context.RouteData.Values["country"]);
|
||||
|
|
@ -224,82 +225,6 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.NotSame(route.DataTokens, context.RouteData.DataTokens);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstrait_OptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:int?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index/5");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeValues);
|
||||
Assert.True(routeValues.ContainsKey("id"));
|
||||
Assert.Equal("5", routeValues["id"]);
|
||||
|
||||
Assert.True(context.RouteData.Values.ContainsKey("id"));
|
||||
Assert.Equal("5", context.RouteData.Values["id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstrait_OptionalParameter_NotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:int?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeValues);
|
||||
Assert.False(routeValues.ContainsKey("id"));
|
||||
|
||||
Assert.False(context.RouteData.Values.ContainsKey("id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_MergesExistingRouteData_PassedToConstraint()
|
||||
{
|
||||
|
|
@ -568,7 +493,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
Assert.NotEmpty(route.Constraints);
|
||||
Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
|
||||
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
|
|
@ -647,7 +572,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: constraints,
|
||||
constraints: constraints,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
|
|
@ -657,7 +582,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.IsType<CompositeRouteConstraint>(innerConstraint);
|
||||
var compositeConstraint = (CompositeRouteConstraint)innerConstraint;
|
||||
Assert.Equal(compositeConstraint.Constraints.Count<IRouteConstraint>(), 2);
|
||||
|
||||
|
||||
Assert.Single(compositeConstraint.Constraints, c => c is IntRouteConstraint);
|
||||
Assert.Single(compositeConstraint.Constraints, c => c is RangeRouteConstraint);
|
||||
|
||||
|
|
@ -705,7 +630,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.False(context.IsHandled);
|
||||
}
|
||||
|
|
@ -862,6 +787,73 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.Null(context.RouteData.Values["1controller"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_Success_OptionalParameter_ValueProvided()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
|
||||
var context = CreateRouteContext("/Home/Create.xml");
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.Equal(3, context.RouteData.Values.Count);
|
||||
Assert.Equal("Home", context.RouteData.Values["controller"]);
|
||||
Assert.Equal("Create", context.RouteData.Values["action"]);
|
||||
Assert.Equal("xml", context.RouteData.Values["format"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_Success_OptionalParameter_ValueNotProvided()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
|
||||
var context = CreateRouteContext("/Home/Create");
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.Equal(2, context.RouteData.Values.Count);
|
||||
Assert.Equal("Home", context.RouteData.Values["controller"]);
|
||||
Assert.Equal("Create", context.RouteData.Values["action"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_Success_OptionalParameter_DefaultValue()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index", format = "xml" });
|
||||
var context = CreateRouteContext("/Home/Create");
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.Equal(3, context.RouteData.Values.Count);
|
||||
Assert.Equal("Home", context.RouteData.Values["controller"]);
|
||||
Assert.Equal("Create", context.RouteData.Values["action"]);
|
||||
Assert.Equal("xml", context.RouteData.Values["format"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_Success_OptionalParameter_EndsWithDot()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
|
||||
var context = CreateRouteContext("/Home/Create.");
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.IsHandled);
|
||||
}
|
||||
|
||||
private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
|
||||
{
|
||||
if (factory == null)
|
||||
|
|
@ -1070,7 +1062,9 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
.Returns<string>(null);
|
||||
|
||||
var route = CreateRoute(target.Object, "{controller}/{action}");
|
||||
var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog" });
|
||||
var context = CreateVirtualPathContext(
|
||||
new { action = "Store" },
|
||||
new { Controller = "Home", action = "Blog" });
|
||||
|
||||
var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" });
|
||||
|
||||
|
|
@ -1094,9 +1088,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
.Returns<string>(null);
|
||||
|
||||
var route = CreateRoute(target.Object, "Admin/{controller}/{action}", new { area = "Admin" });
|
||||
var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog" });
|
||||
var context = CreateVirtualPathContext(
|
||||
new { action = "Store" }, new { Controller = "Home", action = "Blog" });
|
||||
|
||||
var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store", area = "Admin" });
|
||||
var expectedValues = new RouteValueDictionary(
|
||||
new { controller = "Home", action = "Store", area = "Admin" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
|
@ -1118,7 +1114,8 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
.Returns<string>(null);
|
||||
|
||||
var route = CreateRoute(target.Object, "{controller}/{action}");
|
||||
var context = CreateVirtualPathContext(new { action = "Store", id = 5 }, new { Controller = "Home", action = "Blog" });
|
||||
var context = CreateVirtualPathContext(
|
||||
new { action = "Store", id = 5 }, new { Controller = "Home", action = "Blog" });
|
||||
|
||||
var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" });
|
||||
|
||||
|
|
@ -1247,7 +1244,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}/{id:int}");
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", id = 4 });
|
||||
values: new { action = "Index", controller = "Home", id = 4 });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
|
@ -1352,7 +1349,166 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.Equal("Home/Index/products", path);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_ParameterPresentInValues()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/{name}.{format?}",
|
||||
defaults: null,
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", name = "products", format = "xml" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/products.xml", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/{name}.{format?}",
|
||||
defaults: null,
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", name = "products" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/products", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_ParameterPresentInValuesAndDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/{name}.{format?}",
|
||||
defaults: new { format = "json" },
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", name = "products", format = "xml" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/products.xml", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/{name}.{format?}",
|
||||
defaults: new { format = "json" },
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", name = "products" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/products", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_ParameterNotPresentInTemplate_PresentInValues()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/{name}",
|
||||
defaults: null,
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", name = "products", format = "json" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/products?format=json", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/.{name?}",
|
||||
defaults: null,
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", name = "products" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/.products", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/.{name?}",
|
||||
defaults: null,
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_OptionalParameter_InSimpleSegment()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/{name?}",
|
||||
defaults: null,
|
||||
accept: true,
|
||||
constraints: null);
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index", path);
|
||||
}
|
||||
|
||||
private static VirtualPathContext CreateVirtualPathContext(object values)
|
||||
{
|
||||
return CreateVirtualPathContext(new RouteValueDictionary(values), null);
|
||||
|
|
@ -1363,7 +1519,9 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return CreateVirtualPathContext(new RouteValueDictionary(values), new RouteValueDictionary(ambientValues));
|
||||
}
|
||||
|
||||
private static VirtualPathContext CreateVirtualPathContext(IDictionary<string, object> values, IDictionary<string, object> ambientValues)
|
||||
private static VirtualPathContext CreateVirtualPathContext(
|
||||
IDictionary<string, object> values,
|
||||
IDictionary<string, object> ambientValues)
|
||||
{
|
||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
#if ASPNET50
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Constraints;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
"warningsAsErrors": "true"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Core": "1.0.0-*",
|
||||
"Microsoft.AspNet.Routing": "1.0.0-*",
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Xunit.KRunner": "1.0.0-*"
|
||||
"xunit.runner.kre": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnetcore50": {},
|
||||
|
|
@ -17,6 +17,6 @@
|
|||
}
|
||||
},
|
||||
"commands": {
|
||||
"test": "Xunit.KRunner"
|
||||
"test": "xunit.runner.kre"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue