Adding optional parameters

This commit is contained in:
Ryan Nowak 2014-02-18 15:22:18 -08:00
parent 42ce8c6594
commit 64c29fe813
8 changed files with 279 additions and 176 deletions

View File

@ -0,0 +1,115 @@
// <auto-generated />
namespace Microsoft.AspNet.Routing
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Routing.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
/// </summary>
internal static string TemplateRoute_CannotHaveCatchAllInMultiSegment
{
get { return GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment"); }
}
/// <summary>
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
/// </summary>
internal static string TemplateRoute_CannotHaveConsecutiveParameters
{
get { return GetString("TemplateRoute_CannotHaveConsecutiveParameters"); }
}
/// <summary>
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
/// </summary>
internal static string TemplateRoute_CannotHaveConsecutiveSeparators
{
get { return GetString("TemplateRoute_CannotHaveConsecutiveSeparators"); }
}
/// <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
{
get { return GetString("TemplateRoute_CannotHaveOptionalParameterInMultiSegment"); }
}
/// <summary>
/// A catch-all parameter can only appear as the last segment of the route template.
/// </summary>
internal static string TemplateRoute_CatchAllMustBeLast
{
get { return GetString("TemplateRoute_CatchAllMustBeLast"); }
}
/// <summary>
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
/// </summary>
internal static string TemplateRoute_InvalidLiteral
{
get { return GetString("TemplateRoute_InvalidLiteral"); }
}
/// <summary>
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can only occur at the end of the parameter.
/// </summary>
internal static string TemplateRoute_InvalidParameterName
{
get { return GetString("TemplateRoute_InvalidParameterName"); }
}
/// <summary>
/// The route template cannot start with a '/' or '~' character.
/// </summary>
internal static string TemplateRoute_InvalidRouteTemplate
{
get { return GetString("TemplateRoute_InvalidRouteTemplate"); }
}
/// <summary>
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
/// </summary>
internal static string TemplateRoute_MismatchedParameter
{
get { return GetString("TemplateRoute_MismatchedParameter"); }
}
/// <summary>
/// The route parameter name '{0}' appears more than one time in the route template.
/// </summary>
internal static string TemplateRoute_RepeatedParameter
{
get { return GetString("TemplateRoute_RepeatedParameter"); }
}
/// <summary>
/// The constraint entry '{0}' on the route with route template '{1}' must have a string value or be of a type which implements '{2}'.
/// </summary>
internal static string TemplateRoute_ValidationMustBeStringOrCustomConstraint
{
get { return GetString("TemplateRoute_ValidationMustBeStringOrCustomConstraint"); }
}
private static string GetString(string name, params string[] argumentNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
for (var i = 0; i < argumentNames.Length; i++)
{
value = value.Replace("{" + argumentNames[i] + "}", "{" + i + "}");
}
return value;
}
}
}

View File

