[Fixes #2931] AttributeRoute does not replace existing route values with null

This commit is contained in:
Ajay Bhargav Baaskaran 2015-08-25 12:32:28 -07:00
parent c0d4981452
commit 4fbaea2463
6 changed files with 123 additions and 10 deletions

View File

@ -294,10 +294,13 @@ namespace Microsoft.AspNet.Mvc.Routing
{ {
foreach (var kvp in values) foreach (var kvp in values)
{ {
// This will replace the original value for the specified key. if (kvp.Value != null)
// Values from the matched route will take preference over previous {
// data in the route context. // This will replace the original value for the specified key.
destination[kvp.Key] = kvp.Value; // Values from the matched route will take preference over previous
// data in the route context.
destination[kvp.Key] = kvp.Value;
}
} }
} }

View File

@ -1463,6 +1463,62 @@ namespace Microsoft.AspNet.Mvc.Routing
Assert.Equal(next.Object.GetType(), context.RouteData.Routers[0].GetType()); Assert.Equal(next.Object.GetType(), context.RouteData.Routers[0].GetType());
} }
[Fact]
public async Task AttributeRoute_ReplacesExistingRouteValues_IfNotNull()
{
// Arrange
var router = new Mock<IRouter>();
router
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) =>
{
c.IsHandled = true;
})
.Returns(Task.FromResult(true));
var entry = CreateMatchingEntry(router.Object, "Foo/{*path}", order: 0);
var route = CreateAttributeRoute(router.Object, entry);
var context = CreateRouteContext("/Foo/Bar");
var originalRouteData = context.RouteData;
originalRouteData.Values.Add("path", "default");
// Act
await route.RouteAsync(context);
// Assert
Assert.Equal("Bar", context.RouteData.Values["path"]);
}
[Fact]
public async Task AttributeRoute_DoesNotReplaceExistingRouteValues_IfNull()
{
// Arrange
var router = new Mock<IRouter>();
router
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) =>
{
c.IsHandled = true;
})
.Returns(Task.FromResult(true));
var entry = CreateMatchingEntry(router.Object, "Foo/{*path}", order: 0);
var route = CreateAttributeRoute(router.Object, entry);
var context = CreateRouteContext("/Foo/");
var originalRouteData = context.RouteData;
originalRouteData.Values.Add("path", "default");
// Act
await route.RouteAsync(context);
// Assert
Assert.Equal("default", context.RouteData.Values["path"]);
}
[Fact] [Fact]
public async Task AttributeRoute_CreatesNewRouteData_ResetsWhenNotMatched() public async Task AttributeRoute_CreatesNewRouteData_ResetsWhenNotMatched()
{ {

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
private readonly Action<IServiceCollection> _configureServices = new RoutingWebSite.Startup().ConfigureServices; private readonly Action<IServiceCollection> _configureServices = new RoutingWebSite.Startup().ConfigureServices;
[Fact] [Fact]
public async Task ConventionRoutedController_ActionIsReachable() public async Task ConventionalRoutedController_ActionIsReachable()
{ {
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
} }
[Fact] [Fact]
public async Task ConventionRoutedController_ActionIsReachable_WithDefaults() public async Task ConventionalRoutedController_ActionIsReachable_WithDefaults()
{ {
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
} }
[Fact] [Fact]
public async Task ConventionRoutedController_NonActionIsNotReachable() public async Task ConventionalRoutedController_NonActionIsNotReachable()
{ {
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
} }
[Fact] [Fact]
public async Task ConventionRoutedController_InArea_ActionIsReachable() public async Task ConventionalRoutedController_InArea_ActionIsReachable()
{ {
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -121,7 +121,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
} }
[Fact] [Fact]
public async Task ConventionRoutedController_InArea_ActionBlockedByHttpMethod() public async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod()
{ {
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -134,6 +134,27 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
[Theory]
[InlineData("", "/Home/OptionalPath/default")]
[InlineData("CustomPath", "/Home/OptionalPath/CustomPath")]
public async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected)
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Single(result.ExpectedUrls, expected);
}
[Fact] [Fact]
public async Task AttributeRoutedAction_IsReachable() public async Task AttributeRoutedAction_IsReachable()
{ {
@ -338,7 +359,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// There's two actions at this URL - but attribute routes go in the route table // There's two actions at this URL - but attribute routes go in the route table
// first. // first.
[Fact] [Fact]
public async Task AttributeRoutedAction_TriedBeforeConventionRouting() public async Task AttributeRoutedAction_TriedBeforeConventionalRouting()
{ {
// Arrange // Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -721,6 +742,24 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("/Teams/AllOrganizations", response); Assert.Equal("/Teams/AllOrganizations", response);
} }
[Theory]
[InlineData("", "/TeamName/DefaultName")]
[InlineData("CustomName", "/TeamName/CustomName")]
public async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected)
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var body = await client.GetStringAsync("http://localhost/TeamName/" + teamName);
// Assert
Assert.NotNull(body);
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Single(result.ExpectedUrls, expected);
}
[Fact] [Fact]
public async Task AttributeRoutedAction_LinkToSelf() public async Task AttributeRoutedAction_LinkToSelf()
{ {

View File

@ -30,5 +30,10 @@ namespace RoutingWebSite
{ {
return _generator.Generate("/Home/Contact"); return _generator.Generate("/Home/Contact");
} }
public IActionResult OptionalPath(string path = "default")
{
return _generator.Generate("/Home/OptionalPath/" + path);
}
} }
} }

View File

@ -62,5 +62,11 @@ namespace RoutingWebSite
{ {
return Content(Url.Action(), "text/plain"); return Content(Url.Action(), "text/plain");
} }
[HttpGet("/TeamName/{*Name}/")]
public ActionResult GetTeam(string name = "DefaultName")
{
return _generator.Generate("/TeamName/" + name);
}
} }
} }

View File

@ -44,6 +44,10 @@ namespace RoutingWebSite
"DuplicateRoute", "DuplicateRoute",
"conventional/Duplicate", "conventional/Duplicate",
defaults: new { controller = "Duplicate", action = "Duplicate" }); defaults: new { controller = "Duplicate", action = "Duplicate" });
routes.MapRoute(
"RouteWithOptionalSegment",
"{controller}/{action}/{path?}");
}); });
} }
} }