Fix for #92 - Make Constraints, DataTokens, and Defaults readonly
The properties on TemplateRoute for DataTokens and Defaults are now readonly. This prevents modifying these collections in a way that invalidates cached data, or violates thread-safety. To do the same for constraints, this change includes a substantial refactor of how we realize inline constraints, and moves the constraint resolver out of the parsing phase. This allow creates a builder for the constraint map, that will make it easier to implement features like optional constraints, and is reusable for anyone building their own type of routing system.
This commit is contained in:
parent
72604cb327
commit
01345eca91
|
|
@ -27,8 +27,8 @@ namespace Microsoft.AspNet.Routing
|
|||
private static readonly Regex _parameterRegex = new Regex(
|
||||
"^" + ParameterNamePattern + ConstraintPattern + DefaultValueParameter + "$",
|
||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
public static TemplatePart ParseRouteParameter([NotNull] string routeParameter,
|
||||
[NotNull] IInlineConstraintResolver constraintResolver)
|
||||
|
||||
public static TemplatePart ParseRouteParameter([NotNull] string routeParameter)
|
||||
{
|
||||
var isCatchAll = routeParameter.StartsWith("*", StringComparison.Ordinal);
|
||||
var isOptional = routeParameter.EndsWith("?", StringComparison.Ordinal);
|
||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Routing
|
|||
isCatchAll: isCatchAll,
|
||||
isOptional: isOptional,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null);
|
||||
inlineConstraints: null);
|
||||
}
|
||||
|
||||
var parameterName = parameterMatch.Groups["parameterName"].Value;
|
||||
|
|
@ -54,13 +54,13 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
// Register inline constraints if present
|
||||
var constraintGroup = parameterMatch.Groups["constraint"];
|
||||
var inlineConstraint = GetInlineConstraint(constraintGroup, constraintResolver);
|
||||
var inlineConstraints = GetInlineConstraints(constraintGroup);
|
||||
|
||||
return TemplatePart.CreateParameter(parameterName,
|
||||
isCatchAll,
|
||||
isOptional,
|
||||
defaultValue,
|
||||
inlineConstraint);
|
||||
inlineConstraints);
|
||||
}
|
||||
|
||||
private static string GetDefaultValue(Group defaultValueGroup)
|
||||
|
|
@ -77,33 +77,16 @@ namespace Microsoft.AspNet.Routing
|
|||
return null;
|
||||
}
|
||||
|
||||
private static IRouteConstraint GetInlineConstraint(Group constraintGroup,
|
||||
IInlineConstraintResolver _constraintResolver)
|
||||
private static IEnumerable<InlineConstraint> GetInlineConstraints(Group constraintGroup)
|
||||
{
|
||||
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));
|
||||
}
|
||||
var constraints = new List<InlineConstraint>();
|
||||
|
||||
parameterConstraints.Add(constraint);
|
||||
foreach (Capture capture in constraintGroup.Captures)
|
||||
{
|
||||
constraints.Add(new InlineConstraint(capture.Value));
|
||||
}
|
||||
|
||||
if (parameterConstraints.Count > 0)
|
||||
{
|
||||
var constraint = parameterConstraints.Count == 1 ?
|
||||
parameterConstraints[0] :
|
||||
new CompositeRouteConstraint(parameterConstraints);
|
||||
return constraint;
|
||||
}
|
||||
|
||||
return null;
|
||||
return constraints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNet.Routing.Logging
|
|||
/// <summary>
|
||||
/// The values produced by default.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> DefaultValues { get; set; }
|
||||
public IReadOnlyDictionary<string, object> DefaultValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The values produced from the request.
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Routing.Logging
|
|||
/// <summary>
|
||||
/// The constraints matched on the produced values.
|
||||
/// </summary>
|
||||
public IDictionary<string, IRouteConstraint> Constraints { get; set; }
|
||||
public IReadOnlyDictionary<string, IRouteConstraint> Constraints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the <see cref="ProducedValues"/> matched.
|
||||
|
|
|
|||
|
|
@ -138,22 +138,6 @@ namespace Microsoft.AspNet.Routing
|
|||
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>
|
||||
internal static string GeneralConstraints_ValidationMustBeStringOrCustomConstraint
|
||||
{
|
||||
get { return GetString("GeneralConstraints_ValidationMustBeStringOrCustomConstraint"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' must have a string value or be of a type which implements '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatGeneralConstraints_ValidationMustBeStringOrCustomConstraint(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("GeneralConstraints_ValidationMustBeStringOrCustomConstraint"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
|
|
@ -363,35 +347,35 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' on the route with route template '{1}' must have a string value or be of a type which implements '{2}'.
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_ValidationMustBeStringOrCustomConstraint
|
||||
internal static string RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint
|
||||
{
|
||||
get { return GetString("TemplateRoute_ValidationMustBeStringOrCustomConstraint"); }
|
||||
get { return GetString("RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' on the route with route template '{1}' must have a string value or be of a type which implements '{2}'.
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_ValidationMustBeStringOrCustomConstraint(object p0, object p1, object p2)
|
||||
internal static string FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_ValidationMustBeStringOrCustomConstraint"), p0, p1, p2);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint"), p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string InlineRouteParser_CouldNotResolveConstraint
|
||||
internal static string RouteConstraintBuilder_CouldNotResolveConstraint
|
||||
{
|
||||
get { return GetString("InlineRouteParser_CouldNotResolveConstraint"); }
|
||||
get { return GetString("RouteConstraintBuilder_CouldNotResolveConstraint"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The inline constraint resolver of type '{0}' was unable to resolve the following inline constraint: '{1}'.
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatInlineRouteParser_CouldNotResolveConstraint(object p0, object p1)
|
||||
internal static string FormatRouteConstraintBuilder_CouldNotResolveConstraint(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("InlineRouteParser_CouldNotResolveConstraint"), p0, p1);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintBuilder_CouldNotResolveConstraint"), p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
|
|
|
|||
|
|
@ -141,9 +141,6 @@
|
|||
<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>
|
||||
|
|
@ -183,10 +180,10 @@
|
|||
<data name="TemplateRoute_RepeatedParameter" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' appears more than one time in the route template.</value>
|
||||
</data>
|
||||
<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 name="RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.</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 name="RouteConstraintBuilder_CouldNotResolveConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -7,58 +7,52 @@ using Microsoft.AspNet.Routing.Constraints;
|
|||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// A builder for produding a mapping of keys to see <see cref="IRouteConstraint"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="RouteConstraintBuilder"/> allows iterative building a set of route constraints, and will
|
||||
/// merge multiple entries for the same key.
|
||||
/// </remarks>
|
||||
public class RouteConstraintBuilder
|
||||
{
|
||||
public static IDictionary<string, IRouteConstraint>
|
||||
BuildConstraints(IDictionary<string, object> inputConstraints)
|
||||
private readonly IInlineConstraintResolver _inlineConstraintResolver;
|
||||
private readonly string _displayName;
|
||||
|
||||
private readonly Dictionary<string, List<IRouteConstraint>> _constraints;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteConstraintBuilder"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="inlineConstraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
|
||||
/// <param name="displayName">The display name (for use in error messages).</param>
|
||||
public RouteConstraintBuilder(
|
||||
[NotNull] IInlineConstraintResolver inlineConstraintResolver,
|
||||
[NotNull] string displayName)
|
||||
{
|
||||
return BuildConstraintsCore(inputConstraints, routeTemplate: null);
|
||||
_inlineConstraintResolver = inlineConstraintResolver;
|
||||
_displayName = displayName;
|
||||
|
||||
_constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static IDictionary<string, IRouteConstraint>
|
||||
BuildConstraints(IDictionary<string, object> inputConstraints, [NotNull] string routeTemplate)
|
||||
/// <summary>
|
||||
/// Builds a mapping of constraints.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IReadOnlyDictionary{string, IRouteConstraint}"/> of the constraints.</returns>
|
||||
public IReadOnlyDictionary<string, IRouteConstraint> Build()
|
||||
{
|
||||
return BuildConstraintsCore(inputConstraints, routeTemplate);
|
||||
}
|
||||
|
||||
private static IDictionary<string, IRouteConstraint>
|
||||
BuildConstraintsCore(IDictionary<string, object> inputConstraints, string routeTemplate)
|
||||
{
|
||||
if (inputConstraints == null || inputConstraints.Count == 0)
|
||||
var constraints = new Dictionary<string, IRouteConstraint>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in _constraints)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var constraints = new Dictionary<string, IRouteConstraint>(inputConstraints.Count,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var kvp in inputConstraints)
|
||||
{
|
||||
var constraint = kvp.Value as IRouteConstraint;
|
||||
|
||||
if (constraint == null)
|
||||
IRouteConstraint constraint;
|
||||
if (kvp.Value.Count == 1)
|
||||
{
|
||||
var regexPattern = kvp.Value as string;
|
||||
|
||||
if (regexPattern == null)
|
||||
{
|
||||
if (routeTemplate != null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatTemplateRoute_ValidationMustBeStringOrCustomConstraint(
|
||||
kvp.Key, routeTemplate, typeof(IRouteConstraint)));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatGeneralConstraints_ValidationMustBeStringOrCustomConstraint(
|
||||
kvp.Key, typeof(IRouteConstraint)));
|
||||
}
|
||||
}
|
||||
|
||||
var constraintsRegEx = "^(" + regexPattern + ")$";
|
||||
|
||||
constraint = new RegexRouteConstraint(constraintsRegEx);
|
||||
constraint = kvp.Value[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
constraint = new CompositeRouteConstraint(kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
constraints.Add(kvp.Key, constraint);
|
||||
|
|
@ -66,5 +60,79 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
return constraints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a constraint instance for the given key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">
|
||||
/// The constraint instance. Must either be a string or an instance of <see cref="IRouteConstraint"/>.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// If the <paramref name="value"/> is a string, it will be converted to a <see cref="RegexRouteConstraint"/>.
|
||||
///
|
||||
/// For example, the string <code>Product[0-9]+</code> will be converted to the regular expression
|
||||
/// <code>^(Product[0-9]+)</code>. See <see cref="System.Text.RegularExpressions.Regex"/> for more details.
|
||||
/// </remarks>
|
||||
public void AddConstraint([NotNull] string key, [NotNull] object value)
|
||||
{
|
||||
var constraint = value as IRouteConstraint;
|
||||
if (constraint == null)
|
||||
{
|
||||
var regexPattern = value as string;
|
||||
if (regexPattern == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
|
||||
key,
|
||||
value,
|
||||
_displayName,
|
||||
typeof(IRouteConstraint)));
|
||||
}
|
||||
|
||||
var constraintsRegEx = "^(" + regexPattern + ")$";
|
||||
constraint = new RegexRouteConstraint(constraintsRegEx);
|
||||
}
|
||||
|
||||
Add(key, constraint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a constraint for the given key, resolved by the <see cref="IInlineConstraintResolver"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="constraintText">The text to be resolved by <see cref="IInlineConstraintResolver"/>.</param>
|
||||
/// <remarks>
|
||||
/// The <see cref="IInlineConstraintResolver"/> can create <see cref="IRouteConstraint"/> instances
|
||||
/// based on <paramref name="constraintText"/>. See <see cref="RouteOptions.ConstraintMap"/> to register
|
||||
/// custom constraint types.
|
||||
/// </remarks>
|
||||
public void AddResolvedConstraint([NotNull] string key, [NotNull] string constraintText)
|
||||
{
|
||||
var constraint = _inlineConstraintResolver.ResolveConstraint(constraintText);
|
||||
if (constraint == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatRouteConstraintBuilder_CouldNotResolveConstraint(
|
||||
key,
|
||||
constraintText,
|
||||
_displayName,
|
||||
_inlineConstraintResolver.GetType().Name));
|
||||
}
|
||||
|
||||
Add(key, constraint);
|
||||
}
|
||||
|
||||
private void Add(string key, IRouteConstraint constraint)
|
||||
{
|
||||
List<IRouteConstraint> list;
|
||||
if (!_constraints.TryGetValue(key, out list))
|
||||
{
|
||||
list = new List<IRouteConstraint>();
|
||||
_constraints.Add(key, list);
|
||||
}
|
||||
|
||||
list.Add(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Routing
|
|||
{
|
||||
public static class RouteConstraintMatcher
|
||||
{
|
||||
public static bool Match(IDictionary<string, IRouteConstraint> constraints,
|
||||
public static bool Match(IReadOnlyDictionary<string, IRouteConstraint> constraints,
|
||||
[NotNull] IDictionary<string, object> routeValues,
|
||||
[NotNull] HttpContext httpContext,
|
||||
[NotNull] IRouter route,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ namespace Microsoft.AspNet.Routing
|
|||
/// <summary>
|
||||
/// An <see cref="IDictionary{string, object}"/> type for route values.
|
||||
/// </summary>
|
||||
public class RouteValueDictionary : IDictionary<string, object>
|
||||
public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object>
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty, cached instance of <see cref="RouteValueDictionary"/>.
|
||||
/// </summary>
|
||||
internal static readonly IReadOnlyDictionary<string, object> Empty = new RouteValueDictionary();
|
||||
|
||||
private readonly Dictionary<string, object> _dictionary;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -139,6 +144,14 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
}
|
||||
|
||||
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dictionary.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, object>.ValueCollection Values
|
||||
{
|
||||
|
|
@ -157,6 +170,14 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
}
|
||||
|
||||
IEnumerable<object> IReadOnlyDictionary<string, object>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dictionary.Values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.Template
|
||||
{
|
||||
/// <summary>
|
||||
/// The parsed representation of an inline constraint in a route parameter.
|
||||
/// </summary>
|
||||
public class InlineConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="InlineConstraint"/>.
|
||||
/// </summary>
|
||||
/// <param name="constraint">The constraint text.</param>
|
||||
public InlineConstraint([NotNull] string constraint)
|
||||
{
|
||||
Constraint = constraint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the constraint text.
|
||||
/// </summary>
|
||||
public string Constraint { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,10 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
public class TemplateBinder
|
||||
{
|
||||
private readonly IDictionary<string, object> _defaults;
|
||||
private readonly IReadOnlyDictionary<string, object> _defaults;
|
||||
private readonly RouteTemplate _template;
|
||||
|
||||
public TemplateBinder(RouteTemplate template, IDictionary<string, object> defaults)
|
||||
public TemplateBinder(RouteTemplate template, IReadOnlyDictionary<string, object> defaults)
|
||||
{
|
||||
if (template == null)
|
||||
{
|
||||
|
|
@ -336,12 +336,14 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private class TemplateBindingContext
|
||||
{
|
||||
private readonly IDictionary<string, object> _defaults;
|
||||
private readonly IReadOnlyDictionary<string, object> _defaults;
|
||||
|
||||
private readonly RouteValueDictionary _acceptedValues;
|
||||
private readonly RouteValueDictionary _filters;
|
||||
|
||||
public TemplateBindingContext(IDictionary<string, object> defaults, IDictionary<string, object> values)
|
||||
public TemplateBindingContext(
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
IDictionary<string, object> values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
|
|
@ -355,6 +357,10 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
if (_defaults != null)
|
||||
{
|
||||
_filters = new RouteValueDictionary(defaults);
|
||||
foreach (var kvp in _defaults)
|
||||
{
|
||||
_filters.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,27 +14,22 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
private static readonly char[] Delimiters = new char[] { SeparatorChar };
|
||||
|
||||
public TemplateMatcher(RouteTemplate template)
|
||||
public TemplateMatcher(
|
||||
[NotNull] RouteTemplate template,
|
||||
[NotNull] IReadOnlyDictionary<string, object> defaults)
|
||||
{
|
||||
if (template == null)
|
||||
{
|
||||
throw new ArgumentNullException("template");
|
||||
}
|
||||
|
||||
Template = template;
|
||||
Defaults = defaults ?? RouteValueDictionary.Empty;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, object> Defaults { get; private set; }
|
||||
|
||||
public RouteTemplate Template { get; private set; }
|
||||
|
||||
public IDictionary<string, object> Match(string requestPath, IDictionary<string, object> defaults)
|
||||
public IDictionary<string, object> Match([NotNull] string requestPath)
|
||||
{
|
||||
var requestSegments = requestPath.Split(Delimiters);
|
||||
|
||||
if (defaults == null)
|
||||
{
|
||||
defaults = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
var values = new RouteValueDictionary();
|
||||
|
||||
for (var i = 0; i < requestSegments.Length; i++)
|
||||
|
|
@ -77,7 +72,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
// It's ok for a catch-all to produce a null value
|
||||
object defaultValue;
|
||||
defaults.TryGetValue(part.Name, out defaultValue);
|
||||
Defaults.TryGetValue(part.Name, out defaultValue);
|
||||
|
||||
values.Add(part.Name, defaultValue);
|
||||
}
|
||||
|
|
@ -94,7 +89,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
else
|
||||
{
|
||||
object defaultValue;
|
||||
if (defaults.TryGetValue(part.Name, out defaultValue))
|
||||
if (Defaults.TryGetValue(part.Name, out defaultValue))
|
||||
{
|
||||
values.Add(part.Name, defaultValue);
|
||||
}
|
||||
|
|
@ -114,7 +109,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
else
|
||||
{
|
||||
if (!MatchComplexSegment(routeSegment, requestSegment, defaults, values))
|
||||
if (!MatchComplexSegment(routeSegment, requestSegment, Defaults, values))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
@ -142,7 +137,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
// It's ok for a catch-all to produce a null value
|
||||
object defaultValue;
|
||||
if (defaults.TryGetValue(part.Name, out defaultValue) || part.IsCatchAll)
|
||||
if (Defaults.TryGetValue(part.Name, out defaultValue) || part.IsCatchAll)
|
||||
{
|
||||
values.Add(part.Name, defaultValue);
|
||||
}
|
||||
|
|
@ -158,14 +153,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
// Copy all remaining default values to the route data
|
||||
if (defaults != null)
|
||||
foreach (var kvp in Defaults)
|
||||
{
|
||||
foreach (var kvp in defaults)
|
||||
if (!values.ContainsKey(kvp.Key))
|
||||
{
|
||||
if (!values.ContainsKey(kvp.Key))
|
||||
{
|
||||
values.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
values.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +166,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
private bool MatchComplexSegment(TemplateSegment routeSegment,
|
||||
string requestSegment,
|
||||
IDictionary<string, object> defaults,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
RouteValueDictionary values)
|
||||
{
|
||||
Contract.Assert(routeSegment != null);
|
||||
|
|
@ -214,7 +206,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return false;
|
||||
}
|
||||
|
||||
var indexOfLiteral = requestSegment.LastIndexOf(part.Text,
|
||||
var indexOfLiteral = requestSegment.LastIndexOf(part.Text,
|
||||
startIndex,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
if (indexOfLiteral == -1)
|
||||
|
|
@ -237,7 +229,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
newLastIndex = indexOfLiteral;
|
||||
}
|
||||
|
||||
if ((parameterNeedsValue != null) &&
|
||||
if ((parameterNeedsValue != null) &&
|
||||
(((lastLiteral != null) && (part.IsLiteral)) || (indexOfLastSegmentUsed == 0)))
|
||||
{
|
||||
// If we have a pending parameter that needs a value, grab that value
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
private const char QuestionMark = '?';
|
||||
private const char Asterisk = '*';
|
||||
|
||||
public static RouteTemplate Parse(string routeTemplate, IInlineConstraintResolver constraintResolver)
|
||||
public static RouteTemplate Parse(string routeTemplate)
|
||||
{
|
||||
if (routeTemplate == null)
|
||||
{
|
||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, "routeTemplate");
|
||||
}
|
||||
|
||||
var context = new TemplateParserContext(routeTemplate, constraintResolver);
|
||||
var context = new TemplateParserContext(routeTemplate);
|
||||
var segments = new List<TemplateSegment>();
|
||||
|
||||
while (context.Next())
|
||||
|
|
@ -178,8 +178,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
// 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);
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(rawParameter);
|
||||
|
||||
if (templatePart.IsCatchAll && templatePart.IsOptional)
|
||||
{
|
||||
|
|
@ -406,13 +405,12 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
private HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public TemplateParserContext(string template, IInlineConstraintResolver constraintResolver)
|
||||
public TemplateParserContext(string template)
|
||||
{
|
||||
Contract.Assert(template != null);
|
||||
_template = template;
|
||||
|
||||
_index = -1;
|
||||
ConstraintResolver = constraintResolver;
|
||||
}
|
||||
|
||||
public char Current
|
||||
|
|
@ -431,12 +429,6 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
get { return _parameterNames; }
|
||||
}
|
||||
|
||||
public IInlineConstraintResolver ConstraintResolver
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool Back()
|
||||
{
|
||||
return --_index >= 0;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// 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 System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template
|
||||
{
|
||||
|
|
@ -21,7 +23,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
bool isCatchAll,
|
||||
bool isOptional,
|
||||
object defaultValue,
|
||||
IRouteConstraint inlineConstraint)
|
||||
IEnumerable<InlineConstraint> inlineConstraints)
|
||||
{
|
||||
return new TemplatePart()
|
||||
{
|
||||
|
|
@ -30,7 +32,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
IsCatchAll = isCatchAll,
|
||||
IsOptional = isOptional,
|
||||
DefaultValue = defaultValue,
|
||||
InlineConstraint = inlineConstraint,
|
||||
InlineConstraints = inlineConstraints ?? Enumerable.Empty<InlineConstraint>(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
public string Name { get; private set; }
|
||||
public string Text { get; private set; }
|
||||
public object DefaultValue { get; private set; }
|
||||
public IRouteConstraint InlineConstraint { get; private set; }
|
||||
public IEnumerable<InlineConstraint> InlineConstraints { get; private set; }
|
||||
|
||||
internal string DebuggerToString()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
public class TemplateRoute : INamedRouter
|
||||
{
|
||||
private readonly IDictionary<string, object> _defaults;
|
||||
private readonly IDictionary<string, IRouteConstraint> _constraints;
|
||||
private readonly IDictionary<string, object> _dataTokens;
|
||||
private readonly IReadOnlyDictionary<string, IRouteConstraint> _constraints;
|
||||
private readonly IReadOnlyDictionary<string, object> _dataTokens;
|
||||
private readonly IReadOnlyDictionary<string, object> _defaults;
|
||||
private readonly IRouter _target;
|
||||
private readonly RouteTemplate _parsedTemplate;
|
||||
private readonly string _routeTemplate;
|
||||
|
|
@ -25,7 +25,10 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
private ILogger _logger;
|
||||
private ILogger _constraintLogger;
|
||||
|
||||
public TemplateRoute(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver)
|
||||
public TemplateRoute(
|
||||
[NotNull] IRouter target,
|
||||
string routeTemplate,
|
||||
IInlineConstraintResolver inlineConstraintResolver)
|
||||
: this(target,
|
||||
routeTemplate,
|
||||
defaults: null,
|
||||
|
|
@ -56,27 +59,28 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
_target = target;
|
||||
_routeTemplate = routeTemplate ?? string.Empty;
|
||||
Name = routeName;
|
||||
_defaults = defaults ?? new RouteValueDictionary();
|
||||
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate) ??
|
||||
new Dictionary<string, IRouteConstraint>();
|
||||
_dataTokens = dataTokens ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// The parser will throw for invalid routes.
|
||||
_parsedTemplate = TemplateParser.Parse(RouteTemplate, inlineConstraintResolver);
|
||||
UpdateInlineDefaultValuesAndConstraints();
|
||||
_dataTokens = dataTokens == null ? RouteValueDictionary.Empty : new RouteValueDictionary(dataTokens);
|
||||
|
||||
_matcher = new TemplateMatcher(_parsedTemplate);
|
||||
_binder = new TemplateBinder(_parsedTemplate, _defaults);
|
||||
// Data we parse from the template will be used to fill in the rest of the constraints or
|
||||
// defaults. The parser will throw for invalid routes.
|
||||
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
||||
|
||||
_constraints = GetConstraints(inlineConstraintResolver, RouteTemplate, _parsedTemplate, constraints);
|
||||
_defaults = GetDefaults(_parsedTemplate, defaults);
|
||||
|
||||
_matcher = new TemplateMatcher(_parsedTemplate, Defaults);
|
||||
_binder = new TemplateBinder(_parsedTemplate, Defaults);
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public IDictionary<string, object> Defaults
|
||||
public IReadOnlyDictionary<string, object> Defaults
|
||||
{
|
||||
get { return _defaults; }
|
||||
}
|
||||
|
||||
public IDictionary<string, object> DataTokens
|
||||
public IReadOnlyDictionary<string, object> DataTokens
|
||||
{
|
||||
get { return _dataTokens; }
|
||||
}
|
||||
|
|
@ -86,7 +90,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
get { return _routeTemplate; }
|
||||
}
|
||||
|
||||
public IDictionary<string, IRouteConstraint> Constraints
|
||||
public IReadOnlyDictionary<string, IRouteConstraint> Constraints
|
||||
{
|
||||
get { return _constraints; }
|
||||
}
|
||||
|
|
@ -103,7 +107,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
requestPath = requestPath.Substring(1);
|
||||
}
|
||||
|
||||
var values = _matcher.Match(requestPath, Defaults);
|
||||
var values = _matcher.Match(requestPath);
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
|
|
@ -252,38 +256,59 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
};
|
||||
}
|
||||
|
||||
private void UpdateInlineDefaultValuesAndConstraints()
|
||||
private static IReadOnlyDictionary<string, IRouteConstraint> GetConstraints(
|
||||
IInlineConstraintResolver inlineConstraintResolver,
|
||||
string template,
|
||||
RouteTemplate parsedTemplate,
|
||||
IDictionary<string, object> constraints)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, template);
|
||||
|
||||
if (constraints != null)
|
||||
{
|
||||
foreach (var kvp in constraints)
|
||||
{
|
||||
constraintBuilder.AddConstraint(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
foreach (var inlineConstraint in parameter.InlineConstraints)
|
||||
{
|
||||
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return constraintBuilder.Build();
|
||||
}
|
||||
|
||||
private static RouteValueDictionary GetDefaults(
|
||||
RouteTemplate parsedTemplate,
|
||||
IDictionary<string, object> defaults)
|
||||
{
|
||||
// Do not use RouteValueDictionary.Empty for defaults, it might be modified inside
|
||||
// UpdateInlineDefaultValuesAndConstraints()
|
||||
var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
if (_defaults.ContainsKey(parameter.Name))
|
||||
if (result.ContainsKey(parameter.Name))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources
|
||||
.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(parameter.Name));
|
||||
Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(
|
||||
parameter.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
_defaults[parameter.Name] = parameter.DefaultValue;
|
||||
result.Add(parameter.Name, parameter.DefaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TemplateRouteRouteAsyncValues CreateRouteAsyncValues(
|
||||
|
|
@ -320,6 +345,20 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
}
|
||||
|
||||
// Needed because IDictionary<> is not an IReadOnlyDictionary<>
|
||||
private static void MergeValues(
|
||||
IDictionary<string, object> destination,
|
||||
IReadOnlyDictionary<string, object> values)
|
||||
{
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
// This will replace the original value for the specified key.
|
||||
// Values from the matched route will take preference over previous
|
||||
// data in the route context.
|
||||
destination[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureLoggers(HttpContext context)
|
||||
{
|
||||
if (_logger == null)
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
// 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.
|
||||
|
||||
#if ASPNET50
|
||||
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;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Tests
|
||||
{
|
||||
public class ConstraintsBuilderTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData("EmptyAndNullDictionary")]
|
||||
public void ConstraintBuilderReturnsNull_OnNullOrEmptyInput(IDictionary<string, object> input)
|
||||
{
|
||||
var result = RouteConstraintBuilder.BuildConstraints(input);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("EmptyAndNullDictionary")]
|
||||
public void ConstraintBuilderWithTemplateReturnsNull_OnNullOrEmptyInput(IDictionary<string, object> input)
|
||||
{
|
||||
var result = RouteConstraintBuilder.BuildConstraints(input, "{controller}");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithConstraintsThatIsAStringCreatesARegex()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = "abc" });
|
||||
var constraintDictionary = RouteConstraintBuilder.BuildConstraints(dictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, constraintDictionary.Count);
|
||||
Assert.Equal("controller", constraintDictionary.First().Key);
|
||||
|
||||
var constraint = constraintDictionary["controller"];
|
||||
|
||||
Assert.IsType<RegexRouteConstraint>(constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithConstraintsThatIsCustomConstraint_IsPassThrough()
|
||||
{
|
||||
// Arrange
|
||||
var originalConstraint = new Mock<IRouteConstraint>().Object;
|
||||
|
||||
var dictionary = new RouteValueDictionary(new { controller = originalConstraint });
|
||||
var constraintDictionary = RouteConstraintBuilder.BuildConstraints(dictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, constraintDictionary.Count);
|
||||
Assert.Equal("controller", constraintDictionary.First().Key);
|
||||
|
||||
var constraint = constraintDictionary["controller"];
|
||||
|
||||
Assert.Equal(originalConstraint, constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithConstraintsThatIsNotStringOrCustomConstraint_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = new RouteValueDictionary() });
|
||||
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => RouteConstraintBuilder.BuildConstraints(dictionary),
|
||||
"The constraint entry 'controller' must have a string value or be of a type which implements '" +
|
||||
typeof(IRouteConstraint) + "'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RouteTemplateGetRouteDataWithConstraintsThatIsNotStringOrCustomConstraint_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = new RouteValueDictionary() });
|
||||
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => RouteConstraintBuilder.BuildConstraints(dictionary, "{controller}/{action}"),
|
||||
"The constraint entry 'controller' on the route with route template " +
|
||||
"'{controller}/{action}' must have a string value or be of a type which implements '" +
|
||||
typeof(IRouteConstraint) + "'.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("abc", "abc", true)] // simple case
|
||||
[InlineData("abc", "bbb|abc", true)] // Regex or
|
||||
[InlineData("Abc", "abc", true)] // Case insensitive
|
||||
[InlineData("Abc ", "abc", false)] // Matches whole (but no trimming)
|
||||
[InlineData("Abcd", "abc", false)] // Matches whole (additional non whitespace char)
|
||||
[InlineData("Abc", " abc", false)] // Matches whole (less one char)
|
||||
public void StringConstraintsMatchingScenarios(string routeValue,
|
||||
string constraintValue,
|
||||
bool shouldMatch)
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = routeValue });
|
||||
|
||||
var constraintDictionary = RouteConstraintBuilder.BuildConstraints(
|
||||
new RouteValueDictionary(new { controller = constraintValue }));
|
||||
var constraint = constraintDictionary["controller"];
|
||||
|
||||
Assert.Equal(shouldMatch,
|
||||
constraint.Match(
|
||||
httpContext: new Mock<HttpContext>().Object,
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeKey: "controller",
|
||||
values: dictionary,
|
||||
routeDirection: RouteDirection.IncomingRequest));
|
||||
}
|
||||
|
||||
public static IEnumerable<object> EmptyAndNullDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new Object[]
|
||||
{
|
||||
null,
|
||||
},
|
||||
|
||||
new Object[]
|
||||
{
|
||||
new Dictionary<string, object>(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Constraints;
|
||||
using Microsoft.AspNet.Routing.Template;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||
|
|
@ -25,7 +24,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("111111", templatePart.DefaultValue);
|
||||
Assert.IsType<IntRouteConstraint>(templatePart.InlineConstraint);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == "int");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -37,8 +38,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("111111", templatePart.DefaultValue);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"\d+", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\d+)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -50,7 +52,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
Assert.IsType<IntRouteConstraint>(templatePart.InlineConstraint);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"int");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -62,7 +66,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
Assert.Equal(@"\d+", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\d+)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -73,10 +79,11 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.IsType<CompositeRouteConstraint>(templatePart.InlineConstraint);
|
||||
var constraint = (CompositeRouteConstraint)templatePart.InlineConstraint;
|
||||
Assert.Equal(@"\d+", ((TestRouteConstraint)constraint.Constraints.ElementAt(0)).Pattern);
|
||||
Assert.Equal(@"\w+", ((TestRouteConstraint)constraint.Constraints.ElementAt(1)).Pattern);
|
||||
|
||||
Assert.Equal(2, templatePart.InlineConstraints.Count());
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\d+)");
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\w+)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -92,10 +99,10 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
Assert.Equal("p1", param1.Name);
|
||||
Assert.Equal("hello", param1.DefaultValue);
|
||||
Assert.False(param1.IsOptional);
|
||||
Assert.IsType<CompositeRouteConstraint>(param1.InlineConstraint);
|
||||
var constraint = (CompositeRouteConstraint)param1.InlineConstraint;
|
||||
Assert.IsType<IntRouteConstraint>(constraint.Constraints.ElementAt(0));
|
||||
Assert.IsType<TestRouteConstraint>(constraint.Constraints.ElementAt(1));
|
||||
|
||||
Assert.Equal(2, param1.InlineConstraints.Count());
|
||||
Assert.Single(param1.InlineConstraints, c => c.Constraint == "int");
|
||||
Assert.Single(param1.InlineConstraints, c => c.Constraint == "test(3)");
|
||||
|
||||
var param2 = parameters[1];
|
||||
Assert.Equal("p2", param2.Name);
|
||||
|
|
@ -136,8 +143,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"\}", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\})");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -148,8 +156,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"\)", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\))");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -160,8 +169,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@":", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(:)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -172,8 +182,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"\w,\w", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\w,\w)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -184,27 +195,10 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
var constraint = Assert.IsType<IntRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal("", templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(",")]
|
||||
[InlineData("(")]
|
||||
[InlineData(")")]
|
||||
[InlineData("}")]
|
||||
[InlineData("{")]
|
||||
public void ParseRouteParameter_MisplacedSpecialCharacterInParameter_Throws(string character)
|
||||
{
|
||||
// Arrange
|
||||
var unresolvedConstraint = character + @"test(\w,\w)";
|
||||
var parameter = "param:" + unresolvedConstraint;
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => ParseParameter(parameter));
|
||||
Assert.Equal(@"The inline constraint resolver of type 'DefaultInlineConstraintResolver'"+
|
||||
" was unable to resolve the following inline constraint: '"+ unresolvedConstraint + "'.",
|
||||
ex.Message);
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"int");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -216,8 +210,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"=", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(=)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -228,8 +223,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"\{", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\{)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -240,8 +236,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"\(", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\()");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -254,8 +251,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
|
||||
Assert.Equal(@"\?", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\?)");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -275,7 +273,7 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Assert
|
||||
Assert.Equal(expectedParameterName, templatePart.Name);
|
||||
Assert.Null(templatePart.InlineConstraint);
|
||||
Assert.Empty(templatePart.InlineConstraints);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
|
|
@ -283,14 +281,14 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
private TemplatePart ParseParameter(string routeParameter)
|
||||
{
|
||||
var _constraintResolver = GetConstraintResolver();
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter, _constraintResolver);
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
||||
return templatePart;
|
||||
}
|
||||
|
||||
private static RouteTemplate ParseRouteTemplate(string template)
|
||||
{
|
||||
var _constraintResolver = GetConstraintResolver();
|
||||
return TemplateParser.Parse(template, _constraintResolver);
|
||||
return TemplateParser.Parse(template);
|
||||
}
|
||||
|
||||
private static IInlineConstraintResolver GetConstraintResolver()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
// 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.
|
||||
|
||||
#if ASPNET50
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Constraints;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
{
|
||||
public class RouteConstraintBuilderTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddConstraint_String_CreatesARegex()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateBuilder("{controller}/{action}");
|
||||
builder.AddConstraint("controller", "abc");
|
||||
|
||||
// Act
|
||||
var result = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result.Count);
|
||||
Assert.Equal("controller", result.First().Key);
|
||||
|
||||
Assert.IsType<RegexRouteConstraint>(Assert.Single(result).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConstraint_IRouteConstraint()
|
||||
{
|
||||
// Arrange
|
||||
var originalConstraint = Mock.Of<IRouteConstraint>();
|
||||
|
||||
var builder = CreateBuilder("{controller}/{action}");
|
||||
builder.AddConstraint("controller", originalConstraint);
|
||||
|
||||
// Act
|
||||
var result = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result.Count);
|
||||
|
||||
var kvp = Assert.Single(result);
|
||||
Assert.Equal("controller", kvp.Key);
|
||||
|
||||
Assert.Same(originalConstraint, kvp.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddResolvedConstraint_IRouteConstraint()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateBuilder("{controller}/{action}");
|
||||
builder.AddResolvedConstraint("controller", "int");
|
||||
|
||||
// Act
|
||||
var result = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result.Count);
|
||||
|
||||
var kvp = Assert.Single(result);
|
||||
Assert.Equal("controller", kvp.Key);
|
||||
|
||||
Assert.IsType<IntRouteConstraint>(kvp.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConstraint_InvalidType_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateBuilder("{controller}/{action}");
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => builder.AddConstraint("controller", 5),
|
||||
"The constraint entry 'controller' - '5' on the route " +
|
||||
"'{controller}/{action}' must have a string value or be of a type which implements '" +
|
||||
typeof(IRouteConstraint) + "'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddResolvedConstraint_NotFound_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var unresolvedConstraint = @"test";
|
||||
|
||||
var builder = CreateBuilder("{controller}/{action}");
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => builder.AddResolvedConstraint("controller", unresolvedConstraint),
|
||||
@"The constraint entry 'controller' - '" + unresolvedConstraint + "' on the route " +
|
||||
"'{controller}/{action}' could not be resolved by the constraint resolver " +
|
||||
"of type 'DefaultInlineConstraintResolver'.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("abc", "abc", true)] // simple case
|
||||
[InlineData("abc", "bbb|abc", true)] // Regex or
|
||||
[InlineData("Abc", "abc", true)] // Case insensitive
|
||||
[InlineData("Abc ", "abc", false)] // Matches whole (but no trimming)
|
||||
[InlineData("Abcd", "abc", false)] // Matches whole (additional non whitespace char)
|
||||
[InlineData("Abc", " abc", false)] // Matches whole (less one char)
|
||||
public void StringConstraintsMatchingScenarios(string routeValue,
|
||||
string constraintValue,
|
||||
bool shouldMatch)
|
||||
{
|
||||
// Arrange
|
||||
var routeValues = new RouteValueDictionary(new { controller = routeValue });
|
||||
|
||||
var builder = CreateBuilder("{controller}/{action}");
|
||||
builder.AddConstraint("controller", constraintValue);
|
||||
|
||||
var constraint = Assert.Single(builder.Build()).Value;
|
||||
|
||||
Assert.Equal(shouldMatch,
|
||||
constraint.Match(
|
||||
httpContext: new Mock<HttpContext>().Object,
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeKey: "controller",
|
||||
values: routeValues,
|
||||
routeDirection: RouteDirection.IncomingRequest));
|
||||
}
|
||||
|
||||
private static RouteConstraintBuilder CreateBuilder(string template)
|
||||
{
|
||||
var services = new Mock<IServiceProvider>(MockBehavior.Strict);
|
||||
|
||||
var options = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
|
||||
options
|
||||
.SetupGet(o => o.Options)
|
||||
.Returns(new RouteOptions());
|
||||
|
||||
var inlineConstraintResolver = new DefaultInlineConstraintResolver(services.Object, options.Object);
|
||||
return new RouteConstraintBuilder(inlineConstraintResolver, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -129,13 +129,14 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
[MemberData("EmptyAndNullDefaultValues")]
|
||||
public void Binding_WithEmptyAndNull_DefaultValues(
|
||||
string template,
|
||||
IDictionary<string, object> defaults,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
IDictionary<string, object> values,
|
||||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TemplateBinder(TemplateParser.Parse(template, _inlineConstraintResolver),
|
||||
defaults);
|
||||
var binder = new TemplateBinder(
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
|
||||
// Act & Assert
|
||||
var result = binder.GetValues(ambientValues: null, values: values);
|
||||
|
|
@ -962,13 +963,13 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
private static void RunTest(
|
||||
string template,
|
||||
IDictionary<string, object> defaults,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
IDictionary<string, object> ambientValues,
|
||||
IDictionary<string, object> values,
|
||||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TemplateBinder(TemplateParser.Parse(template, _inlineConstraintResolver), defaults);
|
||||
var binder = new TemplateBinder(TemplateParser.Parse(template), defaults);
|
||||
|
||||
// Act & Assert
|
||||
var result = binder.GetValues(ambientValues, values);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
|
||||
// Act
|
||||
var match = matcher.Match("Bank/DoAction/123", null);
|
||||
var match = matcher.Match("Bank/DoAction/123");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
|
||||
// Act
|
||||
var match = matcher.Match("Bank/DoAction", null);
|
||||
var match = matcher.Match("Bank/DoAction");
|
||||
|
||||
// Assert
|
||||
Assert.Null(match);
|
||||
|
|
@ -49,10 +49,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void MatchSingleRouteWithDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("Bank/DoAction", new RouteValueDictionary(new { id = "default id" }));
|
||||
var rd = matcher.Match("Bank/DoAction");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Bank", rd["controller"]);
|
||||
|
|
@ -64,10 +64,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void NoMatchSingleRouteWithDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("Bank", new RouteValueDictionary(new { id = "default id" }));
|
||||
var rd = matcher.Match("Bank");
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
|
|
@ -77,10 +77,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void MatchRouteWithLiterals()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/{p1}/bar/{p2}");
|
||||
var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/111/bar/222", new RouteValueDictionary(new { p2 = "default p2" }));
|
||||
var rd = matcher.Match("moo/111/bar/222");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("111", rd["p1"]);
|
||||
|
|
@ -91,10 +91,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void MatchRouteWithLiteralsAndDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/{p1}/bar/{p2}");
|
||||
var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/111/bar/", new RouteValueDictionary(new { p2 = "default p2" }));
|
||||
var rd = matcher.Match("moo/111/bar/");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("111", rd["p1"]);
|
||||
|
|
@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("moo/bar");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
var rd = matcher.Match("moo/bar");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("moo/bars");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
var rd = matcher.Match("moo/bar");
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
|
|
@ -135,7 +135,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("moo/bar");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar/", null);
|
||||
var rd = matcher.Match("moo/bar/");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -149,7 +149,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("moo/bar/");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
var rd = matcher.Match("moo/bar");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{p1}/{p2}/");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
var rd = matcher.Match("moo/bar");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -178,7 +178,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{p1}/{p2}/baz");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar/boo", null);
|
||||
var rd = matcher.Match("moo/bar/boo");
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
|
|
@ -191,7 +191,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{p1}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
var rd = matcher.Match("moo/bar");
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
|
|
@ -204,7 +204,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("DEFAULT.ASPX");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("default.aspx", null);
|
||||
var rd = matcher.Match("default.aspx");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -224,7 +224,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher(template);
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match(path, null);
|
||||
var rd = matcher.Match(path);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -234,10 +234,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void MatchRouteWithExtraDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{p2}");
|
||||
var matcher = CreateMatcher("{p1}/{p2}", new { p2 = (string)null, foo = "bar" });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1", new RouteValueDictionary(new { p2 = (string)null, foo = "bar" }));
|
||||
var rd = matcher.Match("v1");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -251,10 +251,12 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void MatchPrettyRouteWithExtraDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("date/{y}/{m}/{d}");
|
||||
var matcher = CreateMatcher(
|
||||
"date/{y}/{m}/{d}",
|
||||
new { controller = "blog", action = "showpost", m = (string)null, d = (string)null });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("date/2007/08", new RouteValueDictionary(new { controller = "blog", action = "showpost", m = (string)null, d = (string)null }));
|
||||
var rd = matcher.Match("date/2007/08");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -540,7 +542,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1/v2/v3", null);
|
||||
var rd = matcher.Match("v1/v2/v3");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -556,7 +558,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1/", null);
|
||||
var rd = matcher.Match("v1/");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -572,7 +574,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1", null);
|
||||
var rd = matcher.Match("v1");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -585,10 +587,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void RouteWithCatchAllClauseUsesDefaultValueForEmptyContent()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1", new RouteValueDictionary(new { p2 = "catchall" }));
|
||||
var rd = matcher.Match("v1");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -601,10 +603,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void RouteWithCatchAllClauseIgnoresDefaultValueForNonEmptyContent()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1/hello/whatever", new RouteValueDictionary(new { p2 = "catchall" }));
|
||||
var rd = matcher.Match("v1/hello/whatever");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
|
|
@ -715,7 +717,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var url = "Home/Index";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
var match = route.Match(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
|
|
@ -732,7 +734,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var url = "Home";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
var match = route.Match(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
|
|
@ -749,7 +751,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var url = "";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
var match = route.Match(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
|
|
@ -765,7 +767,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var url = "";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
var match = route.Match(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
|
|
@ -780,7 +782,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var url = "Home/Index";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
var match = route.Match(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
|
|
@ -790,18 +792,24 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
Assert.False(match.ContainsKey("id"));
|
||||
}
|
||||
|
||||
private TemplateMatcher CreateMatcher(string template)
|
||||
private TemplateMatcher CreateMatcher(string template, object defaults = null)
|
||||
{
|
||||
return new TemplateMatcher(TemplateParser.Parse(template, _inlineConstraintResolver));
|
||||
return new TemplateMatcher(
|
||||
TemplateParser.Parse(template),
|
||||
new RouteValueDictionary(defaults));
|
||||
}
|
||||
|
||||
private static void RunTest(string template, string path, IDictionary<string, object> defaults, IDictionary<string, object> expected)
|
||||
private static void RunTest(
|
||||
string template,
|
||||
string path,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
IDictionary<string, object> expected)
|
||||
{
|
||||
// Arrange
|
||||
var matcher = new TemplateMatcher(TemplateParser.Parse(template, _inlineConstraintResolver));
|
||||
var matcher = new TemplateMatcher(TemplateParser.Parse(template), defaults);
|
||||
|
||||
// Act
|
||||
var match = matcher.Match(path, defaults);
|
||||
var match = matcher.Match(path);
|
||||
|
||||
// Assert
|
||||
if (expected == null)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
public class TemplateRouteParserTests
|
||||
{
|
||||
private IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
|
||||
|
||||
[Fact]
|
||||
public void Parse_SingleLiteral()
|
||||
{
|
||||
|
|
@ -28,7 +26,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -42,11 +40,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraint: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -60,11 +58,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraint: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -85,7 +83,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
expected.Segments[2].Parts.Add(TemplatePart.CreateLiteral("super"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -104,7 +102,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
|
|
@ -112,7 +110,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
|
|
@ -120,11 +118,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
true,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[2].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -143,11 +141,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -165,12 +163,12 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -188,18 +186,18 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: 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,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -218,12 +216,12 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraint: null));
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template, _inlineConstraintResolver);
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
|
|
@ -233,7 +231,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_WithRepeatedParameter()
|
||||
{
|
||||
var ex = ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
|
||||
"The route parameter name 'controller' appears more than one time in the route template." + Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +245,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_WithMismatchedBraces(string template)
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template, _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse(template),
|
||||
@"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -256,7 +254,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("123{a}abc{*moo}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("123{a}abc{*moo}"),
|
||||
"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");
|
||||
}
|
||||
|
|
@ -265,7 +263,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{*p1}/{*p2}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{*p1}/{*p2}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -274,7 +272,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{*p1}abc{*p2}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{*p1}abc{*p2}"),
|
||||
"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");
|
||||
}
|
||||
|
|
@ -283,7 +281,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotHaveCatchAllWithNoName()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{*}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("foo/{*}"),
|
||||
"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 occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
|
|
@ -308,7 +306,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
// Act & Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template, _inlineConstraintResolver), expectedMessage + Environment.NewLine +
|
||||
() => TemplateParser.Parse(template), expectedMessage + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -316,7 +314,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{{p1}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("foo/{{p1}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -325,7 +323,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{p1}}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("foo/{p1}}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -334,7 +332,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_SameParameterTwiceThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{AAA}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{aaa}/{AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -343,7 +341,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{*AAA}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{aaa}/{*AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -352,7 +350,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{aa}a}/{z}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{a}/{aa}a}/{z}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -361,7 +359,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{a{aa}/{z}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{a}/{a{aa}/{z}"),
|
||||
"The route parameter name 'a{aa' is invalid. Route parameter names must be non-empty and cannot" +
|
||||
" contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
|
||||
" can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
|
|
@ -373,7 +371,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{}/{z}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{a}/{}/{z}"),
|
||||
"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 occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
|
|
@ -385,7 +383,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{?}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{?}"),
|
||||
"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 occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
|
|
@ -397,7 +395,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}//{z}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{a}//{z}"),
|
||||
"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");
|
||||
}
|
||||
|
|
@ -406,7 +404,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -415,7 +413,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_RepeatedParametersThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/aa{p1}{p2}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("foo/aa{p1}{p2}"),
|
||||
"A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -424,7 +422,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotStartWithSlash()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("/foo", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("/foo"),
|
||||
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -433,7 +431,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotStartWithTilde()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("~foo", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("~foo"),
|
||||
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -442,7 +440,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CannotContainQuestionMark()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foor?bar", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("foor?bar"),
|
||||
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -451,7 +449,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{foor?b}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{foor?b}"),
|
||||
"The route parameter name 'foor?b' is invalid. Route parameter names must be non-empty and cannot" +
|
||||
" contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
|
||||
" can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
|
|
@ -463,7 +461,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_MultiSegmentParameterCannotContainOptionalParameter()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{foorb?}-bar-{z}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{foorb?}-bar-{z}"),
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -472,19 +470,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
public void InvalidTemplate_CatchAllMarkedOptional()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{*b?}", _inlineConstraintResolver),
|
||||
() => TemplateParser.Parse("{a}/{*b?}"),
|
||||
"A catch-all parameter cannot be marked optional." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
private static IInlineConstraintResolver GetInlineConstraintResolver()
|
||||
{
|
||||
var services = new ServiceCollection { OptionsServices.GetDefaultServices() };
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
|
||||
return new DefaultInlineConstraintResolver(serviceProvider, accessor);
|
||||
}
|
||||
|
||||
private class TemplateEqualityComparer : IEqualityComparer<RouteTemplate>
|
||||
{
|
||||
public bool Equals(RouteTemplate x, RouteTemplate y)
|
||||
|
|
|
|||
|
|
@ -515,6 +515,29 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.Equal("Index", context.RouteData.Values["action"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_Success_CopiesDataTokens()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute(
|
||||
"{controller}/{action}",
|
||||
defaults: new { action = "Index" },
|
||||
dataTokens: new { culture = "en-CA" });
|
||||
|
||||
var context = CreateRouteContext("/Home");
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
Assert.True(context.IsHandled);
|
||||
|
||||
// This should not affect the route - RouteData.DataTokens is a copy
|
||||
context.RouteData.DataTokens.Add("company", "contoso");
|
||||
|
||||
// Assert
|
||||
Assert.Single(route.DataTokens);
|
||||
Assert.Single(route.DataTokens, kvp => kvp.Key == "culture" && ((string)kvp.Value) == "en-CA");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_Fails()
|
||||
{
|
||||
|
|
@ -1060,11 +1083,12 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
var routeBuilder = CreateRouteBuilder();
|
||||
|
||||
// Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(() => routeBuilder.MapRoute("mockName",
|
||||
"{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "a.*", action = new Object() }),
|
||||
"The constraint entry 'action' on the route with route template '{controller}/{action}' " +
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => routeBuilder.MapRoute("mockName",
|
||||
"{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "a.*", action = 17 }),
|
||||
"The constraint entry 'action' - '17' on the route '{controller}/{action}' " +
|
||||
"must have a string value or be of a type which implements '" +
|
||||
typeof(IRouteConstraint) + "'.");
|
||||
}
|
||||
|
|
@ -1195,7 +1219,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
(constraints as IDictionary<string, object>) ??
|
||||
new RouteValueDictionary(constraints),
|
||||
(dataTokens as IDictionary<string, object>) ??
|
||||
new RouteValueDictionary(constraints),
|
||||
new RouteValueDictionary(dataTokens),
|
||||
_inlineConstraintResolver);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue