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 System.Text.RegularExpressions;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing
|
namespace Microsoft.AspNet.Routing.Constraints
|
||||||
{
|
{
|
||||||
public class RegexConstraint : IRouteConstraint
|
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; }
|
IRouter DefaultHandler { get; set; }
|
||||||
|
|
||||||
|
IInlineConstraintResolver InlineConstraintResolver { get; set; }
|
||||||
|
|
||||||
void Add(IRouter router);
|
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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="BuilderExtensions.cs" />
|
<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="INamedRouter.cs" />
|
||||||
|
<Compile Include="InlineRouteParameterParser.cs" />
|
||||||
<Compile Include="IRouteCollection.cs" />
|
<Compile Include="IRouteCollection.cs" />
|
||||||
<Compile Include="IRouteConstraint.cs" />
|
<Compile Include="IRouteConstraint.cs" />
|
||||||
<Compile Include="IRouter.cs" />
|
<Compile Include="IRouter.cs" />
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
= new ResourceManager("Microsoft.AspNet.Routing.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
= new ResourceManager("Microsoft.AspNet.Routing.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
internal static string NamedRoutes_AmbiguousRoutesFound
|
internal static string NamedRoutes_AmbiguousRoutesFound
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
internal static string FormatNamedRoutes_AmbiguousRoutesFound(object p0)
|
internal static string FormatNamedRoutes_AmbiguousRoutesFound(object p0)
|
||||||
{
|
{
|
||||||
|
|
@ -42,6 +42,54 @@ namespace Microsoft.AspNet.Routing
|
||||||
return GetString("DefaultHandler_MustBeSet");
|
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>
|
/// <summary>
|
||||||
/// The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.
|
/// The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -74,6 +122,22 @@ namespace Microsoft.AspNet.Routing
|
||||||
return GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
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>
|
/// <summary>
|
||||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -138,6 +202,22 @@ namespace Microsoft.AspNet.Routing
|
||||||
return GetString("TemplateRoute_CatchAllCannotBeOptional");
|
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>
|
/// <summary>
|
||||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -250,6 +330,22 @@ namespace Microsoft.AspNet.Routing
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_ValidationMustBeStringOrCustomConstraint"), p0, p1, p2);
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -123,12 +123,24 @@
|
||||||
<data name="DefaultHandler_MustBeSet" xml:space="preserve">
|
<data name="DefaultHandler_MustBeSet" xml:space="preserve">
|
||||||
<value>A default handler must be set on the RouteCollection.</value>
|
<value>A default handler must be set on the RouteCollection.</value>
|
||||||
</data>
|
</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">
|
<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>
|
<value>The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
|
<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>
|
<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>
|
||||||
|
<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">
|
<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>
|
<value>A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -141,6 +153,9 @@
|
||||||
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
||||||
<value>A catch-all parameter cannot be marked optional.</value>
|
<value>A catch-all parameter cannot be marked optional.</value>
|
||||||
</data>
|
</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">
|
<data name="TemplateRoute_CatchAllMustBeLast" xml:space="preserve">
|
||||||
<value>A catch-all parameter can only appear as the last segment of the route template.</value>
|
<value>A catch-all parameter can only appear as the last segment of the route template.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -162,4 +177,7 @@
|
||||||
<data name="TemplateRoute_ValidationMustBeStringOrCustomConstraint" xml:space="preserve">
|
<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>
|
<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>
|
||||||
|
<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>
|
</root>
|
||||||
|
|
@ -26,6 +26,8 @@ namespace Microsoft.AspNet.Routing
|
||||||
|
|
||||||
public IRouter DefaultHandler { get; set; }
|
public IRouter DefaultHandler { get; set; }
|
||||||
|
|
||||||
|
public IInlineConstraintResolver InlineConstraintResolver { get; set; }
|
||||||
|
|
||||||
public void Add([NotNull] IRouter router)
|
public void Add([NotNull] IRouter router)
|
||||||
{
|
{
|
||||||
var namedRouter = router as INamedRouter;
|
var namedRouter = router as INamedRouter;
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,23 @@ namespace Microsoft.AspNet.Routing
|
||||||
throw new InvalidOperationException(Resources.DefaultHandler_MustBeSet);
|
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;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IRouteCollection MapRoute(this IRouteCollection routes, string name, string template,
|
public static IRouteCollection MapRoute(this IRouteCollection routes, string name, string template,
|
||||||
object defaults, object constraints)
|
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;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,15 +64,23 @@ namespace Microsoft.AspNet.Routing
|
||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IRouteCollection MapRoute(this IRouteCollection routes, string name, string template,
|
public static IRouteCollection MapRoute(this IRouteCollection routes,
|
||||||
IDictionary<string, object> defaults, IDictionary<string, object> constraints)
|
string name,
|
||||||
|
string template,
|
||||||
|
IDictionary<string, object> defaults,
|
||||||
|
IDictionary<string, object> constraints)
|
||||||
{
|
{
|
||||||
if (routes.DefaultHandler == null)
|
if (routes.DefaultHandler == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.DefaultHandler_MustBeSet);
|
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;
|
return routes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Routing.Constraints;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing
|
namespace Microsoft.AspNet.Routing
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Diagnostics.Contracts;
|
using System.Diagnostics.Contracts;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing.Template
|
namespace Microsoft.AspNet.Routing.Template
|
||||||
{
|
{
|
||||||
|
|
@ -18,7 +16,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
private const char EqualsSign = '=';
|
private const char EqualsSign = '=';
|
||||||
private const char QuestionMark = '?';
|
private const char QuestionMark = '?';
|
||||||
|
|
||||||
public static Template Parse(string routeTemplate)
|
public static Template Parse(string routeTemplate, IInlineConstraintResolver constraintResolver)
|
||||||
{
|
{
|
||||||
if (routeTemplate == null)
|
if (routeTemplate == null)
|
||||||
{
|
{
|
||||||
|
|
@ -30,7 +28,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, "routeTemplate");
|
throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, "routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = new TemplateParserContext(routeTemplate);
|
var context = new TemplateParserContext(routeTemplate, constraintResolver);
|
||||||
var segments = new List<TemplateSegment>();
|
var segments = new List<TemplateSegment>();
|
||||||
|
|
||||||
while (context.Next())
|
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);
|
// At this point, we need to parse the raw name for inline constraint,
|
||||||
var isOptional = rawName.EndsWith("?", StringComparison.Ordinal);
|
// 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;
|
context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rawName = isCatchAll ? rawName.Substring(1) : rawName;
|
if (templatePart.IsOptional && templatePart.DefaultValue != null)
|
||||||
rawName = isOptional ? rawName.Substring(0, rawName.Length - 1) : rawName;
|
{
|
||||||
|
// 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))
|
if (IsValidParameterName(context, parameterName))
|
||||||
{
|
{
|
||||||
segment.Parts.Add(TemplatePart.CreateParameter(parameterName, isCatchAll, isOptional));
|
segment.Parts.Add(templatePart);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -392,12 +401,13 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
|
|
||||||
private HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
public TemplateParserContext(string template)
|
public TemplateParserContext(string template, IInlineConstraintResolver constraintResolver)
|
||||||
{
|
{
|
||||||
Contract.Assert(template != null);
|
Contract.Assert(template != null);
|
||||||
_template = template;
|
_template = template;
|
||||||
|
|
||||||
_index = -1;
|
_index = -1;
|
||||||
|
ConstraintResolver = constraintResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public char Current
|
public char Current
|
||||||
|
|
@ -416,6 +426,12 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
get { return _parameterNames; }
|
get { return _parameterNames; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IInlineConstraintResolver ConstraintResolver
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Back()
|
public bool Back()
|
||||||
{
|
{
|
||||||
return --_index >= 0;
|
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()
|
return new TemplatePart()
|
||||||
{
|
{
|
||||||
|
|
@ -25,6 +29,8 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
Name = name,
|
Name = name,
|
||||||
IsCatchAll = isCatchAll,
|
IsCatchAll = isCatchAll,
|
||||||
IsOptional = isOptional,
|
IsOptional = isOptional,
|
||||||
|
DefaultValue = defaultValue,
|
||||||
|
InlineConstraint = inlineConstraint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,6 +40,8 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
public bool IsOptional { get; private set; }
|
public bool IsOptional { get; private set; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public string Text { get; private set; }
|
public string Text { get; private set; }
|
||||||
|
public object DefaultValue { get; private set; }
|
||||||
|
public IRouteConstraint InlineConstraint { get; private set; }
|
||||||
|
|
||||||
internal string DebuggerToString()
|
internal string DebuggerToString()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.Contracts;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Routing.Constraints;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing.Template
|
namespace Microsoft.AspNet.Routing.Template
|
||||||
{
|
{
|
||||||
|
|
@ -18,16 +18,17 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
private readonly TemplateMatcher _matcher;
|
private readonly TemplateMatcher _matcher;
|
||||||
private readonly TemplateBinder _binder;
|
private readonly TemplateBinder _binder;
|
||||||
|
|
||||||
public TemplateRoute(IRouter target, string routeTemplate)
|
public TemplateRoute(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver)
|
||||||
: this(target, routeTemplate, null, null)
|
: this(target, routeTemplate, null, null, inlineConstraintResolver)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public TemplateRoute([NotNull] IRouter target,
|
public TemplateRoute([NotNull] IRouter target,
|
||||||
string routeTemplate,
|
string routeTemplate,
|
||||||
IDictionary<string, object> defaults,
|
IDictionary<string, object> defaults,
|
||||||
IDictionary<string, object> constraints)
|
IDictionary<string, object> constraints,
|
||||||
: this(target, null, routeTemplate, defaults, constraints)
|
IInlineConstraintResolver inlineConstraintResolver)
|
||||||
|
: this(target, null, routeTemplate, defaults, constraints, inlineConstraintResolver)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,16 +36,19 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
string routeName,
|
string routeName,
|
||||||
string routeTemplate,
|
string routeTemplate,
|
||||||
IDictionary<string, object> defaults,
|
IDictionary<string, object> defaults,
|
||||||
IDictionary<string, object> constraints)
|
IDictionary<string, object> constraints,
|
||||||
|
IInlineConstraintResolver inlineConstraintResolver)
|
||||||
{
|
{
|
||||||
_target = target;
|
_target = target;
|
||||||
_routeTemplate = routeTemplate ?? string.Empty;
|
_routeTemplate = routeTemplate ?? string.Empty;
|
||||||
Name = routeName;
|
Name = routeName;
|
||||||
_defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
_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.
|
// The parser will throw for invalid routes.
|
||||||
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
_parsedTemplate = TemplateParser.Parse(RouteTemplate, inlineConstraintResolver);
|
||||||
|
UpdateInlineDefaultValuesAndConstraints();
|
||||||
|
|
||||||
_matcher = new TemplateMatcher(_parsedTemplate);
|
_matcher = new TemplateMatcher(_parsedTemplate);
|
||||||
_binder = new TemplateBinder(_parsedTemplate, _defaults);
|
_binder = new TemplateBinder(_parsedTemplate, _defaults);
|
||||||
|
|
@ -170,5 +174,39 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
ProvidedValues = providedValues,
|
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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Routing.Constraints;
|
||||||
using Microsoft.AspNet.Testing;
|
using Microsoft.AspNet.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Routing.Constraints;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
string expected)
|
string expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TemplateBinder(TemplateParser.Parse(template), defaults);
|
var binder = new TemplateBinder(TemplateParser.Parse(template, new DefaultInlineConstraintResolver()),
|
||||||
|
defaults);
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var acceptedValues = binder.GetAcceptedValues(null, values);
|
var acceptedValues = binder.GetAcceptedValues(null, values);
|
||||||
|
|
@ -960,7 +961,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
string expected)
|
string expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TemplateBinder(TemplateParser.Parse(template), defaults);
|
var binder = new TemplateBinder(TemplateParser.Parse(template, new DefaultInlineConstraintResolver()), defaults);
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var acceptedValues = binder.GetAcceptedValues(ambientValues, values);
|
var acceptedValues = binder.GetAcceptedValues(ambientValues, values);
|
||||||
|
|
|
||||||
|
|
@ -784,13 +784,13 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
private TemplateMatcher CreateMatcher(string template)
|
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)
|
private static void RunTest(string template, string path, IDictionary<string, object> defaults, IDictionary<string, object> expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var matcher = new TemplateMatcher(TemplateParser.Parse(template));
|
var matcher = new TemplateMatcher(TemplateParser.Parse(template, new DefaultInlineConstraintResolver()));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var match = matcher.Match(path, defaults);
|
var match = matcher.Match(path, defaults);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
{
|
{
|
||||||
public class TemplateRouteParserTests
|
public class TemplateRouteParserTests
|
||||||
{
|
{
|
||||||
|
private IInlineConstraintResolver _inlineConstraintResolver = new DefaultInlineConstraintResolver();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Parse_SingleLiteral()
|
public void Parse_SingleLiteral()
|
||||||
{
|
{
|
||||||
|
|
@ -21,7 +23,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
|
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -35,11 +37,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
var expected = new Template(new List<TemplateSegment>());
|
var expected = new Template(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new 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]);
|
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -53,11 +55,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
var expected = new Template(new List<TemplateSegment>());
|
var expected = new Template(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new 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]);
|
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
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"));
|
expected.Segments[2].Parts.Add(TemplatePart.CreateLiteral("super"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -93,19 +95,31 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
var expected = new Template(new List<TemplateSegment>());
|
var expected = new Template(new List<TemplateSegment>());
|
||||||
|
|
||||||
expected.Segments.Add(new 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.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||||
|
|
||||||
expected.Segments.Add(new TemplateSegment());
|
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.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||||
|
|
||||||
expected.Segments.Add(new TemplateSegment());
|
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]);
|
expected.Parameters.Add(expected.Segments[2].Parts[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -120,11 +134,15 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
var expected = new Template(new List<TemplateSegment>());
|
var expected = new Template(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new TemplateSegment());
|
expected.Segments.Add(new TemplateSegment());
|
||||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
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.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -138,12 +156,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
var expected = new Template(new List<TemplateSegment>());
|
var expected = new Template(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new 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.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -157,14 +179,22 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
var expected = new Template(new List<TemplateSegment>());
|
var expected = new Template(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new 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.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
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]);
|
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -179,12 +209,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
var expected = new Template(new List<TemplateSegment>());
|
var expected = new Template(new List<TemplateSegment>());
|
||||||
expected.Segments.Add(new TemplateSegment());
|
expected.Segments.Add(new TemplateSegment());
|
||||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
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.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
|
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = TemplateParser.Parse(template);
|
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||||
|
|
@ -194,7 +228,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_WithRepeatedParameter()
|
public void InvalidTemplate_WithRepeatedParameter()
|
||||||
{
|
{
|
||||||
var ex = ExceptionAssert.Throws<ArgumentException>(
|
var ex = ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
|
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}", _inlineConstraintResolver),
|
||||||
"The route parameter name 'controller' appears more than one time in the route template." + Environment.NewLine + "Parameter name: routeTemplate");
|
"The route parameter name 'controller' appears more than one time in the route template." + Environment.NewLine + "Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +242,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_WithMismatchedBraces(string template)
|
public void InvalidTemplate_WithMismatchedBraces(string template)
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
@"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +251,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
|
public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +260,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
|
public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +269,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
|
public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +278,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotHaveCatchAllWithNoName()
|
public void InvalidTemplate_CannotHaveCatchAllWithNoName()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 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 +
|
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
|
|
@ -254,7 +288,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
|
public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +297,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
|
public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +306,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_SameParameterTwiceThrows()
|
public void InvalidTemplate_SameParameterTwiceThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -281,7 +315,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
|
public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +324,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
|
public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +333,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
|
public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 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 +
|
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
|
|
@ -309,7 +343,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
|
public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 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 +
|
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
|
|
@ -319,7 +353,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
|
public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 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 +
|
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
|
|
@ -329,7 +363,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
|
public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -338,7 +372,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
|
public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +381,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_RepeatedParametersThrows()
|
public void InvalidTemplate_RepeatedParametersThrows()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +390,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotStartWithSlash()
|
public void InvalidTemplate_CannotStartWithSlash()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("/foo"),
|
() => TemplateParser.Parse("/foo", _inlineConstraintResolver),
|
||||||
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -365,7 +399,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotStartWithTilde()
|
public void InvalidTemplate_CannotStartWithTilde()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("~foo"),
|
() => TemplateParser.Parse("~foo", _inlineConstraintResolver),
|
||||||
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -374,7 +408,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CannotContainQuestionMark()
|
public void InvalidTemplate_CannotContainQuestionMark()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -383,7 +417,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
|
public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 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 +
|
"The '?' character marks a parameter as optional, and can only occur at the end of the parameter." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
|
|
@ -393,7 +427,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_MultiSegmentParameterCannotContainOptionalParameter()
|
public void InvalidTemplate_MultiSegmentParameterCannotContainOptionalParameter()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
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 +
|
"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");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
@ -402,7 +436,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void InvalidTemplate_CatchAllMarkedOptional()
|
public void InvalidTemplate_CatchAllMarkedOptional()
|
||||||
{
|
{
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => TemplateParser.Parse("{a}/{*b?}"),
|
() => TemplateParser.Parse("{a}/{*b?}", _inlineConstraintResolver),
|
||||||
"A catch-all parameter cannot be marked optional." + Environment.NewLine +
|
"A catch-all parameter cannot be marked optional." + Environment.NewLine +
|
||||||
"Parameter name: routeTemplate");
|
"Parameter name: routeTemplate");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Routing.Constraints;
|
||||||
using Microsoft.AspNet.Testing;
|
using Microsoft.AspNet.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -463,22 +464,34 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
|
|
||||||
private static TemplateRoute CreateRoute(string template, bool accept = true)
|
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)
|
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)
|
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)
|
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)
|
private static IRouter CreateTarget(bool accept = true)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue