[Fixes #90] RouteTemplate does not take RouteData

Changed the implementation of route template to merge the existing route data
with the values obtained from parsing the request path with the given template.

Restored original route data values in case the route template data does not match.
This commit is contained in:
jacalvar 2014-10-20 16:22:08 -07:00
parent e0f01aa6bb
commit 02a0a218b9
2 changed files with 202 additions and 36 deletions

View File

@ -121,39 +121,51 @@ namespace Microsoft.AspNet.Routing.Template
} }
else else
{ {
// Not currently doing anything to clean this up if it's not a match. Consider hardening this. var originalValues = context.RouteData.Values;
context.RouteData.Values = values; context.RouteData.Values = MergeValues(originalValues, values);
if (RouteConstraintMatcher.Match(Constraints, try
values,
context.HttpContext,
this,
RouteDirection.IncomingRequest,
_constraintLogger))
{ {
context.RouteData.DataTokens = _dataTokens; if (RouteConstraintMatcher.Match(Constraints,
await _target.RouteAsync(context); values,
context.HttpContext,
if (_logger.IsEnabled(TraceType.Information)) this,
RouteDirection.IncomingRequest,
_constraintLogger))
{ {
_logger.WriteValues(CreateRouteAsyncValues( context.RouteData.DataTokens = _dataTokens;
requestPath, await _target.RouteAsync(context);
values,
matchedValues: true, if (_logger.IsEnabled(TraceType.Information))
matchedConstraints: true, {
handled: context.IsHandled)); _logger.WriteValues(CreateRouteAsyncValues(
requestPath,
values,
matchedValues: true,
matchedConstraints: true,
handled: context.IsHandled));
}
}
else
{
if (_logger.IsEnabled(TraceType.Information))
{
_logger.WriteValues(CreateRouteAsyncValues(
requestPath,
values,
matchedValues: true,
matchedConstraints: false,
handled: context.IsHandled));
}
} }
} }
else finally
{ {
if (_logger.IsEnabled(TraceType.Information)) if (!context.IsHandled)
{ {
_logger.WriteValues(CreateRouteAsyncValues( // Restore the original route data if we didn't handle the route to prevent
requestPath, // poluting the original route data with the values from this route.
values, context.RouteData.Values = originalValues;
matchedValues: true,
matchedConstraints: false,
handled: context.IsHandled));
} }
} }
} }
@ -291,6 +303,28 @@ namespace Microsoft.AspNet.Routing.Template
return values; return values;
} }
private static IDictionary<string, object> MergeValues(
IDictionary<string, object> originalValues,
IDictionary<string, object> values)
{
if (originalValues == null)
{
// There is nothing to merge
return values;
}
var result = new RouteValueDictionary(originalValues);
foreach (var kvp in values)
{
// This will replace the original value for the specified key.
// Values from the matched route will take preference over previous
// data in the route context.
result[kvp.Key] = kvp.Value;
}
return result;
}
private void EnsureLoggers(HttpContext context) private void EnsureLoggers(HttpContext context)
{ {
if (_logger == null) if (_logger == null)

View File

@ -168,6 +168,138 @@ namespace Microsoft.AspNet.Routing.Template
Assert.Equal(context.IsHandled, values.Handled); Assert.Equal(context.IsHandled, values.Handled);
} }
[Fact]
public async Task RouteAsync_MergesExistingRouteData_IfRouteMatches()
{
// Arrange
var template = "{controller}/{action}/{id:int}";
var context = CreateRouteContext("/Home/Index/5");
var originalRouteDataValues = new Dictionary<string, object>
{
{ "country", "USA" },
};
context.RouteData.Values = originalRouteDataValues;
IDictionary<string, object> routeValues = null;
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
mockTarget
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(mockTarget.Object, template, _inlineConstraintResolver);
// Act
await route.RouteAsync(context);
// Assert
Assert.NotNull(routeValues);
Assert.True(routeValues.ContainsKey("country"));
Assert.Equal("USA", routeValues["country"]);
Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5",routeValues["id"]);
Assert.True(context.RouteData.Values.ContainsKey("country"));
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.True(context.RouteData.Values.ContainsKey("id"));
Assert.Equal("5", context.RouteData.Values["id"]);
Assert.NotSame(originalRouteDataValues, context.RouteData.Values);
}
[Fact]
public async Task RouteAsync_CleansUpMergedRouteData_IfRouteDoesNotMatch()
{
// Arrange
var template = "{controller}/{action}/{id:int}";
var context = CreateRouteContext("/Home/Index/5");
var originalRouteDataValues = new Dictionary<string, object>
{
{ "country", "USA" },
};
context.RouteData.Values = originalRouteDataValues;
IDictionary<string, object> routeValues = null;
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
mockTarget
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = false;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(mockTarget.Object, template, _inlineConstraintResolver);
// Act
await route.RouteAsync(context);
// Assert
Assert.NotNull(routeValues);
Assert.True(routeValues.ContainsKey("country"));
Assert.Equal("USA", routeValues["country"]);
Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]);
Assert.True(context.RouteData.Values.ContainsKey("country"));
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.Same(originalRouteDataValues, context.RouteData.Values);
}
[Fact]
public async Task RouteAsync_CleansUpMergedRouteData_IfHandlerThrows()
{
// Arrange
var template = "{controller}/{action}/{id:int}";
var context = CreateRouteContext("/Home/Index/5");
var originalRouteDataValues = new Dictionary<string, object>
{
{ "country", "USA" },
};
context.RouteData.Values = originalRouteDataValues;
IDictionary<string, object> routeValues = null;
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
mockTarget
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = false;
})
.Throws(new Exception());
var route = new TemplateRoute(mockTarget.Object, template, _inlineConstraintResolver);
// Act
var ex = await Assert.ThrowsAsync<Exception>(() => route.RouteAsync(context));
// Assert
Assert.NotNull(routeValues);
Assert.True(routeValues.ContainsKey("country"));
Assert.Equal("USA", routeValues["country"]);
Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]);
Assert.True(context.RouteData.Values.ContainsKey("country"));
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.Same(originalRouteDataValues, context.RouteData.Values);
}
[Fact] [Fact]
public async Task RouteAsync_MatchFailOnConstraints_DoesNotLogWhenDisabled() public async Task RouteAsync_MatchFailOnConstraints_DoesNotLogWhenDisabled()
{ {
@ -265,7 +397,7 @@ namespace Microsoft.AspNet.Routing.Template
Assert.False(context.IsHandled); Assert.False(context.IsHandled);
// Issue #16 tracks this. // Issue #16 tracks this.
Assert.NotNull(context.RouteData.Values); Assert.Null(context.RouteData.Values);
} }
[Fact] [Fact]
@ -490,9 +622,9 @@ namespace Microsoft.AspNet.Routing.Template
.Returns<string>(null); .Returns<string>(null);
var route = CreateRoute(target.Object, "{controller}/{action}"); var route = CreateRoute(target.Object, "{controller}/{action}");
var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog"}); var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog" });
var expectedValues = new RouteValueDictionary(new {controller = "Home", action = "Store"}); var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" });
// Act // Act
var path = route.GetVirtualPath(context); var path = route.GetVirtualPath(context);
@ -513,7 +645,7 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<VirtualPathContext>(c => { childContext = c; c.IsBound = true; }) .Callback<VirtualPathContext>(c => { childContext = c; c.IsBound = true; })
.Returns<string>(null); .Returns<string>(null);
var route = CreateRoute(target.Object, "Admin/{controller}/{action}", new {area = "Admin"}); var route = CreateRoute(target.Object, "Admin/{controller}/{action}", new { area = "Admin" });
var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog" }); var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog" });
var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store", area = "Admin" }); var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store", area = "Admin" });
@ -564,7 +696,7 @@ namespace Microsoft.AspNet.Routing.Template
constraints: new { c = constraint }); constraints: new { c = constraint });
var context = CreateVirtualPathContext( var context = CreateVirtualPathContext(
values: new { action = "Store" }, values: new { action = "Store" },
ambientValues: new { Controller = "Home", action = "Blog", extra = "42" }); ambientValues: new { Controller = "Home", action = "Blog", extra = "42" });
var expectedValues = new RouteValueDictionary( var expectedValues = new RouteValueDictionary(
@ -750,7 +882,7 @@ namespace Microsoft.AspNet.Routing.Template
var routeBuilder = CreateRouteBuilder(); var routeBuilder = CreateRouteBuilder();
// Assert // Assert
ExceptionAssert.Throws<InvalidOperationException>(() => routeBuilder.MapRoute("mockName", ExceptionAssert.Throws<InvalidOperationException>(() => routeBuilder.MapRoute("mockName",
"{controller}/{action}", "{controller}/{action}",
defaults: null, defaults: null,
constraints: new { controller = "a.*", action = new Object() }), constraints: new { controller = "a.*", action = new Object() }),
@ -937,10 +1069,10 @@ namespace Microsoft.AspNet.Routing.Template
public IDictionary<string, object> Values { get; private set; } public IDictionary<string, object> Values { get; private set; }
public bool Match( public bool Match(
HttpContext httpContext, HttpContext httpContext,
IRouter route, IRouter route,
string routeKey, string routeKey,
IDictionary<string, object> values, IDictionary<string, object> values,
RouteDirection routeDirection) RouteDirection routeDirection)
{ {
Values = new RouteValueDictionary(values); Values = new RouteValueDictionary(values);