diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs
index 230540f8b5..7a73d0d5c8 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs
@@ -76,10 +76,57 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// A for the given .
protected virtual ControllerModel CreateControllerModel([NotNull] TypeInfo typeInfo)
{
+ // For attribute routes on a controller, we want want to support 'overriding' routes on a derived
+ // class. So we need to walk up the hierarchy looking for the first class to define routes.
+ //
+ // Then we want to 'filter' the set of attributes, so that only the effective routes apply.
+ var currentTypeInfo = typeInfo;
+ var objectTypeInfo = typeof(object).GetTypeInfo();
+
+ IRouteTemplateProvider[] routeAttributes = null;
+
+ do
+ {
+ routeAttributes = currentTypeInfo
+ .GetCustomAttributes(inherit: false)
+ .OfType()
+ .ToArray();
+
+ if (routeAttributes.Length > 0)
+ {
+ // Found 1 or more route attributes.
+ break;
+ }
+
+ currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo();
+ }
+ while (currentTypeInfo != objectTypeInfo);
+
// CoreCLR returns IEnumerable from GetCustomAttributes - the OfType
// is needed to so that the result of ToArray() is object
var attributes = typeInfo.GetCustomAttributes(inherit: true).OfType().ToArray();
+
+ // This is fairly complicated so that we maintain referential equality between items in
+ // ControllerModel.Attributes and ControllerModel.Attributes[*].Attribute.
+ var filteredAttributes = new List();
+ foreach (var attribute in attributes)
+ {
+ if (attribute is IRouteTemplateProvider)
+ {
+ // This attribute is a route-attribute, leave it out.
+ }
+ else
+ {
+ filteredAttributes.Add(attribute);
+ }
+ }
+ filteredAttributes.AddRange(routeAttributes);
+
+ attributes = filteredAttributes.ToArray();
+
var controllerModel = new ControllerModel(typeInfo, attributes);
+ AddRange(
+ controllerModel.AttributeRoutes, routeAttributes.Select(a => new AttributeRouteModel(a)));
controllerModel.ControllerName =
typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ?
@@ -108,10 +155,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
controllerModel.Filters.Add(new AuthorizeFilter(policy));
}
- AddRange(
- controllerModel.AttributeRoutes,
- attributes.OfType().Select(rtp => new AttributeRouteModel(rtp)));
-
var apiVisibility = attributes.OfType().FirstOrDefault();
if (apiVisibility != null)
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs
index e630bbacd7..ab2e5b0d3f 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs
@@ -11,7 +11,6 @@ using Microsoft.AspNet.Mvc.ApiExplorer;
using Microsoft.AspNet.Mvc.ApplicationModels;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Routing;
-using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs
index 29c59cef09..4166aba409 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs
@@ -152,6 +152,64 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
Assert.Empty(model.Filters);
}
+ [Fact]
+ public void BuildControllerModel_ClassWithInheritedRoutes()
+ {
+ // Arrange
+ var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), null);
+ var typeInfo = typeof(DerivedClassInheritingRoutesController).GetTypeInfo();
+
+ // Act
+ var model = builder.BuildControllerModel(typeInfo);
+
+ // Assert
+ Assert.Equal(2, model.AttributeRoutes.Count);
+ Assert.Equal(2, model.Attributes.Count);
+
+ var route = Assert.Single(model.AttributeRoutes, r => r.Template == "A");
+ Assert.Contains(route.Attribute, model.Attributes);
+
+ route = Assert.Single(model.AttributeRoutes, r => r.Template == "B");
+ Assert.Contains(route.Attribute, model.Attributes);
+ }
+
+ [Fact]
+ public void BuildControllerModel_ClassWithHiddenInheritedRoutes()
+ {
+ // Arrange
+ var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), null);
+ var typeInfo = typeof(DerivedClassHidingRoutesController).GetTypeInfo();
+
+ // Act
+ var model = builder.BuildControllerModel(typeInfo);
+
+ // Assert
+ Assert.Equal(2, model.AttributeRoutes.Count);
+ Assert.Equal(2, model.Attributes.Count);
+
+ var route = Assert.Single(model.AttributeRoutes, r => r.Template == "C");
+ Assert.Contains(route.Attribute, model.Attributes);
+
+ route = Assert.Single(model.AttributeRoutes, r => r.Template == "D");
+ Assert.Contains(route.Attribute, model.Attributes);
+ }
+
+ [Route("A")]
+ [Route("B")]
+ private class BaseClassWithRoutesController
+ {
+ }
+
+ private class DerivedClassInheritingRoutesController : BaseClassWithRoutesController
+ {
+ }
+
+ [Route("C")]
+ [Route("D")]
+ private class DerivedClassHidingRoutesController : BaseClassWithRoutesController
+ {
+ }
+
private class StoreController : Mvc.Controller
{
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs
index c4df19f262..f9f6cdf502 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs
@@ -1391,6 +1391,7 @@ namespace Microsoft.AspNet.Mvc.Test
{
// Arrange
var actionName = nameof(MultipleRouteProviderOnActionAndControllerController.Delete);
+
var provider = GetProvider(typeof(MultipleRouteProviderOnActionAndControllerController).GetTypeInfo());
// Act
diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.xproj b/test/WebSites/RoutingWebSite/RoutingWebSite.xproj
index ee01dc4ba4..a71d233141 100644
--- a/test/WebSites/RoutingWebSite/RoutingWebSite.xproj
+++ b/test/WebSites/RoutingWebSite/RoutingWebSite.xproj
@@ -10,6 +10,12 @@
..\..\..\artifacts\obj\$(MSBuildProjectName)
..\..\..\artifacts\bin\$(MSBuildProjectName)\
+
+ RoutingWebSite
+
+
+ RoutingWebSite
+
2.0
49632