diff --git a/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml b/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml
index f3e73a79b2..3441b3561c 100644
--- a/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml
+++ b/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml
@@ -15,7 +15,11 @@
{
@parameter.Name - @(parameter?.Type?.FullName ?? "Unknown") - @parameter.Source.ToString()
- - Constraint: @(parameter?.Constraint?.GetType()?.Name?.Replace("RouteConstraint", "") ?? " none")
+ @if (parameter.Constraints != null && parameter.Constraints.Any())
+ {
+ var constraints = parameter.Constraints;
+ Write("-Constraint:" + string.Join(", ", constraints.Select(c => c.GetType()?.Name?.Replace("RouteConstraint", ""))));
+ }
- Default value: @(parameter?.DefaultValue ?? " none")
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs
index 80a85b576a..6a037efe0b 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs
@@ -2,6 +2,7 @@
// 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 Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
@@ -19,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Description
public ApiParameterSource Source { get; set; }
- public IRouteConstraint Constraint { get; set; }
+ public IEnumerable Constraints { get; set; }
public object DefaultValue { get; set; }
diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs
index febdaf2e29..28e176e850 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs
@@ -180,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.Description
if (action.AttributeRouteInfo != null &&
action.AttributeRouteInfo.Template != null)
{
- return TemplateParser.Parse(action.AttributeRouteInfo.Template, _constraintResolver);
+ return TemplateParser.Parse(action.AttributeRouteInfo.Template);
}
return null;
@@ -281,7 +281,7 @@ namespace Microsoft.AspNet.Mvc.Description
return resourceParameter;
}
- private static ApiParameterDescription CreateParameterFromTemplateAndParameterDescriptor(
+ private ApiParameterDescription CreateParameterFromTemplateAndParameterDescriptor(
TemplatePart templateParameter,
ParameterDescriptor parameter)
{
@@ -291,7 +291,7 @@ namespace Microsoft.AspNet.Mvc.Description
IsOptional = parameter.IsOptional && IsOptionalParameter(templateParameter),
Name = parameter.Name,
ParameterDescriptor = parameter,
- Constraint = templateParameter.InlineConstraint,
+ Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints),
DefaultValue = templateParameter.DefaultValue,
Type = parameter.ParameterType,
};
@@ -299,12 +299,23 @@ namespace Microsoft.AspNet.Mvc.Description
return resourceParameter;
}
+ private static IEnumerable GetConstraints(
+ IInlineConstraintResolver constraintResolver,
+ IEnumerable constraints)
+ {
+ return
+ constraints
+ .Select(c => constraintResolver.ResolveConstraint(c.Constraint))
+ .Where(c => c != null)
+ .ToArray();
+ }
+
private static bool IsOptionalParameter(TemplatePart templateParameter)
{
return templateParameter.IsOptional || templateParameter.DefaultValue != null;
}
- private static ApiParameterDescription CreateParameterFromTemplate(TemplatePart templateParameter)
+ private ApiParameterDescription CreateParameterFromTemplate(TemplatePart templateParameter)
{
return new ApiParameterDescription
{
@@ -312,7 +323,7 @@ namespace Microsoft.AspNet.Mvc.Description
IsOptional = IsOptionalParameter(templateParameter),
Name = templateParameter.Name,
ParameterDescriptor = null,
- Constraint = templateParameter.InlineConstraint,
+ Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints),
DefaultValue = templateParameter.DefaultValue,
};
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteLinkGenerationEntry.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteLinkGenerationEntry.cs
index 8510f8e310..4c3b5a9626 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteLinkGenerationEntry.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteLinkGenerationEntry.cs
@@ -21,12 +21,12 @@ namespace Microsoft.AspNet.Mvc.Routing
///
/// The route constraints.
///
- public IDictionary Constraints { get; set; }
+ public IReadOnlyDictionary Constraints { get; set; }
///
/// The route defaults.
///
- public IDictionary Defaults { get; set; }
+ public IReadOnlyDictionary Defaults { get; set; }
///
/// The order of the template.
diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoutePrecedence.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoutePrecedence.cs
index 3f4c6dff60..1f7b7e49d5 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoutePrecedence.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoutePrecedence.cs
@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
+using System.Linq;
using Microsoft.AspNet.Routing.Template;
namespace Microsoft.AspNet.Mvc.Routing
@@ -59,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.Routing
// If there is a route constraint for the parameter, reduce order by 1
// Constrained parameters end up with order 2, Constrained catch alls end up with order 4
- if (part.InlineConstraint != null)
+ if (part.InlineConstraints != null && part.InlineConstraints.Any())
{
digit--;
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs
index 0d416fbeea..c00a5106a9 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs
@@ -184,7 +184,7 @@ namespace Microsoft.AspNet.Mvc.Routing
if (!templateCache.TryGetValue(action.AttributeRouteInfo.Template, out parsedTemplate))
{
// Parsing with throw if the template is invalid.
- parsedTemplate = TemplateParser.Parse(action.AttributeRouteInfo.Template, constraintResolver);
+ parsedTemplate = TemplateParser.Parse(action.AttributeRouteInfo.Template);
templateCache.Add(action.AttributeRouteInfo.Template, parsedTemplate);
}
@@ -218,9 +218,20 @@ namespace Microsoft.AspNet.Mvc.Routing
routeInfo.Name = action.AttributeRouteInfo.Name;
- routeInfo.Constraints = routeInfo.ParsedTemplate.Parameters
- .Where(p => p.InlineConstraint != null)
- .ToDictionary(p => p.Name, p => p.InlineConstraint, StringComparer.OrdinalIgnoreCase);
+ var constraintBuilder = new RouteConstraintBuilder(constraintResolver, routeInfo.RouteTemplate);
+
+ foreach (var parameter in routeInfo.ParsedTemplate.Parameters)
+ {
+ if (parameter.InlineConstraints != null)
+ {
+ foreach (var inlineConstraint in parameter.InlineConstraints)
+ {
+ constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
+ }
+ }
+ }
+
+ routeInfo.Constraints = constraintBuilder.Build();
routeInfo.Defaults = routeInfo.ParsedTemplate.Parameters
.Where(p => p.DefaultValue != null)
@@ -233,9 +244,9 @@ namespace Microsoft.AspNet.Mvc.Routing
{
public ActionDescriptor ActionDescriptor { get; set; }
- public IDictionary Constraints { get; set; }
+ public IReadOnlyDictionary Constraints { get; set; }
- public IDictionary Defaults { get; set; }
+ public IReadOnlyDictionary Defaults { get; set; }
public string ErrorMessage { get; set; }
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs
index 88fd3c30bd..359add36fd 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs
@@ -188,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Description
if (constraintType != null)
{
- Assert.IsType(constraintType, parameter.Constraint);
+ Assert.IsType(constraintType, Assert.Single(parameter.Constraints));
}
if (defaultValue != null)
@@ -243,7 +243,7 @@ namespace Microsoft.AspNet.Mvc.Description
if (constraintType != null)
{
- Assert.IsType(constraintType, parameter.Constraint);
+ Assert.IsType(constraintType, Assert.Single(parameter.Constraints));
}
if (defaultValue != null)
@@ -303,7 +303,7 @@ namespace Microsoft.AspNet.Mvc.Description
if (constraintType != null)
{
- Assert.IsType(constraintType, pathParameter.Constraint);
+ Assert.IsType(constraintType, Assert.Single(pathParameter.Constraints));
}
if (defaultValue != null)
@@ -388,11 +388,11 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var id1 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id1");
Assert.Equal(ApiParameterSource.Path, id1.Source);
- Assert.Null(id1.Constraint);
+ Assert.Empty(id1.Constraints);
var id2 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id2");
Assert.Equal(ApiParameterSource.Path, id2.Source);
- Assert.IsType(id2.Constraint);
+ Assert.IsType(Assert.Single(id2.Constraints));
}
[Fact]
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRoutePrecedenceTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRoutePrecedenceTests.cs
index 8f8df78634..21064e5d6e 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRoutePrecedenceTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRoutePrecedenceTests.cs
@@ -63,11 +63,7 @@ namespace Microsoft.AspNet.Mvc.Routing
var options = new Mock>();
options.SetupGet(o => o.Options).Returns(new RouteOptions());
- var constraintResolver = new DefaultInlineConstraintResolver(
- Mock.Of(),
- options.Object);
-
- var parsed = TemplateParser.Parse(template, constraintResolver);
+ var parsed = TemplateParser.Parse(template);
return AttributeRoutePrecedence.Compute(parsed);
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs
index f9935549e0..add6b624a6 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs
@@ -1256,18 +1256,21 @@ namespace Microsoft.AspNet.Mvc.Routing
private static AttributeRouteMatchingEntry CreateMatchingEntry(IRouter router, string template, int order)
{
- var constraintResolver = CreateConstraintResolver();
-
- var routeTemplate = TemplateParser.Parse(template, constraintResolver);
+ var routeGroup = string.Format("{0}&&{1}", order, template);
var entry = new AttributeRouteMatchingEntry();
- entry.Route = new TemplateRoute(router, template, constraintResolver);
+ entry.Route = new TemplateRoute(
+ target: router,
+ routeTemplate: template,
+ defaults: new RouteValueDictionary(new { test_route_group = routeGroup }),
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: CreateConstraintResolver());
+
+ var routeTemplate = TemplateParser.Parse(template);
entry.Precedence = AttributeRoutePrecedence.Compute(routeTemplate);
entry.Order = order;
- string routeGroup = string.Format("{0}&&{1}", order, template);
- entry.Route.Defaults.Add("test_route_group", routeGroup);
-
return entry;
}
@@ -1281,15 +1284,25 @@ namespace Microsoft.AspNet.Mvc.Routing
var entry = new AttributeRouteLinkGenerationEntry();
entry.TemplateText = template;
- entry.Template = TemplateParser.Parse(template, constraintResolver);
+ entry.Template = TemplateParser.Parse(template);
var defaults = entry.Template.Parameters
.Where(p => p.DefaultValue != null)
.ToDictionary(p => p.Name, p => p.DefaultValue);
- var constraints = entry.Template.Parameters
- .Where(p => p.InlineConstraint != null)
- .ToDictionary(p => p.Name, p => p.InlineConstraint);
+ var constraintBuilder = new RouteConstraintBuilder(CreateConstraintResolver(), template);
+ foreach (var parameter in entry.Template.Parameters)
+ {
+ if (parameter.InlineConstraints != null)
+ {
+ foreach (var constraint in parameter.InlineConstraints)
+ {
+ constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
+ }
+ }
+ }
+
+ var constraints = constraintBuilder.Build();
entry.Constraints = constraints;
entry.Defaults = defaults;
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs
index 0c26d7d42a..a75f62c00a 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs
@@ -178,7 +178,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("id", parameter.Name);
Assert.False(parameter.IsOptional);
Assert.Equal("Path", parameter.Source);
- Assert.Null(parameter.ConstraintType);
+ Assert.Empty(parameter.ConstraintTypes);
}
[Fact]
@@ -203,7 +203,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("integer", parameter.Name);
Assert.False(parameter.IsOptional);
Assert.Equal("Path", parameter.Source);
- Assert.Equal("IntRouteConstraint", parameter.ConstraintType);
+ Assert.Equal("IntRouteConstraint", Assert.Single(parameter.ConstraintTypes));
}
[Fact]
@@ -254,7 +254,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("integer", parameter.Name);
Assert.False(parameter.IsOptional);
Assert.Equal("Path", parameter.Source);
- Assert.Equal("IntRouteConstraint", parameter.ConstraintType);
+ Assert.Equal("IntRouteConstraint", Assert.Single(parameter.ConstraintTypes));
}
[Fact]
@@ -283,17 +283,17 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month");
Assert.False(month.IsOptional);
Assert.Equal("Path", month.Source);
- Assert.Equal("RangeRouteConstraint", month.ConstraintType);
+ Assert.Equal("RangeRouteConstraint", Assert.Single(month.ConstraintTypes));
var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day");
Assert.False(day.IsOptional);
Assert.Equal("Path", day.Source);
- Assert.Equal("IntRouteConstraint", day.ConstraintType);
+ Assert.Equal("IntRouteConstraint", Assert.Single(day.ConstraintTypes));
var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year");
Assert.False(year.IsOptional);
Assert.Equal("Path", year.Source);
- Assert.Equal("IntRouteConstraint", year.ConstraintType);
+ Assert.Equal("IntRouteConstraint", Assert.Single(year.ConstraintTypes));
}
[Fact]
@@ -321,17 +321,17 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month");
Assert.False(month.IsOptional);
Assert.Equal("Path", month.Source);
- Assert.Equal("RangeRouteConstraint", month.ConstraintType);
+ Assert.Equal("RangeRouteConstraint", Assert.Single(month.ConstraintTypes));
var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day");
Assert.False(day.IsOptional);
Assert.Equal("Path", day.Source);
- Assert.Equal("IntRouteConstraint", day.ConstraintType);
+ Assert.Equal("IntRouteConstraint", Assert.Single(day.ConstraintTypes));
var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year");
Assert.True(year.IsOptional);
Assert.Equal("Path", year.Source);
- Assert.Equal("IntRouteConstraint", year.ConstraintType);
+ Assert.Equal("IntRouteConstraint", Assert.Single(year.ConstraintTypes));
}
[Fact]
@@ -749,7 +749,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
public string Type { get; set; }
- public string ConstraintType { get; set; }
+ public string[] ConstraintTypes { get; set; }
}
// Used to serialize data between client and server
diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs
index 8c23c32e1e..e672b4e9aa 100644
--- a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs
+++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
+using System.Linq;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Description;
@@ -56,7 +57,7 @@ namespace ApiExplorer
Name = parameter.Name,
Source = parameter.Source.ToString(),
Type = parameter?.Type?.FullName,
- ConstraintType = parameter?.Constraint?.GetType()?.Name,
+ ConstraintTypes = parameter?.Constraints?.Select(c => c.GetType().Name).ToArray(),
};
data.ParameterDescriptions.Add(parameterData);
@@ -103,7 +104,7 @@ namespace ApiExplorer
public string Type { get; set; }
- public string ConstraintType { get; set; }
+ public string[] ConstraintTypes { get; set; }
}
// Used to serialize data between client and server