@ -1,148 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34003
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.AspNet.Routing {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
#if NET45
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Routing.Resources", typeof(Resources).Assembly);
#else
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Routing.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
#endif
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter..
/// </summary>
internal static string TemplateRoute_CannotHaveCatchAllInMultiSegment {
get {
return ResourceManager.GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A path segment cannot contain two consecutive parameters. They must be separated by a &apos;/&apos; or by a literal string..
/// </summary>
internal static string TemplateRoute_CannotHaveConsecutiveParameters {
get {
return ResourceManager.GetString("TemplateRoute_CannotHaveConsecutiveParameters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The route template separator character &apos;/&apos; cannot appear consecutively. It must be separated by either a parameter or a literal value..
/// </summary>
internal static string TemplateRoute_CannotHaveConsecutiveSeparators {
get {
return ResourceManager.GetString("TemplateRoute_CannotHaveConsecutiveSeparators", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A catch-all parameter can only appear as the last segment of the route template..
/// </summary>
internal static string TemplateRoute_CatchAllMustBeLast {
get {
return ResourceManager.GetString("TemplateRoute_CatchAllMustBeLast", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The route parameter name &apos;{0}&apos; is invalid. Route parameter names must be non-empty and cannot contain these characters: &quot;{{&quot;, &quot;}}&quot;, &quot;/&quot;, &quot;?&quot;.
/// </summary>
internal static string TemplateRoute_InvalidParameterName {
get {
return ResourceManager.GetString("TemplateRoute_InvalidParameterName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The route template cannot start with a &apos;/&apos; or &apos;~&apos; character and it cannot contain a &apos;?&apos; character..
/// </summary>
internal static string TemplateRoute_InvalidRouteTemplate {
get {
return ResourceManager.GetString("TemplateRoute_InvalidRouteTemplate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There is an incomplete parameter in the route template. Check that each &apos;{&apos; character has a matching &apos;}&apos; character..
/// </summary>
internal static string TemplateRoute_MismatchedParameter {
get {
return ResourceManager.GetString("TemplateRoute_MismatchedParameter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The route parameter name &apos;{0}&apos; appears more than one time in the route template..
/// </summary>
internal static string TemplateRoute_RepeatedParameter {
get {
return ResourceManager.GetString("TemplateRoute_RepeatedParameter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The constraint entry &apos;{0}&apos; on the route with route template &apos;{1}&apos; must have a string value or be of a type which implements &apos;{2}&apos;..
/// </summary>
internal static string TemplateRoute_ValidationMustBeStringOrCustomConstraint {
get {
return ResourceManager.GetString("TemplateRoute_ValidationMustBeStringOrCustomConstraint", resourceCulture);
}
}
}
}

View File

@ -126,14 +126,20 @@
<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>
<data name="TemplateRoute_CatchAllMustBeLast" xml:space="preserve">
<value>A catch-all parameter can only appear as the last segment of the route template.</value>
</data>
<data name="TemplateRoute_InvalidLiteral" xml:space="preserve">
<value>The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.</value>
</data>
<data name="TemplateRoute_InvalidParameterName" xml:space="preserve">
<value>The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: "{{", "}}", "/", "?"</value>
<value>The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can only occur at the end of the parameter.</value>
</data>
<data name="TemplateRoute_InvalidRouteTemplate" xml:space="preserve">
<value>The route template cannot start with a '/' or '~' character and it cannot contain a '?' character.</value>
<value>The route template cannot start with a '/' or '~' character.</value>
</data>
<data name="TemplateRoute_MismatchedParameter" xml:space="preserve">
<value>There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.</value>

View File

@ -144,6 +144,10 @@ namespace Microsoft.AspNet.Routing.Template
{
values.Add(part.Name, defaultValue);
}
else if (part.IsOptional)
{
// This is optional (with no default value) - there's nothing to capture here, so just move on.
}
else
{
// There's no default for this (non-catch-all) parameter so it can't match.

View File

@ -14,6 +14,8 @@ namespace Microsoft.AspNet.Routing.Template
private const char Separator = '/';
private const char OpenBrace = '{';
private const char CloseBrace = '}';
private const char EqualsSign = '=';
private const char QuestionMark = '?';
public static ParsedTemplate Parse(string routeTemplate)
{
@ -174,10 +176,15 @@ namespace Microsoft.AspNet.Routing.Template
var rawName = context.Capture();
var isCatchAll = rawName.StartsWith("*", StringComparison.Ordinal);
var parameterName = isCatchAll ? rawName.Substring(1) : rawName;
var isOptional = rawName.EndsWith("?", StringComparison.Ordinal);
rawName = isCatchAll ? rawName.Substring(1) : rawName;
rawName = isOptional ? rawName.Substring(0, rawName.Length - 1) : rawName;
var parameterName = rawName;
if (IsValidParameterName(context, parameterName))
{
segment.Parts.Add(TemplatePart.CreateParameter(parameterName, isCatchAll));
segment.Parts.Add(TemplatePart.CreateParameter(parameterName, isCatchAll, isOptional));
return true;
}
else
@ -250,8 +257,15 @@ namespace Microsoft.AspNet.Routing.Template
}
var decoded = encoded.Replace("}}", "}").Replace("{{", "}");
segment.Parts.Add(TemplatePart.CreateLiteral(decoded));
return true;
if (IsValidLiteral(context, decoded))
{
segment.Parts.Add(TemplatePart.CreateLiteral(decoded));
return true;
}
else
{
return false;
}
}
private static bool IsAllValid(TemplateParserContext context, List<TemplateSegment> segments)
@ -287,6 +301,17 @@ namespace Microsoft.AspNet.Routing.Template
}
}
// if a segment has multiple parts, then the parameters can't be optional
for (int 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;
}
}
// A segment cannot containt two consecutive parameters
var isLastSegmentParameter = false;
for (int i = 0; i < segment.Parts.Count; i++)
@ -315,7 +340,7 @@ namespace Microsoft.AspNet.Routing.Template
for (int i = 0; i < parameterName.Length; i++)
{
var c = parameterName[i];
if (c == '/' || c == '{' || c == '}')
if (c == Separator || c == OpenBrace || c == CloseBrace || c == QuestionMark)
{
context.Error = String.Format(CultureInfo.CurrentCulture, Resources.TemplateRoute_InvalidParameterName, parameterName);
return false;
@ -331,11 +356,24 @@ namespace Microsoft.AspNet.Routing.Template
return true;
}
private static bool IsValidLiteral(TemplateParserContext context, string literal)
{
Contract.Assert(context != null);
Contract.Assert(literal != null);
if (literal.IndexOf(QuestionMark) != -1)
{
context.Error = String.Format(CultureInfo.CurrentCulture, Resources.TemplateRoute_InvalidLiteral, literal);
return false;
}
return true;
}
private static bool IsInvalidRouteTemplate(string routeTemplate)
{
return routeTemplate.StartsWith("~", StringComparison.Ordinal) ||
routeTemplate.StartsWith("/", StringComparison.Ordinal) ||
(routeTemplate.IndexOf('?') != -1);
routeTemplate.StartsWith("/", StringComparison.Ordinal);
}

View File

@ -16,20 +16,21 @@ namespace Microsoft.AspNet.Routing.Template
};
}
public static TemplatePart CreateParameter(string name, bool isCatchAll)
public static TemplatePart CreateParameter(string name, bool isCatchAll, bool isOptional)
{
return new TemplatePart()
{
IsParameter = true,
Name = name,
IsCatchAll = isCatchAll,
IsOptional = isOptional,
};
}
public bool IsCatchAll { get; private set; }
public bool IsLiteral { get; private set; }
public bool IsParameter { get; private set; }
public bool IsOptional { get; private set; }
public string Name { get; private set; }
public string Text { get; private set; }
@ -37,7 +38,7 @@ namespace Microsoft.AspNet.Routing.Template
{
if (IsParameter)
{
return "{" + (IsCatchAll ? "*" : string.Empty) + Name + "}";
return "{" + (IsCatchAll ? "*" : string.Empty) + Name + (IsOptional ? "?" : string.Empty) + "}";
}
else
{

View File

@ -35,7 +35,24 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new ParsedTemplate(new List<TemplateSegment>());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, false));
// Act
var actual = TemplateParser.Parse(template);
// Assert
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
}
[Fact]
public void Parse_OptionalParameter()
{
// Arrange
var template = "{p?}";
var expected = new ParsedTemplate(new List<TemplateSegment>());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, true));
// Act
var actual = TemplateParser.Parse(template);
@ -73,11 +90,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new ParsedTemplate(new List<TemplateSegment>());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
expected.Segments.Add(new TemplateSegment());
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2", false));
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2", false, false));
expected.Segments.Add(new TemplateSegment());
expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3", true));
expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3", true, false));
// Act
var actual = TemplateParser.Parse(template);
@ -95,7 +112,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new ParsedTemplate(new List<TemplateSegment>());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
// Act
var actual = TemplateParser.Parse(template);
@ -112,7 +129,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new ParsedTemplate(new List<TemplateSegment>());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
// Act
@ -130,9 +147,9 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new ParsedTemplate(new List<TemplateSegment>());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2", false));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2", false, false));
// Act
var actual = TemplateParser.Parse(template);
@ -150,7 +167,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new ParsedTemplate(new List<TemplateSegment>());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false));
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
// Act
@ -215,7 +232,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("foo/{*}"),
@"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot contain these characters: ""{"", ""}"", ""/"", ""?""" + Environment.NewLine +
"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{', '}', '/'. " +
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@ -269,7 +287,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("{a}/{a{aa}/{z}"),
@"The route parameter name 'a{aa' is invalid. Route parameter names must be non-empty and cannot contain these characters: ""{"", ""}"", ""/"", ""?""" + Environment.NewLine +
"The route parameter name 'a{aa' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{', '}', '/'. " +
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@ -278,7 +297,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("{a}/{}/{z}"),
@"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot contain these characters: ""{"", ""}"", ""/"", ""?""" + Environment.NewLine +
"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{', '}', '/'. " +
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@ -287,7 +307,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("{Controller}.mvc/{?}"),
"The route template cannot start with a '/' or '~' character and it cannot contain a '?' character." + Environment.NewLine +
"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{', '}', '/'. " +
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@ -323,7 +344,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("/foo"),
"The route template cannot start with a '/' or '~' character and it cannot contain a '?' character." + Environment.NewLine +
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@ -332,7 +353,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("~foo"),
"The route template cannot start with a '/' or '~' character and it cannot contain a '?' character." + Environment.NewLine +
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@ -341,10 +362,29 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("foor?bar"),
"The route template cannot start with a '/' or '~' character and it cannot contain a '?' character." + Environment.NewLine +
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
[Fact]
public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
{
Assert.Throws<ArgumentException>(
() => TemplateParser.Parse("{foor?b}"),
"The route parameter name 'foor?b' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{', '}', '/'. " +
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
"Parameter name: routeTemplate");
}
[Fact]
public void InvalidTemplate_MultiSegmentParameterCannotContainOptionalParameter()
{
Assert.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 +
"Parameter name: routeTemplate");
}
private class TemplateParsedRouteEqualityComparer : IEqualityComparer<ParsedTemplate>
{
public bool Equals(ParsedTemplate x, ParsedTemplate y)
@ -379,6 +419,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
if (xPart.IsLiteral != yPart.IsLiteral ||
xPart.IsParameter != yPart.IsParameter ||
xPart.IsCatchAll != yPart.IsCatchAll ||
xPart.IsOptional != yPart.IsOptional ||
!String.Equals(xPart.Name, yPart.Name, StringComparison.Ordinal) ||
!String.Equals(xPart.Name, yPart.Name, StringComparison.Ordinal))
{

View File

@ -710,6 +710,52 @@ namespace Microsoft.AspNet.Routing.Template.Tests
new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } });
}
[Fact]
public void MatchSetsOptionalParameter()
{
// Arrange
var route = CreateRoute("{controller}/{action?}");
var url = "Home/Index";
// Act
var match = route.Match(new RouteContext(GetHttpContext(url)));
// Assert
Assert.NotNull(match);
Assert.Equal("Index", match.Values["action"]);
}
[Fact]
public void MatchDoesNotSetOptionalParameter()
{
// Arrange
var route = CreateRoute("{controller}/{action?}");
var url = "Home";
// Act
var match = route.Match(new RouteContext(GetHttpContext(url)));
// Assert
Assert.NotNull(match);
Assert.False(match.Values.ContainsKey("action"));
}
[Fact]
public void MatchMultipleOptionalParameters()
{
// Arrange
var route = CreateRoute("{controller}/{action?}/{id?}");
var url = "Home/Index";
// Act
var match = route.Match(new RouteContext(GetHttpContext(url)));
// Assert
Assert.NotNull(match);
Assert.Equal("Index", match.Values["action"]);
Assert.False(match.Values.ContainsKey("id"));
}
private static IRouteValues CreateRouteData()
{
return new RouteValues(new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase));