[Fixes #900] Use [Route(...)] on action methods to specify an attribute route that allows all verbs.
1. Changed attribute usage on RouteAttribute. 2. Added a test on action discovery to ensure that actions with [Route] get discovered as attribute routed actions. 3. Added a test on reflected action descriptor provider to ensure that an action with [Route] on the controller and [Route] on the action results in an action that allows any Http method.
This commit is contained in:
parent
44ccea6871
commit
cde1a95d49
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// <summary>
|
||||
/// Specifies an attribute route on a controller.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class RouteAttribute : Attribute, IRouteTemplateProvider
|
||||
{
|
||||
private int? _order;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<RoutingResult>(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<RoutingResult>(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<RoutingResult>(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<RoutingResult>(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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue