Support for InlineConstraints in Route Template

This commit is contained in:
harshgMSFT 2014-05-16 17:52:36 -07:00
parent bde341caed
commit 187d8e4911
22 changed files with 711 additions and 83 deletions

View File

@ -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
{
/// <summary>
/// Constrains a route by several child constraints.
/// </summary>
public class CompositeRouteConstraint : IRouteConstraint
{
/// <summary>
/// Initializes a new instance of the <see cref="CompositeRouteConstraint" /> class.
/// </summary>
/// <param name="constraints">The child constraints that must match for this constraint to match.</param>
public CompositeRouteConstraint([NotNull] IEnumerable<IRouteConstraint> constraints)
{
Constraints = constraints;
}
/// <summary>
/// Gets the child constraints that must match for this constraint to match.
/// </summary>
public IEnumerable<IRouteConstraint> Constraints { get; private set; }
/// <summary>
/// Calls Match on the child constraints.
/// The call returns as soon as one of the child constraints does not match.
/// </summary>
/// <param name="httpContext">The HTTP context associated with the current call.</param>
/// <param name="route">The route that is being constrained.</param>
/// <param name="routeKey">The route key used for the constraint.</param>
/// <param name="values">The route value dictionary.</param>
/// <param name="routeDirection">The direction of the routing,
/// i.e. incoming request or URL generation.</param>
/// <returns>True if all the constraints Match,
/// false as soon as one of the child constraints does not match.</returns>
/// <remarks>
/// 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.
/// </remarks>
public bool Match([NotNull] HttpContext httpContext,
[NotNull] IRouter route,
[NotNull] string routeKey,
[NotNull] IDictionary<string, object> values,
RouteDirection routeDirection)
{
foreach (var constraint in Constraints)
{
if (!constraint.Match(httpContext, route, routeKey, values, routeDirection))
{
return false;
}
}
return true;
}
}
}

View File

@ -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
{
/// <summary>
/// Constrains a route parameter to represent only 32-bit integer values.
/// </summary>
public class IntRouteConstraint : IRouteConstraint
{
/// <inheritdoc />
public bool Match([NotNull] HttpContext httpContext,
[NotNull] IRouter route,
[NotNull] string routeKey,
[NotNull] IDictionary<string, object> 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;
}
}
}

View File

@ -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
{

View File

@ -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
{
/// <summary>
/// The default implementation of <see cref="IInlineConstraintResolver"/>. 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.
/// </summary>
public class DefaultInlineConstraintResolver : IInlineConstraintResolver
{
private readonly IDictionary<string, Type> _inlineConstraintMap = GetDefaultConstraintMap();
/// <summary>
/// Gets the mutable dictionary that maps constraint keys to a particular constraint type.
/// </summary>
public IDictionary<string, Type> ConstraintMap
{
get
{
return _inlineConstraintMap;
}
}
private static IDictionary<string, Type> GetDefaultConstraintMap()
{
return new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
// Type-specific constraints
{ "int", typeof(IntRouteConstraint) },
};
}
/// <inheritdoc />
/// <example>
/// 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.
/// </example>
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;
}
}
}

View File

@ -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
{
/// <summary>
/// Defines an abstraction for resolving inline constraints as instances of <see cref="IRouteConstraint"/>.
/// </summary>
public interface IInlineConstraintResolver
{
/// <summary>
/// Resolves the inline constraint.
/// </summary>
/// <param name="inlineConstraint">The inline constraint to resolve.</param>
/// <returns>The <see cref="IRouteConstraint"/> the inline constraint was resolved to.</returns>
IRouteConstraint ResolveConstraint([NotNull] string inlineConstraint);
}
}

View File

@ -7,6 +7,8 @@ namespace Microsoft.AspNet.Routing
{
IRouter DefaultHandler { get; set; }
IInlineConstraintResolver InlineConstraintResolver { get; set; }
void Add(IRouter router);
}
}

View File

@ -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 = @"(?<parameterName>.+?)";
// 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 = @"(:(?<constraint>.*?(\(.*?\))?))*";
// A default value with an equal sign followed by zero or more characters
// Matches "=", "=abc"
private const string DefaultValueParameter = @"(?<defaultValue>(=.*?))?";
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<IRouteConstraint>();
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;
}
}
}

