diff --git a/src/Microsoft.AspNet.Routing/Constraints/CompositeRouteConstraint.cs b/src/Microsoft.AspNet.Routing/Constraints/CompositeRouteConstraint.cs
new file mode 100644
index 0000000000..ff9ace0bb6
--- /dev/null
+++ b/src/Microsoft.AspNet.Routing/Constraints/CompositeRouteConstraint.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Routing.Constraints
+{
+ ///
+ /// Constrains a route by several child constraints.
+ ///
+ public class CompositeRouteConstraint : IRouteConstraint
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The child constraints that must match for this constraint to match.
+ public CompositeRouteConstraint([NotNull] IEnumerable constraints)
+ {
+ Constraints = constraints;
+ }
+
+ ///
+ /// Gets the child constraints that must match for this constraint to match.
+ ///
+ public IEnumerable Constraints { get; private set; }
+
+ ///
+ /// Calls Match on the child constraints.
+ /// The call returns as soon as one of the child constraints does not match.
+ ///
+ /// The HTTP context associated with the current call.
+ /// The route that is being constrained.
+ /// The route key used for the constraint.
+ /// The route value dictionary.
+ /// The direction of the routing,
+ /// i.e. incoming request or URL generation.
+ /// True if all the constraints Match,
+ /// false as soon as one of the child constraints does not match.
+ ///
+ /// There is no guarantee for the order in which child constraints are invoked,
+ /// also the method returns as soon as one of the constraints does not match.
+ ///
+ public bool Match([NotNull] HttpContext httpContext,
+ [NotNull] IRouter route,
+ [NotNull] string routeKey,
+ [NotNull] IDictionary values,
+ RouteDirection routeDirection)
+ {
+ foreach (var constraint in Constraints)
+ {
+ if (!constraint.Match(httpContext, route, routeKey, values, routeDirection))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Routing/Constraints/IntRouteConstraint.cs b/src/Microsoft.AspNet.Routing/Constraints/IntRouteConstraint.cs
new file mode 100644
index 0000000000..e0cd79fedb
--- /dev/null
+++ b/src/Microsoft.AspNet.Routing/Constraints/IntRouteConstraint.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Routing.Constraints
+{
+ ///
+ /// Constrains a route parameter to represent only 32-bit integer values.
+ ///
+ public class IntRouteConstraint : IRouteConstraint
+ {
+ ///
+ public bool Match([NotNull] HttpContext httpContext,
+ [NotNull] IRouter route,
+ [NotNull] string routeKey,
+ [NotNull] IDictionary values,
+ RouteDirection routeDirection)
+ {
+ object value;
+ if (values.TryGetValue(routeKey, out value) && value != null)
+ {
+ if (value is int)
+ {
+ return true;
+ }
+
+ int result;
+ string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return Int32.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Routing/RegexConstraint.cs b/src/Microsoft.AspNet.Routing/Constraints/RegexConstraint.cs
similarity index 96%
rename from src/Microsoft.AspNet.Routing/RegexConstraint.cs
rename to src/Microsoft.AspNet.Routing/Constraints/RegexConstraint.cs
index 4d4c128eff..a49c66524b 100644
--- a/src/Microsoft.AspNet.Routing/RegexConstraint.cs
+++ b/src/Microsoft.AspNet.Routing/Constraints/RegexConstraint.cs
@@ -7,7 +7,7 @@ using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.AspNet.Http;
-namespace Microsoft.AspNet.Routing
+namespace Microsoft.AspNet.Routing.Constraints
{
public class RegexConstraint : IRouteConstraint
{
diff --git a/src/Microsoft.AspNet.Routing/DefaultInlineConstraintResolver.cs b/src/Microsoft.AspNet.Routing/DefaultInlineConstraintResolver.cs
new file mode 100644
index 0000000000..fc04fff664
--- /dev/null
+++ b/src/Microsoft.AspNet.Routing/DefaultInlineConstraintResolver.cs
@@ -0,0 +1,146 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using Microsoft.AspNet.Routing.Constraints;
+
+namespace Microsoft.AspNet.Routing
+{
+ ///
+ /// The default implementation of . Resolves constraints by parsing
+ /// a constraint key and constraint arguments, using a map to resolve the constraint type, and calling an
+ /// appropriate constructor for the constraint type.
+ ///
+ public class DefaultInlineConstraintResolver : IInlineConstraintResolver
+ {
+ private readonly IDictionary _inlineConstraintMap = GetDefaultConstraintMap();
+
+ ///
+ /// Gets the mutable dictionary that maps constraint keys to a particular constraint type.
+ ///
+ public IDictionary ConstraintMap
+ {
+ get
+ {
+ return _inlineConstraintMap;
+ }
+ }
+
+ private static IDictionary GetDefaultConstraintMap()
+ {
+ return new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ // Type-specific constraints
+ { "int", typeof(IntRouteConstraint) },
+ };
+ }
+
+ ///
+ ///
+ /// A typical constraint looks like the following
+ /// "exampleConstraint(arg1, arg2, 12)".
+ /// Here if the type registered for exampleConstraint has a single constructor with one argument,
+ /// The entire string "arg1, arg2, 12" will be treated as a single argument.
+ /// In all other cases arguments are split at comma.
+ ///
+ public virtual IRouteConstraint ResolveConstraint([NotNull] string inlineConstraint)
+ {
+ string constraintKey;
+ string argumentString;
+ int indexOfFirstOpenParens = inlineConstraint.IndexOf('(');
+ if (indexOfFirstOpenParens >= 0 && inlineConstraint.EndsWith(")", StringComparison.Ordinal))
+ {
+ constraintKey = inlineConstraint.Substring(0, indexOfFirstOpenParens);
+ argumentString = inlineConstraint.Substring(indexOfFirstOpenParens + 1,
+ inlineConstraint.Length - indexOfFirstOpenParens - 2);
+ }
+ else
+ {
+ constraintKey = inlineConstraint;
+ argumentString = null;
+ }
+
+ Type constraintType;
+ if (!_inlineConstraintMap.TryGetValue(constraintKey, out constraintType))
+ {
+ // Cannot resolve the constraint key
+ return null;
+ }
+
+ if (!typeof(IRouteConstraint).GetTypeInfo().IsAssignableFrom(constraintType.GetTypeInfo()))
+ {
+ throw new InvalidOperationException(
+ Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
+ constraintType, constraintKey, typeof(IRouteConstraint).Name));
+ }
+
+ return CreateConstraint(constraintType, argumentString);
+ }
+
+ private static IRouteConstraint CreateConstraint(Type constraintType, string argumentString)
+ {
+ // No arguments - call the default constructor
+ if (argumentString == null)
+ {
+ return (IRouteConstraint)Activator.CreateInstance(constraintType);
+ }
+
+ var constraintTypeInfo = constraintType.GetTypeInfo();
+ ConstructorInfo activationConstructor = null;
+ object[] parameters = null;
+ var constructors = constraintTypeInfo.DeclaredConstructors.ToArray();
+
+ // If there is only one constructor and it has a single parameter, pass the argument string directly
+ // This is necessary for the Regex RouteConstraint to ensure that patterns are not split on commas.
+ if (constructors.Length == 1 && constructors[0].GetParameters().Length == 1)
+ {
+ activationConstructor = constructors[0];
+ parameters = ConvertArguments(activationConstructor.GetParameters(), new string[] { argumentString });
+ }
+ else
+ {
+ string[] arguments = argumentString.Split(',').Select(argument => argument.Trim()).ToArray();
+
+ var matchingConstructors = constructors.Where(ci => ci.GetParameters().Length == arguments.Length).ToArray();
+ var constructorMatches = matchingConstructors.Length;
+
+ if (constructorMatches == 0)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor(
+ constraintTypeInfo.Name, argumentString.Length));
+ }
+ else if (constructorMatches == 1)
+ {
+ activationConstructor = matchingConstructors[0];
+ parameters = ConvertArguments(activationConstructor.GetParameters(), arguments);
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors(
+ constraintTypeInfo.Name, argumentString.Length));
+ }
+ }
+
+ return (IRouteConstraint)activationConstructor.Invoke(parameters);
+ }
+
+ private static object[] ConvertArguments(ParameterInfo[] parameterInfos, string[] arguments)
+ {
+ var parameters = new object[parameterInfos.Length];
+ for (int i = 0; i < parameterInfos.Length; i++)
+ {
+ var parameter = parameterInfos[i];
+ var parameterType = parameter.ParameterType;
+ parameters[i] = Convert.ChangeType(arguments[i], parameterType, CultureInfo.InvariantCulture);
+ }
+
+ return parameters;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Routing/IInlineConstraintResolver.cs b/src/Microsoft.AspNet.Routing/IInlineConstraintResolver.cs
new file mode 100644
index 0000000000..f2011e1db3
--- /dev/null
+++ b/src/Microsoft.AspNet.Routing/IInlineConstraintResolver.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Routing
+{
+ ///
+ /// Defines an abstraction for resolving inline constraints as instances of .
+ ///
+ public interface IInlineConstraintResolver
+ {
+ ///
+ /// Resolves the inline constraint.
+ ///
+ /// The inline constraint to resolve.
+ /// The the inline constraint was resolved to.
+ IRouteConstraint ResolveConstraint([NotNull] string inlineConstraint);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Routing/IRouteCollection.cs b/src/Microsoft.AspNet.Routing/IRouteCollection.cs
index 072e8341b6..18b5820d94 100644
--- a/src/Microsoft.AspNet.Routing/IRouteCollection.cs
+++ b/src/Microsoft.AspNet.Routing/IRouteCollection.cs
@@ -7,6 +7,8 @@ namespace Microsoft.AspNet.Routing
{
IRouter DefaultHandler { get; set; }
+ IInlineConstraintResolver InlineConstraintResolver { get; set; }
+
void Add(IRouter router);
}
}
diff --git a/src/Microsoft.AspNet.Routing/InlineRouteParameterParser.cs b/src/Microsoft.AspNet.Routing/InlineRouteParameterParser.cs
new file mode 100644
index 0000000000..5209b40e87
--- /dev/null
+++ b/src/Microsoft.AspNet.Routing/InlineRouteParameterParser.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Text.RegularExpressions;
+using Microsoft.AspNet.Routing.Template;
+using Microsoft.AspNet.Routing.Constraints;
+
+namespace Microsoft.AspNet.Routing
+{
+ public static class InlineRouteParameterParser
+ {
+ // One or more characters, matches "id"
+ private const string ParameterNamePattern = @"(?.+?)";
+
+ // Zero or more inline constraints that start with a colon followed by zero or more characters
+ // Optionally the constraint can have arguments within parentheses
+ // - necessary to capture characters like ":" and "}"
+ // Matches ":int", ":length(2)", ":regex(\})", ":regex(:)" zero or more times
+ private const string ConstraintPattern = @"(:(?.*?(\(.*?\))?))*";
+
+ // A default value with an equal sign followed by zero or more characters
+ // Matches "=", "=abc"
+ private const string DefaultValueParameter = @"(?(=.*?))?";
+
+ private static readonly Regex _parameterRegex = new Regex(
+ "^" + ParameterNamePattern + ConstraintPattern + DefaultValueParameter + "$",
+ RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
+ public static TemplatePart ParseRouteParameter([NotNull] string routeParameter,
+ [NotNull] IInlineConstraintResolver constraintResolver)
+ {
+ var isCatchAll = routeParameter.StartsWith("*", StringComparison.Ordinal);
+ var isOptional = routeParameter.EndsWith("?", StringComparison.Ordinal);
+
+ routeParameter = isCatchAll ? routeParameter.Substring(1) : routeParameter;
+ routeParameter = isOptional ? routeParameter.Substring(0, routeParameter.Length - 1) : routeParameter;
+
+ var parameterMatch = _parameterRegex.Match(routeParameter);
+ if (!parameterMatch.Success)
+ {
+ return TemplatePart.CreateParameter(name: string.Empty,
+ isCatchAll: isCatchAll,
+ isOptional: isOptional,
+ defaultValue: null,
+ inlineConstraint: null
+ );
+ }
+
+ var parameterName = parameterMatch.Groups["parameterName"].Value;
+
+ // Add the default value if present
+ var defaultValueGroup = parameterMatch.Groups["defaultValue"];
+ var defaultValue = GetDefaultValue(defaultValueGroup);
+
+ // Register inline constraints if present
+ var constraintGroup = parameterMatch.Groups["constraint"];
+ var inlineConstraint = GetInlineConstraint(constraintGroup, constraintResolver);
+
+ return TemplatePart.CreateParameter(parameterName,
+ isCatchAll,
+ isOptional,
+ defaultValue,
+ inlineConstraint);
+ }
+
+ private static string GetDefaultValue(Group defaultValueGroup)
+ {
+ if (defaultValueGroup.Success)
+ {
+ var defaultValueMatch = defaultValueGroup.Value;
+
+ // Strip out the equal sign at the beginning
+ Contract.Assert(defaultValueMatch.StartsWith("=", StringComparison.Ordinal));
+ return defaultValueMatch.Substring(1);
+ }
+
+ return null;
+ }
+
+ private static IRouteConstraint GetInlineConstraint(Group constraintGroup,
+ IInlineConstraintResolver constraintResolver)
+ {
+ var parameterConstraints = new List();
+ foreach (Capture constraintCapture in constraintGroup.Captures)
+ {
+ var inlineConstraint = constraintCapture.Value;
+ var constraint = constraintResolver.ResolveConstraint(inlineConstraint);
+ if (constraint == null)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatInlineRouteParser_CouldNotResolveConstraint(
+ constraintResolver.GetType().Name, inlineConstraint));
+ }
+
+ parameterConstraints.Add(constraint);
+ }
+
+ if (parameterConstraints.Count > 0)
+ {
+ var constraint = parameterConstraints.Count == 1 ?
+ parameterConstraints[0] :
+ new CompositeRouteConstraint(parameterConstraints);
+ return constraint;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj b/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj
index 466f9091ba..b7a6c5c243 100644
--- a/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj
+++ b/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj
@@ -22,7 +22,12 @@
+
+
+
+
+
diff --git a/src/Microsoft.AspNet.Routing/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Routing/Properties/Resources.Designer.cs
index cfc9ad76fe..b226fdddb2 100644
--- a/src/Microsoft.AspNet.Routing/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Routing/Properties/Resources.Designer.cs
@@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Routing
= new ResourceManager("Microsoft.AspNet.Routing.Resources", typeof(Resources).GetTypeInfo().Assembly);
///
- /// The supplied route name '{0}' is ambiguous and matched more than one routes.
+ /// The supplied route name '{0}' is ambiguous and matched more than one route.
///
internal static string NamedRoutes_AmbiguousRoutesFound
{
@@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Routing
}
///
- /// The supplied route name '{0}' is ambiguous and matched more than one routes.
+ /// The supplied route name '{0}' is ambiguous and matched more than one route.
///
internal static string FormatNamedRoutes_AmbiguousRoutesFound(object p0)
{
@@ -42,6 +42,54 @@ namespace Microsoft.AspNet.Routing
return GetString("DefaultHandler_MustBeSet");
}
+ ///
+ /// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
+ ///
+ internal static string DefaultInlineConstraintResolver_AmbiguousCtors
+ {
+ get { return GetString("DefaultInlineConstraintResolver_AmbiguousCtors"); }
+ }
+
+ ///
+ /// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
+ ///
+ internal static string FormatDefaultInlineConstraintResolver_AmbiguousCtors(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_AmbiguousCtors"), p0, p1);
+ }
+
+ ///
+ /// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
+ ///
+ internal static string DefaultInlineConstraintResolver_CouldNotFindCtor
+ {
+ get { return GetString("DefaultInlineConstraintResolver_CouldNotFindCtor"); }
+ }
+
+ ///
+ /// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
+ ///
+ internal static string FormatDefaultInlineConstraintResolver_CouldNotFindCtor(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_CouldNotFindCtor"), p0, p1);
+ }
+
+ ///
+ /// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
+ ///
+ internal static string DefaultInlineConstraintResolver_TypeNotConstraint
+ {
+ get { return GetString("DefaultInlineConstraintResolver_TypeNotConstraint"); }
+ }
+
+ ///
+ /// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
+ ///
+ internal static string FormatDefaultInlineConstraintResolver_TypeNotConstraint(object p0, object p1, object p2)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_TypeNotConstraint"), p0, p1, p2);
+ }
+
///
/// The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.
///
@@ -74,6 +122,22 @@ namespace Microsoft.AspNet.Routing
return GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
}
+ ///
+ /// The route parameter '{0}' has both an inline deafult value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
+ ///
+ internal static string TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly
+ {
+ get { return GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly"); }
+ }
+
+ ///
+ /// The route parameter '{0}' has both an inline deafult value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
+ ///
+ internal static string FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly"), p0);
+ }
+
///
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
///
@@ -138,6 +202,22 @@ namespace Microsoft.AspNet.Routing
return GetString("TemplateRoute_CatchAllCannotBeOptional");
}
+ ///
+ /// An optional parameter cannot have default value.
+ ///
+ internal static string TemplateRoute_OptionalCannotHaveDefaultValue
+ {
+ get { return GetString("TemplateRoute_OptionalCannotHaveDefaultValue"); }
+ }
+
+ ///
+ /// An optional parameter cannot have default value.
+ ///
+ internal static string FormatTemplateRoute_OptionalCannotHaveDefaultValue()
+ {
+ return GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
+ }
+
///
/// A catch-all parameter can only appear as the last segment of the route template.
///
@@ -250,12 +330,28 @@ namespace Microsoft.AspNet.Routing
return string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_ValidationMustBeStringOrCustomConstraint"), p0, p1, p2);
}
+ ///
+ /// The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.
+ ///
+ internal static string InlineRouteParser_CouldNotResolveConstraint
+ {
+ get { return GetString("InlineRouteParser_CouldNotResolveConstraint"); }
+ }
+
+ ///
+ /// The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.
+ ///
+ internal static string FormatInlineRouteParser_CouldNotResolveConstraint(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("InlineRouteParser_CouldNotResolveConstraint"), p0, p1);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
-
+
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
diff --git a/src/Microsoft.AspNet.Routing/Resources.resx b/src/Microsoft.AspNet.Routing/Resources.resx
index 0f73bb2cd9..ba89de6413 100644
--- a/src/Microsoft.AspNet.Routing/Resources.resx
+++ b/src/Microsoft.AspNet.Routing/Resources.resx
@@ -123,12 +123,24 @@
A default handler must be set on the RouteCollection.
+
+ The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
+
+
+ Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
+
+
+ The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
+
The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.
A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
+
+ The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
+
A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
@@ -141,6 +153,9 @@
A catch-all parameter cannot be marked optional.
+
+ An optional parameter cannot have default value.
+
A catch-all parameter can only appear as the last segment of the route template.
@@ -162,4 +177,7 @@
The constraint entry '{0}' on the route with route template '{1}' must have a string value or be of a type which implements '{2}'.
+
+ The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Routing/RouteCollection.cs b/src/Microsoft.AspNet.Routing/RouteCollection.cs
index 16eef2c4b6..b14183046a 100644
--- a/src/Microsoft.AspNet.Routing/RouteCollection.cs
+++ b/src/Microsoft.AspNet.Routing/RouteCollection.cs
@@ -26,6 +26,8 @@ namespace Microsoft.AspNet.Routing
public IRouter DefaultHandler { get; set; }
+ public IInlineConstraintResolver InlineConstraintResolver { get; set; }
+
public void Add([NotNull] IRouter router)
{
var namedRouter = router as INamedRouter;
diff --git a/src/Microsoft.AspNet.Routing/RouteCollectionExtensions.cs b/src/Microsoft.AspNet.Routing/RouteCollectionExtensions.cs
index 412e7e9236..5b17ea3e44 100644
--- a/src/Microsoft.AspNet.Routing/RouteCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Routing/RouteCollectionExtensions.cs
@@ -30,14 +30,23 @@ namespace Microsoft.AspNet.Routing
throw new InvalidOperationException(Resources.DefaultHandler_MustBeSet);
}
- routes.Add(new TemplateRoute(routes.DefaultHandler, name, template, defaults, constraints: null));
+ routes.Add(new TemplateRoute(routes.DefaultHandler,
+ name,
+ template,
+ defaults,
+ constraints: null,
+ inlineConstraintResolver: routes.InlineConstraintResolver));
return routes;
}
public static IRouteCollection MapRoute(this IRouteCollection routes, string name, string template,
object defaults, object constraints)
{
- MapRoute(routes, name, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints));
+ MapRoute(routes,
+ name,
+ template,
+ new RouteValueDictionary(defaults),
+ new RouteValueDictionary(constraints));
return routes;
}
@@ -55,15 +64,23 @@ namespace Microsoft.AspNet.Routing
return routes;
}
- public static IRouteCollection MapRoute(this IRouteCollection routes, string name, string template,
- IDictionary defaults, IDictionary constraints)
+ public static IRouteCollection MapRoute(this IRouteCollection routes,
+ string name,
+ string template,
+ IDictionary defaults,
+ IDictionary constraints)
{
if (routes.DefaultHandler == null)
{
throw new InvalidOperationException(Resources.DefaultHandler_MustBeSet);
}
-
- routes.Add(new TemplateRoute(routes.DefaultHandler, name, template, defaults, constraints));
+
+ routes.Add(new TemplateRoute(routes.DefaultHandler,
+ name,
+ template,
+ defaults,
+ constraints,
+ routes.InlineConstraintResolver));
return routes;
}
}
diff --git a/src/Microsoft.AspNet.Routing/RouteConstraintBuilder.cs b/src/Microsoft.AspNet.Routing/RouteConstraintBuilder.cs
index 607985627f..92948f9ab6 100644
--- a/src/Microsoft.AspNet.Routing/RouteConstraintBuilder.cs
+++ b/src/Microsoft.AspNet.Routing/RouteConstraintBuilder.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNet.Routing.Constraints;
namespace Microsoft.AspNet.Routing
{
diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs b/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs
index 288f8a5507..be6dc62012 100644
--- a/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs
+++ b/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs
@@ -3,10 +3,8 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
-using System.Linq;
namespace Microsoft.AspNet.Routing.Template
{
@@ -18,7 +16,7 @@ namespace Microsoft.AspNet.Routing.Template
private const char EqualsSign = '=';
private const char QuestionMark = '?';
- public static Template Parse(string routeTemplate)
+ public static Template Parse(string routeTemplate, IInlineConstraintResolver constraintResolver)
{
if (routeTemplate == null)
{
@@ -30,7 +28,7 @@ namespace Microsoft.AspNet.Routing.Template
throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, "routeTemplate");
}
- var context = new TemplateParserContext(routeTemplate);
+ var context = new TemplateParserContext(routeTemplate, constraintResolver);
var segments = new List();
while (context.Next())
@@ -174,24 +172,35 @@ namespace Microsoft.AspNet.Routing.Template
}
}
- var rawName = context.Capture();
+ var rawParameter = context.Capture();
- var isCatchAll = rawName.StartsWith("*", StringComparison.Ordinal);
- var isOptional = rawName.EndsWith("?", StringComparison.Ordinal);
+ // At this point, we need to parse the raw name for inline constraint,
+ // default values and optional parameters.
+ var templatePart = InlineRouteParameterParser.ParseRouteParameter(rawParameter,
+ context.ConstraintResolver);
- if (isCatchAll && isOptional)
+
+
+ if (templatePart.IsCatchAll && templatePart.IsOptional)
{
context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
return false;
}
- rawName = isCatchAll ? rawName.Substring(1) : rawName;
- rawName = isOptional ? rawName.Substring(0, rawName.Length - 1) : rawName;
+ if (templatePart.IsOptional && templatePart.DefaultValue != null)
+ {
+ // Cannot be optional and have a default value.
+ // The only way to declare an optional parameter is to have a ? at the end,
+ // hence we cannot have both default value and optional parameter within the template.
+ // A workaround is to add it as a separate entry in the defaults argument.
+ context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
+ return false;
+ }
- var parameterName = rawName;
+ var parameterName = templatePart.Name;
if (IsValidParameterName(context, parameterName))
{
- segment.Parts.Add(TemplatePart.CreateParameter(parameterName, isCatchAll, isOptional));
+ segment.Parts.Add(templatePart);
return true;
}
else
@@ -392,12 +401,13 @@ namespace Microsoft.AspNet.Routing.Template
private HashSet _parameterNames = new HashSet(StringComparer.OrdinalIgnoreCase);
- public TemplateParserContext(string template)
+ public TemplateParserContext(string template, IInlineConstraintResolver constraintResolver)
{
Contract.Assert(template != null);
_template = template;
_index = -1;
+ ConstraintResolver = constraintResolver;
}
public char Current
@@ -416,6 +426,12 @@ namespace Microsoft.AspNet.Routing.Template
get { return _parameterNames; }
}
+ public IInlineConstraintResolver ConstraintResolver
+ {
+ get;
+ private set;
+ }
+
public bool Back()
{
return --_index >= 0;
diff --git a/src/Microsoft.AspNet.Routing/Template/TemplatePart.cs b/src/Microsoft.AspNet.Routing/Template/TemplatePart.cs
index 3f54995276..1ab442eb60 100644
--- a/src/Microsoft.AspNet.Routing/Template/TemplatePart.cs
+++ b/src/Microsoft.AspNet.Routing/Template/TemplatePart.cs
@@ -17,7 +17,11 @@ namespace Microsoft.AspNet.Routing.Template
};
}
- public static TemplatePart CreateParameter(string name, bool isCatchAll, bool isOptional)
+ public static TemplatePart CreateParameter([NotNull] string name,
+ bool isCatchAll,
+ bool isOptional,
+ object defaultValue,
+ IRouteConstraint inlineConstraint)
{
return new TemplatePart()
{
@@ -25,6 +29,8 @@ namespace Microsoft.AspNet.Routing.Template
Name = name,
IsCatchAll = isCatchAll,
IsOptional = isOptional,
+ DefaultValue = defaultValue,
+ InlineConstraint = inlineConstraint,
};
}
@@ -34,6 +40,8 @@ namespace Microsoft.AspNet.Routing.Template
public bool IsOptional { get; private set; }
public string Name { get; private set; }
public string Text { get; private set; }
+ public object DefaultValue { get; private set; }
+ public IRouteConstraint InlineConstraint { get; private set; }
internal string DebuggerToString()
{
diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs
index 985ce2dabb..30687c2024 100644
--- a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs
+++ b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs
@@ -3,8 +3,8 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.Contracts;
using System.Threading.Tasks;
+using Microsoft.AspNet.Routing.Constraints;
namespace Microsoft.AspNet.Routing.Template
{
@@ -18,16 +18,17 @@ namespace Microsoft.AspNet.Routing.Template
private readonly TemplateMatcher _matcher;
private readonly TemplateBinder _binder;
- public TemplateRoute(IRouter target, string routeTemplate)
- : this(target, routeTemplate, null, null)
+ public TemplateRoute(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver)
+ : this(target, routeTemplate, null, null, inlineConstraintResolver)
{
}
public TemplateRoute([NotNull] IRouter target,
string routeTemplate,
IDictionary defaults,
- IDictionary constraints)
- : this(target, null, routeTemplate, defaults, constraints)
+ IDictionary constraints,
+ IInlineConstraintResolver inlineConstraintResolver)
+ : this(target, null, routeTemplate, defaults, constraints, inlineConstraintResolver)
{
}
@@ -35,17 +36,20 @@ namespace Microsoft.AspNet.Routing.Template
string routeName,
string routeTemplate,
IDictionary defaults,
- IDictionary constraints)
+ IDictionary constraints,
+ IInlineConstraintResolver inlineConstraintResolver)
{
_target = target;
_routeTemplate = routeTemplate ?? string.Empty;
Name = routeName;
_defaults = defaults ?? new Dictionary(StringComparer.OrdinalIgnoreCase);
- _constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate);
+ _constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate) ??
+ new Dictionary();
// The parser will throw for invalid routes.
- _parsedTemplate = TemplateParser.Parse(RouteTemplate);
-
+ _parsedTemplate = TemplateParser.Parse(RouteTemplate, inlineConstraintResolver);
+ UpdateInlineDefaultValuesAndConstraints();
+
_matcher = new TemplateMatcher(_parsedTemplate);
_binder = new TemplateBinder(_parsedTemplate, _defaults);
}
@@ -170,5 +174,39 @@ namespace Microsoft.AspNet.Routing.Template
ProvidedValues = providedValues,
};
}
+
+ private void UpdateInlineDefaultValuesAndConstraints()
+ {
+ foreach (var parameter in _parsedTemplate.Parameters)
+ {
+ if (parameter.InlineConstraint != null)
+ {
+ IRouteConstraint constraint;
+ if (_constraints.TryGetValue(parameter.Name, out constraint))
+ {
+ _constraints[parameter.Name] =
+ new CompositeRouteConstraint(new []{ constraint, parameter.InlineConstraint });
+ }
+ else
+ {
+ _constraints[parameter.Name] = parameter.InlineConstraint;
+ }
+ }
+
+ if (parameter.DefaultValue != null)
+ {
+ if (_defaults.ContainsKey(parameter.Name))
+ {
+ throw new InvalidOperationException(
+ Resources.
+ FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(parameter.Name));
+ }
+ else
+ {
+ _defaults[parameter.Name] = parameter.DefaultValue;
+ }
+ }
+ }
+ }
}
}
diff --git a/test/Microsoft.AspNet.Routing.Tests/ConstraintsBuilderTests.cs b/test/Microsoft.AspNet.Routing.Tests/ConstraintsBuilderTests.cs
index a5053f048b..0f922d2d7d 100644
--- a/test/Microsoft.AspNet.Routing.Tests/ConstraintsBuilderTests.cs
+++ b/test/Microsoft.AspNet.Routing.Tests/ConstraintsBuilderTests.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Testing;
using Moq;
using Xunit;
diff --git a/test/Microsoft.AspNet.Routing.Tests/RegexConstraintTests.cs b/test/Microsoft.AspNet.Routing.Tests/RegexConstraintTests.cs
index e4164536e9..a0e85ba860 100644
--- a/test/Microsoft.AspNet.Routing.Tests/RegexConstraintTests.cs
+++ b/test/Microsoft.AspNet.Routing.Tests/RegexConstraintTests.cs
@@ -7,6 +7,7 @@ using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Routing.Constraints;
using Moq;
using Xunit;
diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs
index e489d2b173..aaacb0a17a 100644
--- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs
+++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs
@@ -127,7 +127,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
string expected)
{
// Arrange
- var binder = new TemplateBinder(TemplateParser.Parse(template), defaults);
+ var binder = new TemplateBinder(TemplateParser.Parse(template, new DefaultInlineConstraintResolver()),
+ defaults);
// Act & Assert
var acceptedValues = binder.GetAcceptedValues(null, values);
@@ -960,7 +961,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
string expected)
{
// Arrange
- var binder = new TemplateBinder(TemplateParser.Parse(template), defaults);
+ var binder = new TemplateBinder(TemplateParser.Parse(template, new DefaultInlineConstraintResolver()), defaults);
// Act & Assert
var acceptedValues = binder.GetAcceptedValues(ambientValues, values);
diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs
index 5b73944912..3a8eb4bb38 100644
--- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs
+++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs
@@ -784,13 +784,13 @@ namespace Microsoft.AspNet.Routing.Template.Tests
private TemplateMatcher CreateMatcher(string template)
{
- return new TemplateMatcher(TemplateParser.Parse(template));
+ return new TemplateMatcher(TemplateParser.Parse(template, new DefaultInlineConstraintResolver()));
}
private static void RunTest(string template, string path, IDictionary defaults, IDictionary expected)
{
// Arrange
- var matcher = new TemplateMatcher(TemplateParser.Parse(template));
+ var matcher = new TemplateMatcher(TemplateParser.Parse(template, new DefaultInlineConstraintResolver()));
// Act
var match = matcher.Match(path, defaults);
diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateParserTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateParserTests.cs
index f540b42aa4..72ada89942 100644
--- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateParserTests.cs
+++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateParserTests.cs
@@ -10,6 +10,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
{
public class TemplateRouteParserTests
{
+ private IInlineConstraintResolver _inlineConstraintResolver = new DefaultInlineConstraintResolver();
+
[Fact]
public void Parse_SingleLiteral()
{
@@ -21,7 +23,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -35,11 +37,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List());
expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, false));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[0]);
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -53,11 +55,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List());
expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, true));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[0]);
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -78,7 +80,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
expected.Segments[2].Parts.Add(TemplatePart.CreateLiteral("super"));
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -93,19 +95,31 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List());
expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[0]);
expected.Segments.Add(new TemplateSegment());
- expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2", false, false));
+ expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[1].Parts[0]);
expected.Segments.Add(new TemplateSegment());
- expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3", true, false));
+ expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3",
+ true,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[2].Parts[0]);
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -120,11 +134,15 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[1]);
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -138,12 +156,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List());
expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[0]);
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -157,14 +179,22 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List());
expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[0]);
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2", false, false));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[2]);
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -179,12 +209,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List());
expected.Segments.Add(new TemplateSegment());
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraint: null));
expected.Parameters.Add(expected.Segments[0].Parts[1]);
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
// Act
- var actual = TemplateParser.Parse(template);
+ var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
// Assert
Assert.Equal(expected, actual, new TemplateEqualityComparer());
@@ -194,7 +228,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_WithRepeatedParameter()
{
var ex = ExceptionAssert.Throws(
- () => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
+ () => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}", _inlineConstraintResolver),
"The route parameter name 'controller' appears more than one time in the route template." + Environment.NewLine + "Parameter name: routeTemplate");
}
@@ -208,7 +242,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_WithMismatchedBraces(string template)
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse(template),
+ () => TemplateParser.Parse(template, _inlineConstraintResolver),
@"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -217,7 +251,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("123{a}abc{*moo}"),
+ () => TemplateParser.Parse("123{a}abc{*moo}", _inlineConstraintResolver),
"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");
}
@@ -226,7 +260,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{*p1}/{*p2}"),
+ () => TemplateParser.Parse("{*p1}/{*p2}", _inlineConstraintResolver),
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -235,7 +269,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{*p1}abc{*p2}"),
+ () => TemplateParser.Parse("{*p1}abc{*p2}", _inlineConstraintResolver),
"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");
}
@@ -244,7 +278,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotHaveCatchAllWithNoName()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("foo/{*}"),
+ () => TemplateParser.Parse("foo/{*}", _inlineConstraintResolver),
"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");
@@ -254,7 +288,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("foo/{{p1}"),
+ () => TemplateParser.Parse("foo/{{p1}", _inlineConstraintResolver),
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -263,7 +297,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("foo/{p1}}"),
+ () => TemplateParser.Parse("foo/{p1}}", _inlineConstraintResolver),
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -272,7 +306,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_SameParameterTwiceThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{aaa}/{AAA}"),
+ () => TemplateParser.Parse("{aaa}/{AAA}", _inlineConstraintResolver),
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -281,7 +315,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{aaa}/{*AAA}"),
+ () => TemplateParser.Parse("{aaa}/{*AAA}", _inlineConstraintResolver),
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -290,7 +324,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{a}/{aa}a}/{z}"),
+ () => TemplateParser.Parse("{a}/{aa}a}/{z}", _inlineConstraintResolver),
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -299,7 +333,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{a}/{a{aa}/{z}"),
+ () => TemplateParser.Parse("{a}/{a{aa}/{z}", _inlineConstraintResolver),
"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");
@@ -309,7 +343,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{a}/{}/{z}"),
+ () => TemplateParser.Parse("{a}/{}/{z}", _inlineConstraintResolver),
"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");
@@ -319,7 +353,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{Controller}.mvc/{?}"),
+ () => TemplateParser.Parse("{Controller}.mvc/{?}", _inlineConstraintResolver),
"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");
@@ -329,7 +363,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{a}//{z}"),
+ () => TemplateParser.Parse("{a}//{z}", _inlineConstraintResolver),
"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");
}
@@ -338,7 +372,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
+ () => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}", _inlineConstraintResolver),
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -347,7 +381,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_RepeatedParametersThrows()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("foo/aa{p1}{p2}"),
+ () => TemplateParser.Parse("foo/aa{p1}{p2}", _inlineConstraintResolver),
"A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -356,7 +390,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotStartWithSlash()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("/foo"),
+ () => TemplateParser.Parse("/foo", _inlineConstraintResolver),
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -365,7 +399,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotStartWithTilde()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("~foo"),
+ () => TemplateParser.Parse("~foo", _inlineConstraintResolver),
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -374,7 +408,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CannotContainQuestionMark()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("foor?bar"),
+ () => TemplateParser.Parse("foor?bar", _inlineConstraintResolver),
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." + Environment.NewLine +
"Parameter name: routeTemplate");
}
@@ -383,7 +417,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{foor?b}"),
+ () => TemplateParser.Parse("{foor?b}", _inlineConstraintResolver),
"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");
@@ -393,7 +427,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_MultiSegmentParameterCannotContainOptionalParameter()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{foorb?}-bar-{z}"),
+ () => TemplateParser.Parse("{foorb?}-bar-{z}", _inlineConstraintResolver),
"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");
}
@@ -402,7 +436,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_CatchAllMarkedOptional()
{
ExceptionAssert.Throws(
- () => TemplateParser.Parse("{a}/{*b?}"),
+ () => TemplateParser.Parse("{a}/{*b?}", _inlineConstraintResolver),
"A catch-all parameter cannot be marked optional." + Environment.NewLine +
"Parameter name: routeTemplate");
}
diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs
index e58959464d..336f714425 100644
--- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs
+++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Testing;
using Moq;
using Xunit;
@@ -463,22 +464,34 @@ namespace Microsoft.AspNet.Routing.Template.Tests
private static TemplateRoute CreateRoute(string template, bool accept = true)
{
- return new TemplateRoute(CreateTarget(accept), template);
+ return new TemplateRoute(CreateTarget(accept), template, new DefaultInlineConstraintResolver());
}
private static TemplateRoute CreateRoute(string template, object defaults, bool accept = true, IDictionary constraints = null)
{
- return new TemplateRoute(CreateTarget(accept), template, new RouteValueDictionary(defaults), constraints);
+ return new TemplateRoute(CreateTarget(accept),
+ template,
+ new RouteValueDictionary(defaults),
+ constraints,
+ new DefaultInlineConstraintResolver());
}
private static TemplateRoute CreateRoute(IRouter target, string template)
{
- return new TemplateRoute(target, template, new RouteValueDictionary(), constraints: null);
+ return new TemplateRoute(target,
+ template,
+ new RouteValueDictionary(),
+ constraints: null,
+ inlineConstraintResolver: new DefaultInlineConstraintResolver());
}
private static TemplateRoute CreateRoute(IRouter target, string template, object defaults)
{
- return new TemplateRoute(target, template, new RouteValueDictionary(defaults), constraints: null);
+ return new TemplateRoute(target,
+ template,
+ new RouteValueDictionary(defaults),
+ constraints: null,
+ inlineConstraintResolver: new DefaultInlineConstraintResolver());
}
private static IRouter CreateTarget(bool accept = true)