diff --git a/src/Microsoft.AspNet.Routing/Constraints/CompoundRouteConstraint.cs b/src/Microsoft.AspNet.Routing/Constraints/CompoundRouteConstraint.cs
new file mode 100644
index 0000000000..c03a2854b3
--- /dev/null
+++ b/src/Microsoft.AspNet.Routing/Constraints/CompoundRouteConstraint.cs
@@ -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
+{
+ ///
+ /// Constrains a route by several child constraints.
+ ///
+ public class CompoundRouteConstraint : IRouteConstraint
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The child constraints that must match for this constraint to match.
+ public CompoundRouteConstraint([NotNull] IEnumerable constraints)
+ {
+ Constraints = constraints;
+ }
+
+ ///
+ /// Gets the child constraints that must match for this constraint to match.
+ ///
+ public IEnumerable Constraints { get; private set; }
+
+ ///
+ public bool Match([NotNull] HttpContext httpContext,
+ [NotNull] IRouter route,
+ [NotNull] string routeKey,
+ [NotNull] IDictionary values,
+ RouteDirection routeDirection)
+ {
+ foreach (var constraint in Constraints)
+ {
+ if (!constraint.Match(httpContext, route, routeKey, values, routeDirection))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Routing/InlineRouteTemplateParser.cs b/src/Microsoft.AspNet.Routing/InlineRouteTemplateParser.cs
new file mode 100644
index 0000000000..6220202542
--- /dev/null
+++ b/src/Microsoft.AspNet.Routing/InlineRouteTemplateParser.cs
@@ -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 = @"(?.+?)";
+
+ // 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 = @"(:(?.*?(\(.*?\))?))*";
+
+ // A default value with an equal sign followed by zero or more characters
+ // Matches "=", "=abc"
+ private const string DefaultValueRegex = @"(?(=.*?))?";
+
+ 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();
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs b/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs
index be6dc62012..ab5f36252e 100644
--- a/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs
+++ b/src/Microsoft.AspNet.Routing/Template/TemplateParser.cs
@@ -173,7 +173,7 @@ namespace Microsoft.AspNet.Routing.Template
}
var rawParameter = context.Capture();
-
+
// At this point, we need to parse the raw name for inline constraint,
// default values and optional parameters.
var templatePart = InlineRouteParameterParser.ParseRouteParameter(rawParameter,
@@ -196,6 +196,10 @@ namespace Microsoft.AspNet.Routing.Template
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
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;
if (IsValidParameterName(context, parameterName))