View File

@ -22,7 +22,12 @@
</ItemGroup>
<ItemGroup>
<Compile Include="BuilderExtensions.cs" />
<Compile Include="Constraints\CompositeRouteConstraint.cs" />
<Compile Include="Constraints\IntRouteConstraint.cs" />
<Compile Include="DefaultInlineConstraintResolver.cs" />
<Compile Include="IInlineConstraintResolver.cs" />
<Compile Include="INamedRouter.cs" />
<Compile Include="InlineRouteParameterParser.cs" />
<Compile Include="IRouteCollection.cs" />
<Compile Include="IRouteConstraint.cs" />
<Compile Include="IRouter.cs" />

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Routing
= new ResourceManager("Microsoft.AspNet.Routing.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// 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.
/// </summary>
internal static string NamedRoutes_AmbiguousRoutesFound
{
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Routing
}
/// <summary>
/// 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.
/// </summary>
internal static string FormatNamedRoutes_AmbiguousRoutesFound(object p0)
{
@ -42,6 +42,54 @@ namespace Microsoft.AspNet.Routing
return GetString("DefaultHandler_MustBeSet");
}
/// <summary>
/// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
/// </summary>
internal static string DefaultInlineConstraintResolver_AmbiguousCtors
{
get { return GetString("DefaultInlineConstraintResolver_AmbiguousCtors"); }
}
/// <summary>
/// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
/// </summary>
internal static string FormatDefaultInlineConstraintResolver_AmbiguousCtors(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_AmbiguousCtors"), p0, p1);
}
/// <summary>
/// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
/// </summary>
internal static string DefaultInlineConstraintResolver_CouldNotFindCtor
{
get { return GetString("DefaultInlineConstraintResolver_CouldNotFindCtor"); }
}
/// <summary>
/// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
/// </summary>
internal static string FormatDefaultInlineConstraintResolver_CouldNotFindCtor(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_CouldNotFindCtor"), p0, p1);
}
/// <summary>
/// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
/// </summary>
internal static string DefaultInlineConstraintResolver_TypeNotConstraint
{
get { return GetString("DefaultInlineConstraintResolver_TypeNotConstraint"); }
}
/// <summary>
/// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
/// </summary>
internal static string FormatDefaultInlineConstraintResolver_TypeNotConstraint(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_TypeNotConstraint"), p0, p1, p2);
}
/// <summary>
/// The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.
/// </summary>
@ -74,6 +122,22 @@ namespace Microsoft.AspNet.Routing
return GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
}
/// <summary>
/// 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.
/// </summary>
internal static string TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly
{
get { return GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly"); }
}
/// <summary>
/// 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.
/// </summary>
internal static string FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly"), p0);
}
/// <summary>
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
/// </summary>
@ -138,6 +202,22 @@ namespace Microsoft.AspNet.Routing
return GetString("TemplateRoute_CatchAllCannotBeOptional");
}
/// <summary>
/// An optional parameter cannot have default value.
/// </summary>
internal static string TemplateRoute_OptionalCannotHaveDefaultValue
{
get { return GetString("TemplateRoute_OptionalCannotHaveDefaultValue"); }
}
/// <summary>
/// An optional parameter cannot have default value.
/// </summary>
internal static string FormatTemplateRoute_OptionalCannotHaveDefaultValue()
{
return GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
}
/// <summary>
/// A catch-all parameter can only appear as the last segment of the route template.
/// </summary>
@ -250,12 +330,28 @@ namespace Microsoft.AspNet.Routing
return string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_ValidationMustBeStringOrCustomConstraint"), p0, p1, p2);
}
/// <summary>
/// The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.
/// </summary>
internal static string InlineRouteParser_CouldNotResolveConstraint
{
get { return GetString("InlineRouteParser_CouldNotResolveConstraint"); }
}
/// <summary>
/// The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.
/// </summary>
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++)

View File

