Basic URL Extension functionality working.
1. Template parser now allows a parameter to be an optional parameter in a complex segment if it is the last and only optional parameter and it is followed by a period. 2. Template matcher modified to take into consideration the optional parameter in the complex segment. Also the period shouldn't be present if the optional parameter is not present
This commit is contained in:
parent
5e55833168
commit
3626900bc9
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -223,83 +223,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.NotSame(originalDataTokens, context.RouteData.DataTokens);
|
||||
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()
|
||||
{
|
||||
|
|
@ -862,6 +786,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 +1061,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 +1087,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 +1113,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" });
|
||||
|
||||
|
|
@ -1352,7 +1348,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 +1518,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)))
|
||||
|
|
|
|||
Loading…
Reference in New Issue