Support for InlineConstraints in Route Template
This commit is contained in:
parent
bde341caed
commit
187d8e4911
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ namespace Microsoft.AspNet.Routing
|
|||
{
|
||||
IRouter DefaultHandler { get; set; }
|
||||
|
||||
IInlineConstraintResolver InlineConstraintResolver { get; set; }
|
||||
|
||||
void Add(IRouter router);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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++)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Routing.Constraints;
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue