Support for InlineConstraints in Route Template
This commit is contained in:
parent
187d8e4911
commit
9885830200
|
|
@ -0,0 +1,46 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constrains a route by several child constraints.
|
||||||
|
/// </summary>
|
||||||
|
public class CompoundRouteConstraint : IRouteConstraint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CompoundRouteConstraint" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="constraints">The child constraints that must match for this constraint to match.</param>
|
||||||
|
public CompoundRouteConstraint([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; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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,100 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Routing
|
||||||
|
{
|
||||||
|
internal static class InlineRouteParameterParser
|
||||||
|
{
|
||||||
|
// One or more characters, matches "id"
|
||||||
|
private const string ParameterNameRegex = @"(?<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 ConstraintRegex = @"(:(?<constraint>.*?(\(.*?\))?))*";
|
||||||
|
|
||||||
|
// A default value with an equal sign followed by zero or more characters
|
||||||
|
// Matches "=", "=abc"
|
||||||
|
private const string DefaultValueRegex = @"(?<defaultValue>(=.*?))?";
|
||||||
|
|
||||||
|
private static readonly Regex _parameterRegex = new Regex(
|
||||||
|
"^" + ParameterNameRegex + ConstraintRegex + DefaultValueRegex + "$",
|
||||||
|
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);
|
||||||
|
string 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 object GetDefaultValue(Group defaultValueGroup)
|
||||||
|
{
|
||||||
|
if (defaultValueGroup.Success)
|
||||||
|
{
|
||||||
|
string 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)
|
||||||
|
{
|
||||||
|
string 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 CompoundRouteConstraint(parameterConstraints);
|
||||||
|
return constraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -173,7 +173,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawParameter = context.Capture();
|
var rawParameter = context.Capture();
|
||||||
|
|
||||||
// At this point, we need to parse the raw name for inline constraint,
|
// At this point, we need to parse the raw name for inline constraint,
|
||||||
// default values and optional parameters.
|
// default values and optional parameters.
|
||||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(rawParameter,
|
var templatePart = InlineRouteParameterParser.ParseRouteParameter(rawParameter,
|
||||||
|
|
@ -196,6 +196,10 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
|
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// A workaround is to add it as a separate entry in the defaults argument.
|
||||||
|
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var parameterName = templatePart.Name;
|
var parameterName = templatePart.Name;
|
||||||
if (IsValidParameterName(context, parameterName))
|
if (IsValidParameterName(context, parameterName))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue