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 KoreBuild -ExcludeVersion -o packages -nocache -pre
|
||||||
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
|
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
|
||||||
|
|
||||||
IF "%SKIP_KRE_INSTALL%"=="1" goto run
|
IF "%SKIP_DOTNET_INSTALL%"=="1" goto run
|
||||||
CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86
|
CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86
|
||||||
CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86
|
CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86
|
||||||
|
|
||||||
:run
|
: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 %*
|
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
|
fi
|
||||||
|
|
||||||
if ! type k > /dev/null 2>&1; then
|
if ! type k > /dev/null 2>&1; then
|
||||||
source packages/KoreBuild/build/kvm.sh
|
source packages/KoreBuild/build/dotnetsdk.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! type k > /dev/null 2>&1; then
|
if ! type k > /dev/null 2>&1; then
|
||||||
kvm upgrade
|
dotnetsdk upgrade
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"
|
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"
|
||||||
|
|
|
||||||
|
|
@ -205,17 +205,17 @@ namespace Microsoft.AspNet.Routing
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string TemplateRoute_CannotHaveOptionalParameterInMultiSegment
|
internal static string TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator
|
||||||
{
|
{
|
||||||
get { return GetString("TemplateRoute_CannotHaveOptionalParameterInMultiSegment"); }
|
get { return GetString("TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatTemplateRoute_CannotHaveOptionalParameterInMultiSegment()
|
internal static string FormatTemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator()
|
||||||
{
|
{
|
||||||
return GetString("TemplateRoute_CannotHaveOptionalParameterInMultiSegment");
|
return GetString("TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -153,8 +153,8 @@
|
||||||
<data name="TemplateRoute_CannotHaveConsecutiveSeparators" xml:space="preserve">
|
<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>
|
<value>The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TemplateRoute_CannotHaveOptionalParameterInMultiSegment" xml:space="preserve">
|
<data name="TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator" 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>
|
<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>
|
||||||
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
||||||
<value>A catch-all parameter cannot be marked optional.</value>
|
<value>A catch-all parameter cannot be marked optional.</value>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.AspNet.Routing.Template;
|
using Microsoft.AspNet.Routing.Template;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing
|
namespace Microsoft.AspNet.Builder
|
||||||
{
|
{
|
||||||
public static class RouteBuilderExtensions
|
public static class RouteBuilderExtensions
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.AspNet.WebUtilities;
|
using Microsoft.AspNet.Http.Extensions;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing.Template
|
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.
|
// we won't necessarily add it to the URI we generate.
|
||||||
if (!context.Buffer(converted))
|
if (!context.Buffer(converted))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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))
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Remove(string literal)
|
||||||
|
{
|
||||||
|
_uri.Length -= literal.Length;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Buffer(string value)
|
public bool Buffer(string value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
|
|
|
||||||
|
|
@ -168,17 +168,63 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
string requestSegment,
|
string requestSegment,
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
IReadOnlyDictionary<string, object> defaults,
|
||||||
RouteValueDictionary values)
|
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 != null);
|
||||||
Debug.Assert(routeSegment.Parts.Count > 1);
|
Debug.Assert(routeSegment.Parts.Count > 1);
|
||||||
|
|
||||||
// Find last literal segment and get its last index in the string
|
// Find last literal segment and get its last index in the string
|
||||||
var lastIndex = requestSegment.Length;
|
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 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
|
TemplatePart lastLiteral = null; // Keeps track of the left-most literal we've encountered
|
||||||
|
|
||||||
|
var outValues = new RouteValueDictionary();
|
||||||
|
|
||||||
while (indexOfLastSegmentUsed >= 0)
|
while (indexOfLastSegmentUsed >= 0)
|
||||||
{
|
{
|
||||||
var newLastIndex = lastIndex;
|
var newLastIndex = lastIndex;
|
||||||
|
|
@ -187,7 +233,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
if (part.IsParameter)
|
if (part.IsParameter)
|
||||||
{
|
{
|
||||||
// Hold on to the parameter so that we can fill it in when we locate the next literal
|
// Hold on to the parameter so that we can fill it in when we locate the next literal
|
||||||
parameterNeedsValue = part;
|
parameterNeedsValue = part;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -209,10 +255,10 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
var indexOfLiteral = requestSegment.LastIndexOf(part.Text,
|
var indexOfLiteral = requestSegment.LastIndexOf(part.Text,
|
||||||
startIndex,
|
startIndex,
|
||||||
StringComparison.OrdinalIgnoreCase);
|
StringComparison.OrdinalIgnoreCase);
|
||||||
if (indexOfLiteral == -1)
|
if (indexOfLiteral == -1)
|
||||||
{
|
{
|
||||||
// If we couldn't find this literal index, this segment cannot match
|
// 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.
|
// 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.
|
// 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
|
// 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;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If there's a value in the segment for this parameter, use the subsegment value
|
// 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;
|
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*
|
// 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.
|
// 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.
|
// 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 EqualsSign = '=';
|
||||||
private const char QuestionMark = '?';
|
private const char QuestionMark = '?';
|
||||||
private const char Asterisk = '*';
|
private const char Asterisk = '*';
|
||||||
|
private const string PeriodString = ".";
|
||||||
|
|
||||||
public static RouteTemplate Parse(string routeTemplate)
|
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++)
|
for (var i = 0; i < segment.Parts.Count; i++)
|
||||||
{
|
{
|
||||||
var part = segment.Parts[i];
|
var part = segment.Parts[i];
|
||||||
|
|
||||||
if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1)
|
if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1)
|
||||||
{
|
{
|
||||||
context.Error = Resources.TemplateRoute_CannotHaveOptionalParameterInMultiSegment;
|
// This is the last part
|
||||||
return false;
|
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;
|
var isLastSegmentParameter = false;
|
||||||
for (var i = 0; i < segment.Parts.Count; i++)
|
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 IsLiteral { get; private set; }
|
||||||
public bool IsParameter { get; private set; }
|
public bool IsParameter { get; private set; }
|
||||||
public bool IsOptional { get; private set; }
|
public bool IsOptional { get; private set; }
|
||||||
|
public bool IsOptionalSeperator { get; set; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public string Text { get; private set; }
|
public string Text { get; private set; }
|
||||||
public object DefaultValue { get; private set; }
|
public object DefaultValue { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
|
"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-*"
|
"Microsoft.Framework.Logging": "1.0.0-*"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.PipelineCore;
|
using Microsoft.AspNet.Http.Core;
|
||||||
using Microsoft.AspNet.Routing.Logging;
|
using Microsoft.AspNet.Routing.Logging;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,131 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
"language/axx-yy");
|
"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]
|
[Fact]
|
||||||
public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndMatches()
|
public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndMatches()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,103 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
Assert.Equal("default p2", rd["p2"]);
|
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]
|
[Fact]
|
||||||
public void MatchRouteWithOnlyLiterals()
|
public void MatchRouteWithOnlyLiterals()
|
||||||
{
|
{
|
||||||
|
|
@ -183,7 +280,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(rd);
|
Assert.Null(rd);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void NoMatchLongerUrl()
|
public void NoMatchLongerUrl()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new 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]);
|
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -58,7 +59,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new 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]);
|
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -227,12 +229,275 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
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]
|
[Fact]
|
||||||
public void InvalidTemplate_WithRepeatedParameter()
|
public void InvalidTemplate_WithRepeatedParameter()
|
||||||
{
|
{
|
||||||
var ex = ExceptionAssert.Throws<ArgumentException>(
|
var ex = ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
|
() => 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]
|
[Theory]
|
||||||
|
|
@ -246,7 +511,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse(template),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +521,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("123{a}abc{*moo}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,7 +531,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{*p1}/{*p2}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,7 +541,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{*p1}abc{*p2}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,7 +584,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("foo/{{p1}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,7 +594,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("foo/{p1}}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -333,7 +604,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{aaa}/{AAA}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,7 +614,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{aaa}/{*AAA}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,7 +624,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{a}/{aa}a}/{z}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,7 +670,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{a}//{z}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,7 +680,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,7 +690,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("foo/aa{p1}{p2}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,7 +718,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("foor?bar"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,7 +740,9 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{foorb?}-bar-{z}"),
|
() => 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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Routing.Constraints;
|
using Microsoft.AspNet.Routing.Constraints;
|
||||||
using Microsoft.AspNet.Routing.Logging;
|
using Microsoft.AspNet.Routing.Logging;
|
||||||
|
|
@ -210,7 +211,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
Assert.True(routeValues.ContainsKey("country"));
|
Assert.True(routeValues.ContainsKey("country"));
|
||||||
Assert.Equal("USA", routeValues["country"]);
|
Assert.Equal("USA", routeValues["country"]);
|
||||||
Assert.True(routeValues.ContainsKey("id"));
|
Assert.True(routeValues.ContainsKey("id"));
|
||||||
Assert.Equal("5",routeValues["id"]);
|
Assert.Equal("5", routeValues["id"]);
|
||||||
|
|
||||||
Assert.True(context.RouteData.Values.ContainsKey("country"));
|
Assert.True(context.RouteData.Values.ContainsKey("country"));
|
||||||
Assert.Equal("USA", context.RouteData.Values["country"]);
|
Assert.Equal("USA", context.RouteData.Values["country"]);
|
||||||
|
|
@ -224,82 +225,6 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
Assert.NotSame(route.DataTokens, 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]
|
[Fact]
|
||||||
public async Task RouteAsync_MergesExistingRouteData_PassedToConstraint()
|
public async Task RouteAsync_MergesExistingRouteData_PassedToConstraint()
|
||||||
{
|
{
|
||||||
|
|
@ -568,7 +493,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
|
|
||||||
Assert.NotEmpty(route.Constraints);
|
Assert.NotEmpty(route.Constraints);
|
||||||
Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
|
Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await route.RouteAsync(context);
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
|
@ -647,7 +572,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
mockTarget.Object,
|
mockTarget.Object,
|
||||||
template,
|
template,
|
||||||
defaults: null,
|
defaults: null,
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
dataTokens: null,
|
dataTokens: null,
|
||||||
inlineConstraintResolver: _inlineConstraintResolver);
|
inlineConstraintResolver: _inlineConstraintResolver);
|
||||||
|
|
||||||
|
|
@ -657,7 +582,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
Assert.IsType<CompositeRouteConstraint>(innerConstraint);
|
Assert.IsType<CompositeRouteConstraint>(innerConstraint);
|
||||||
var compositeConstraint = (CompositeRouteConstraint)innerConstraint;
|
var compositeConstraint = (CompositeRouteConstraint)innerConstraint;
|
||||||
Assert.Equal(compositeConstraint.Constraints.Count<IRouteConstraint>(), 2);
|
Assert.Equal(compositeConstraint.Constraints.Count<IRouteConstraint>(), 2);
|
||||||
|
|
||||||
Assert.Single(compositeConstraint.Constraints, c => c is IntRouteConstraint);
|
Assert.Single(compositeConstraint.Constraints, c => c is IntRouteConstraint);
|
||||||
Assert.Single(compositeConstraint.Constraints, c => c is RangeRouteConstraint);
|
Assert.Single(compositeConstraint.Constraints, c => c is RangeRouteConstraint);
|
||||||
|
|
||||||
|
|
@ -705,7 +630,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await route.RouteAsync(context);
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(context.IsHandled);
|
Assert.False(context.IsHandled);
|
||||||
}
|
}
|
||||||
|
|
@ -862,6 +787,73 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
Assert.Null(context.RouteData.Values["1controller"]);
|
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)
|
private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
|
||||||
{
|
{
|
||||||
if (factory == null)
|
if (factory == null)
|
||||||
|
|
@ -1070,7 +1062,9 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
.Returns<string>(null);
|
.Returns<string>(null);
|
||||||
|
|
||||||
var route = CreateRoute(target.Object, "{controller}/{action}");
|
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" });
|
var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" });
|
||||||
|
|
||||||
|
|
@ -1094,9 +1088,11 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
.Returns<string>(null);
|
.Returns<string>(null);
|
||||||
|
|
||||||
var route = CreateRoute(target.Object, "Admin/{controller}/{action}", new { area = "Admin" });
|
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
|
// Act
|
||||||
var path = route.GetVirtualPath(context);
|
var path = route.GetVirtualPath(context);
|
||||||
|
|
@ -1118,7 +1114,8 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
.Returns<string>(null);
|
.Returns<string>(null);
|
||||||
|
|
||||||
var route = CreateRoute(target.Object, "{controller}/{action}");
|
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" });
|
var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" });
|
||||||
|
|
||||||
|
|
@ -1247,7 +1244,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
// Arrange
|
// Arrange
|
||||||
var route = CreateRoute("{controller}/{action}/{id:int}");
|
var route = CreateRoute("{controller}/{action}/{id:int}");
|
||||||
var context = CreateVirtualPathContext(
|
var context = CreateVirtualPathContext(
|
||||||
values: new { action = "Index", controller = "Home", id = 4 });
|
values: new { action = "Index", controller = "Home", id = 4 });
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var path = route.GetVirtualPath(context);
|
var path = route.GetVirtualPath(context);
|
||||||
|
|
@ -1352,7 +1349,166 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
Assert.Equal("Home/Index/products", path);
|
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)
|
private static VirtualPathContext CreateVirtualPathContext(object values)
|
||||||
{
|
{
|
||||||
return CreateVirtualPathContext(new RouteValueDictionary(values), null);
|
return CreateVirtualPathContext(new RouteValueDictionary(values), null);
|
||||||
|
|
@ -1363,7 +1519,9 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
return CreateVirtualPathContext(new RouteValueDictionary(values), new RouteValueDictionary(ambientValues));
|
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);
|
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||||
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,7 @@
|
||||||
#if ASPNET50
|
#if ASPNET50
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using Microsoft.AspNet.Builder;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using Microsoft.AspNet.Http;
|
|
||||||
using Microsoft.AspNet.Routing.Constraints;
|
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
"warningsAsErrors": "true"
|
"warningsAsErrors": "true"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
"Microsoft.AspNet.Http.Core": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Routing": "1.0.0-*",
|
"Microsoft.AspNet.Routing": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||||
"Xunit.KRunner": "1.0.0-*"
|
"xunit.runner.kre": "1.0.0-*"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"aspnetcore50": {},
|
"aspnetcore50": {},
|
||||||
|
|
@ -17,6 +17,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"test": "Xunit.KRunner"
|
"test": "xunit.runner.kre"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue