diff --git a/src/Microsoft.AspNet.Mvc.Core/RouteAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/RouteAttribute.cs
index 217e2afdf6..b2ffed2e18 100644
--- a/src/Microsoft.AspNet.Mvc.Core/RouteAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/RouteAttribute.cs
@@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc
///
/// Specifies an attribute route on a controller.
///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RouteAttribute : Attribute, IRouteTemplateProvider
{
private int? _order;
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs
index ac54f90dc1..fb03777e6f 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs
@@ -566,6 +566,27 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal(expectedMessage, ex.Message);
}
+ [Fact]
+ public void AttributeRouting_RouteOnControllerAndAction_CreatesActionDescriptorWithoutHttpConstraints()
+ {
+ // Arrange
+ var provider = GetProvider(typeof(OnlyRouteController).GetTypeInfo());
+
+ // Act
+ var actions = provider.GetDescriptors();
+
+ // Assert
+ var action = Assert.Single(actions);
+
+ Assert.Equal("Action", action.Name);
+ Assert.Equal("OnlyRoute", action.ControllerName);
+
+ Assert.NotNull(action.AttributeRouteInfo);
+ Assert.Equal("Products/Index", action.AttributeRouteInfo.Template);
+
+ Assert.Null(action.MethodConstraints);
+ }
+
[Fact]
public void AttributeRouting_Name_ThrowsIfMultipleActions_WithDifferentTemplatesHaveTheSameName()
{
@@ -992,6 +1013,13 @@ namespace Microsoft.AspNet.Mvc.Test
public void Delete(int id) { }
}
+ [Route("Products")]
+ public class OnlyRouteController
+ {
+ [Route("Index")]
+ public void Action() { }
+ }
+
[MyRouteConstraint(blockNonAttributedActions: true)]
[MySecondRouteConstraint(blockNonAttributedActions: true)]
private class ConstrainedController
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs
index c6e7b2f2dc..a4ee08096a 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs
@@ -256,6 +256,84 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("List", result.Action);
}
+ // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller
+ // and a test that verifies that if you define another action with a specific verb we'll route to that
+ // more specific action.
+ [Theory]
+ [InlineData("PUT")]
+ [InlineData("POST")]
+ [InlineData("PATCH")]
+ [InlineData("DELETE")]
+ public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method)
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+ var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders");
+
+ // Act
+ var response = await client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var body = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject(body);
+
+ Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls);
+ Assert.Equal("Store", result.Controller);
+ Assert.Equal("Orders", result.Action);
+ }
+
+ [Theory]
+ [InlineData("GET")]
+ [InlineData("POST")]
+ [InlineData("PUT")]
+ [InlineData("PATCH")]
+ [InlineData("DELETE")]
+ public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method)
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+ var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary");
+
+ // Act
+ var response = await client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var body = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject(body);
+
+ Assert.Contains("/api/Employee/5/Salary", result.ExpectedUrls);
+ Assert.Equal("Employee", result.Controller);
+ Assert.Equal("Salary", result.Action);
+ }
+
+ [Fact]
+ public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+ var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders");
+
+ // Act
+ var response = await client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var body = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject(body);
+
+ Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls);
+ Assert.Equal("Store", result.Controller);
+ Assert.Equal("GetOrders", result.Action);
+ }
+
// There's no [HttpGet] on the action here.
[Theory]
[InlineData("PUT")]
@@ -276,7 +354,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject(body);
- // Assert
Assert.Contains("/api/Employee", result.ExpectedUrls);
Assert.Equal("Employee", result.Controller);
Assert.Equal("UpdateEmployee", result.Action);
@@ -421,7 +498,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
- public async Task AttributeRoutedAction__LinkGeneration_OrderOnActionOverridesOrderOnController()
+ public async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController()
{
// Arrange
var server = TestServer.Create(_services, _app);
diff --git a/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs b/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs
index 9c9b796ccc..a712afb8e3 100644
--- a/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs
+++ b/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs
@@ -55,5 +55,11 @@ namespace RoutingWebSite
{
return _generator.Generate("/api/Employee/" + id + "/Administrator");
}
+
+ [Route("{id}/Salary")]
+ public IActionResult Salary(int id)
+ {
+ return _generator.Generate("/api/Employee/" + id + "/Salary");
+ }
}
}
\ No newline at end of file
diff --git a/test/WebSites/RoutingWebSite/Controllers/StoreController.cs b/test/WebSites/RoutingWebSite/Controllers/StoreController.cs
index 14150b65fa..1e12fdaee1 100644
--- a/test/WebSites/RoutingWebSite/Controllers/StoreController.cs
+++ b/test/WebSites/RoutingWebSite/Controllers/StoreController.cs
@@ -27,5 +27,17 @@ namespace RoutingWebSite
{
return _generator.Generate("/Home/About");
}
+
+ [Route("Store/Shop/Orders")]
+ public IActionResult Orders()
+ {
+ return _generator.Generate("/Store/Shop/Orders");
+ }
+
+ [HttpGet("Store/Shop/Orders")]
+ public IActionResult GetOrders()
+ {
+ return _generator.Generate("/Store/Shop/Orders");
+ }
}
}
\ No newline at end of file