@ -123,12 +123,24 @@
<data name="DefaultHandler_MustBeSet" xml:space="preserve">
<value>A default handler must be set on the RouteCollection.</value>
</data>
<data name="DefaultInlineConstraintResolver_AmbiguousCtors" xml:space="preserve">
<value>The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.</value>
</data>
<data name="DefaultInlineConstraintResolver_CouldNotFindCtor" xml:space="preserve">
<value>Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.</value>
</data>
<data name="DefaultInlineConstraintResolver_TypeNotConstraint" xml:space="preserve">
<value>The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.</value>
</data>
<data name="GeneralConstraints_ValidationMustBeStringOrCustomConstraint" xml:space="preserve">
<value>The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.</value>
</data>
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
<value>A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.</value>
</data>
<data name="TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly" xml:space="preserve">
<value>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.</value>
</data>
<data name="TemplateRoute_CannotHaveConsecutiveParameters" xml:space="preserve">
<value>A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.</value>
</data>
@ -141,6 +153,9 @@
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
<value>A catch-all parameter cannot be marked optional.</value>
</data>
<data name="TemplateRoute_OptionalCannotHaveDefaultValue" xml:space="preserve">
<value>An optional parameter cannot have default value.</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>
@ -162,4 +177,7 @@
<data name="TemplateRoute_ValidationMustBeStringOrCustomConstraint" xml:space="preserve">
<value>The constraint entry '{0}' on the route with route template '{1}' must have a string value or be of a type which implements '{2}'.</value>
</data>
<data name="InlineRouteParser_CouldNotResolveConstraint" xml:space="preserve">
<value>The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.</value>
</data>
</root>

View File

@ -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;

View File

@ -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<string, object> defaults, IDictionary<string, object> constraints)
public static IRouteCollection MapRoute(this IRouteCollection routes,
string name,
string template,
IDictionary<string, object> defaults,
IDictionary<string, object> 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;
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Routing.Constraints;
namespace Microsoft.AspNet.Routing
{

View File

@ -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<TemplateSegment>();
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<string> _parameterNames = new HashSet<string>(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;

View File

@ -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()
{

View File

@ -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<string, object> defaults,
IDictionary<string, object> constraints)
: this(target, null, routeTemplate, defaults, constraints)
IDictionary<string, object> constraints,
IInlineConstraintResolver inlineConstraintResolver)
: this(target, null, routeTemplate, defaults, constraints, inlineConstraintResolver)
{
}
@ -35,17 +36,20 @@ namespace Microsoft.AspNet.Routing.Template
string routeName,
string routeTemplate,
IDictionary<string, object> defaults,
IDictionary<string, object> constraints)
IDictionary<string, object> constraints,
IInlineConstraintResolver inlineConstraintResolver)
{
_target = target;
_routeTemplate = routeTemplate ?? string.Empty;
Name = routeName;
_defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate);
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate) ??
new Dictionary<string, IRouteConstraint>();
// 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;
}
}
}
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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<string, object> defaults, IDictionary<string, object> 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);

View File

@ -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<Template>(expected, actual, new TemplateEqualityComparer());
@ -35,11 +37,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List<TemplateSegment>());
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<Template>(expected, actual, new TemplateEqualityComparer());
@ -53,11 +55,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List<TemplateSegment>());
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<Template>(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<Template>(expected, actual, new TemplateEqualityComparer());
@ -93,19 +95,31 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List<TemplateSegment>());
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<Template>(expected, actual, new TemplateEqualityComparer());
@ -120,11 +134,15 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(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, 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<Template>(expected, actual, new TemplateEqualityComparer());
@ -138,12 +156,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List<TemplateSegment>());
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<Template>(expected, actual, new TemplateEqualityComparer());
@ -157,14 +179,22 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(new List<TemplateSegment>());
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<Template>(expected, actual, new TemplateEqualityComparer());
@ -179,12 +209,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var expected = new Template(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, 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<Template>(expected, actual, new TemplateEqualityComparer());
@ -194,7 +228,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
public void InvalidTemplate_WithRepeatedParameter()
{
var ex = ExceptionAssert.Throws<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => 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<ArgumentException>(
() => TemplateParser.Parse("{a}/{*b?}"),
() => TemplateParser.Parse("{a}/{*b?}", _inlineConstraintResolver),
"A catch-all parameter cannot be marked optional." + Environment.NewLine +
"Parameter name: routeTemplate");
}

View File

@ -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<string, object> 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)