Skip to content
This repository was archived by the owner on Nov 27, 2018. It is now read-only.

Commit

Permalink
Issue #92 - Part 2 - Make properties on TemplateRoute readonly
Browse files Browse the repository at this point in the history
This change is 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.
  • Loading branch information
rynowak committed Nov 13, 2014
1 parent 7d3db22 commit c9a9924
Show file tree
Hide file tree
Showing 17 changed files with 413 additions and 401 deletions.
37 changes: 10 additions & 27 deletions src/Microsoft.AspNet.Routing/InlineRouteParameterParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public static class InlineRouteParameterParser
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);
Expand All @@ -43,7 +43,7 @@ public static TemplatePart ParseRouteParameter([NotNull] string routeParameter,
isCatchAll: isCatchAll,
isOptional: isOptional,
defaultValue: null,
inlineConstraint: null);
inlineConstraints: null);
}

var parameterName = parameterMatch.Groups["parameterName"].Value;
Expand All @@ -54,13 +54,13 @@ public static TemplatePart ParseRouteParameter([NotNull] string routeParameter,

// 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)
Expand All @@ -77,33 +77,16 @@ private static string GetDefaultValue(Group defaultValueGroup)
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));
}

parameterConstraints.Add(constraint);
}
var constraints = new List<InlineConstraint>();

if (parameterConstraints.Count > 0)
foreach (Capture capture in constraintGroup.Captures)
{
var constraint = parameterConstraints.Count == 1 ?
parameterConstraints[0] :
new CompositeRouteConstraint(parameterConstraints);
return constraint;
constraints.Add(new InlineConstraint(capture.Value));
}

return null;
return constraints;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public string Name
/// <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.
Expand Down
40 changes: 12 additions & 28 deletions src/Microsoft.AspNet.Routing/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 4 additions & 7 deletions src/Microsoft.AspNet.Routing/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
108 changes: 69 additions & 39 deletions src/Microsoft.AspNet.Routing/RouteConstraintBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,97 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Routing.Template;

namespace Microsoft.AspNet.Routing
{
public class RouteConstraintBuilder
{
public static IDictionary<string, IRouteConstraint>
BuildConstraints(IDictionary<string, object> inputConstraints)
{
return BuildConstraintsCore(inputConstraints, routeTemplate: null);
}
private readonly IInlineConstraintResolver _inlineConstraintResolver;
private readonly string _template;

public static IDictionary<string, IRouteConstraint>
BuildConstraints(IDictionary<string, object> inputConstraints, [NotNull] string routeTemplate)
private readonly Dictionary<string, List<IRouteConstraint>> _constraints;

public RouteConstraintBuilder(
[NotNull] IInlineConstraintResolver inlineConstraintResolver,
[NotNull] string template)
{
return BuildConstraintsCore(inputConstraints, routeTemplate);
_inlineConstraintResolver = inlineConstraintResolver;
_template = template;

_constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
}

private static IDictionary<string, IRouteConstraint>
BuildConstraintsCore(IDictionary<string, object> inputConstraints, string routeTemplate)
public IReadOnlyDictionary<string, IRouteConstraint> Build()
{
if (inputConstraints == null || inputConstraints.Count == 0)
var constraints = new Dictionary<string, IRouteConstraint>(StringComparer.OrdinalIgnoreCase);
foreach (var kvp in _constraints)
{
return null;
IRouteConstraint constraint;
if (kvp.Value.Count == 1)
{
constraint = kvp.Value[0];
}
else
{
constraint = new CompositeRouteConstraint(kvp.Value.ToArray());
}

constraints.Add(kvp.Key, constraint);
}

var constraints = new Dictionary<string, IRouteConstraint>(inputConstraints.Count,
StringComparer.OrdinalIgnoreCase);
return constraints;
}

foreach (var kvp in inputConstraints)
public void AddConstraint([NotNull] string key, [NotNull] object value)
{
var constraint = value as IRouteConstraint;
if (constraint == null)
{
var constraint = kvp.Value as IRouteConstraint;

if (constraint == null)
var regexPattern = value as string;
if (regexPattern == null)
{
var regexPattern = kvp.Value as string;
throw new InvalidOperationException(
Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
key,
value,
_template,
typeof(IRouteConstraint)));
}

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

var constraintsRegEx = "^(" + regexPattern + ")$";
Add(key, constraint);
}

constraint = new RegexRouteConstraint(constraintsRegEx);
}
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,
_template,
_inlineConstraintResolver.GetType().Name));
}

constraints.Add(kvp.Key, constraint);
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);
}

return constraints;
list.Add(constraint);
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.AspNet.Routing/RouteConstraintMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions src/Microsoft.AspNet.Routing/Template/InlineConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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
{
public class InlineConstraint
{
public InlineConstraint([NotNull] string constraint)
{
Constraint = constraint;
}

public string Constraint { get; private set; }
}
}
Loading

0 comments on commit c9a9924

Please sign in to comment.