diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs index 873f6d8..d157fdb 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Threading.Tasks; namespace Microsoft.AspNet.Routing.Template @@ -11,6 +12,7 @@ namespace Microsoft.AspNet.Routing.Template private readonly IDictionary _defaults; private readonly IDictionary _constraints; private readonly IRouter _target; + private readonly Template _parsedTemplate; private readonly string _routeTemplate; private readonly TemplateMatcher _matcher; private readonly TemplateBinder _binder; @@ -29,10 +31,10 @@ namespace Microsoft.AspNet.Routing.Template _constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate); // The parser will throw for invalid routes. - var parsedTemplate = TemplateParser.Parse(RouteTemplate); + _parsedTemplate = TemplateParser.Parse(RouteTemplate); - _matcher = new TemplateMatcher(parsedTemplate); - _binder = new TemplateBinder(parsedTemplate, _defaults); + _matcher = new TemplateMatcher(_parsedTemplate); + _binder = new TemplateBinder(_parsedTemplate, _defaults); } public IDictionary Defaults @@ -85,7 +87,7 @@ namespace Microsoft.AspNet.Routing.Template var values = _binder.GetAcceptedValues(context.AmbientValues, context.Values); if (values == null) { - // We're missing one the required values for this route. + // We're missing one of the required values for this route. return null; } @@ -99,25 +101,59 @@ namespace Microsoft.AspNet.Routing.Template } // Validate that the target can accept these values. - var path = _target.GetVirtualPath(context); + var childContext = CreateChildVirtualPathContext(context, values); + var path = _target.GetVirtualPath(childContext); if (path != null) { // If the target generates a value then that can short circuit. + context.IsBound = true; return path; } - else if (!context.IsBound) + else if (!childContext.IsBound) { // The target has rejected these values. return null; } path = _binder.BindValues(values); - if (path == null) + if (path != null) { - context.IsBound = false; + context.IsBound = true; } return path; } + + private VirtualPathContext CreateChildVirtualPathContext( + VirtualPathContext context, + IDictionary acceptedValues) + { + // We want to build the set of values that would be provided if this route were to generated + // a link and then immediately match it. This includes all the accepted parameter values, and + // the defaults. Accepted values that would go in the query string aren't included. + var providedValues = new RouteValueDictionary(); + + foreach (var parameter in _parsedTemplate.Parameters) + { + object value; + if (acceptedValues.TryGetValue(parameter.Name, out value)) + { + providedValues.Add(parameter.Name, value); + } + } + + foreach (var kvp in _defaults) + { + if (!providedValues.ContainsKey(kvp.Key)) + { + providedValues.Add(kvp.Key, kvp.Value); + } + } + + return new VirtualPathContext(context.Context, context.AmbientValues, context.Values) + { + ProvidedValues = providedValues, + }; + } } } diff --git a/src/Microsoft.AspNet.Routing/VirtualPathContext.cs b/src/Microsoft.AspNet.Routing/VirtualPathContext.cs index fe1cfbb..2dfb3b5 100644 --- a/src/Microsoft.AspNet.Routing/VirtualPathContext.cs +++ b/src/Microsoft.AspNet.Routing/VirtualPathContext.cs @@ -14,6 +14,8 @@ namespace Microsoft.AspNet.Routing Values = values; } + public IDictionary ProvidedValues { get; set; } + public IDictionary AmbientValues { get; private set; } public HttpContext Context { get; private set; } diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs index 3e631cf..eb7d351 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs @@ -284,6 +284,78 @@ namespace Microsoft.AspNet.Routing.Template.Tests Assert.Equal("hello/1234", virtualPath); } + [Fact] + public void GetVirtualPath_Sends_ProvidedValues() + { + // Arrange + VirtualPathContext childContext = null; + var target = new Mock(MockBehavior.Strict); + target + .Setup(r => r.GetVirtualPath(It.IsAny())) + .Callback(c => { childContext = c; c.IsBound = true; }) + .Returns(null); + + var route = CreateRoute(target.Object, "{controller}/{action}"); + var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog"}); + + var expectedValues = new RouteValueDictionary(new {controller = "Home", action = "Store"}); + + // Act + var path = route.GetVirtualPath(context); + + // Assert + Assert.Equal("Home/Store", path); + Assert.Equal(expectedValues, childContext.ProvidedValues); + } + + [Fact] + public void GetVirtualPath_Sends_ProvidedValues_IncludingDefaults() + { + // Arrange + VirtualPathContext childContext = null; + var target = new Mock(MockBehavior.Strict); + target + .Setup(r => r.GetVirtualPath(It.IsAny())) + .Callback(c => { childContext = c; c.IsBound = true; }) + .Returns(null); + + var route = CreateRoute(target.Object, "Admin/{controller}/{action}", new {area = "Admin"}); + var context = CreateVirtualPathContext(new { action = "Store" }, new { Controller = "Home", action = "Blog" }); + + var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store", area = "Admin" }); + + // Act + var path = route.GetVirtualPath(context); + + // Assert + Assert.Equal("Admin/Home/Store", path); + Assert.Equal(expectedValues, childContext.ProvidedValues); + } + + [Fact] + public void GetVirtualPath_Sends_ProvidedValues_ButNotQueryStringValues() + { + // Arrange + VirtualPathContext childContext = null; + var target = new Mock(MockBehavior.Strict); + target + .Setup(r => r.GetVirtualPath(It.IsAny())) + .Callback(c => { childContext = c; c.IsBound = true; }) + .Returns(null); + + var route = CreateRoute(target.Object, "{controller}/{action}"); + var context = CreateVirtualPathContext(new { action = "Store", id = 5 }, new { Controller = "Home", action = "Blog" }); + + var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" }); + + // Act + var path = route.GetVirtualPath(context); + + // Assert + Assert.Equal("Home/Store?id=5", path); + Assert.Equal(expectedValues, childContext.ProvidedValues); + } + private static VirtualPathContext CreateVirtualPathContext(object values) { return CreateVirtualPathContext(new RouteValueDictionary(values), null); @@ -355,6 +427,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests return new TemplateRoute(CreateTarget(accept), template, new RouteValueDictionary(defaults), constraints); } + private static TemplateRoute CreateRoute(IRouter target, string template) + { + return new TemplateRoute(target, template, new RouteValueDictionary(), constraints: null); + } + + private static TemplateRoute CreateRoute(IRouter target, string template, object defaults) + { + return new TemplateRoute(target, template, new RouteValueDictionary(defaults), constraints: null); + } + private static IRouter CreateTarget(bool accept = true) { var target = new Mock(MockBehavior.Strict); @@ -373,4 +455,4 @@ namespace Microsoft.AspNet.Routing.Template.Tests } } -#endif \ No newline at end of file +#endif