diff --git a/samples/RoutingSample.Web/DelegateRouteEndpoint.cs b/samples/RoutingSample.Web/DelegateRouteEndpoint.cs index c561ce8836..415722d199 100644 --- a/samples/RoutingSample.Web/DelegateRouteEndpoint.cs +++ b/samples/RoutingSample.Web/DelegateRouteEndpoint.cs @@ -23,7 +23,7 @@ namespace RoutingSample.Web context.IsHandled = true; } - public string GetVirtualPath(VirtualPathContext context) + public VirtualPathData GetVirtualPath(VirtualPathContext context) { // We don't really care what the values look like. context.IsBound = true; diff --git a/samples/RoutingSample.Web/PrefixRoute.cs b/samples/RoutingSample.Web/PrefixRoute.cs index e9760dd795..dbeb0aac4c 100644 --- a/samples/RoutingSample.Web/PrefixRoute.cs +++ b/samples/RoutingSample.Web/PrefixRoute.cs @@ -52,9 +52,9 @@ namespace RoutingSample.Web } } - public string GetVirtualPath(VirtualPathContext context) + public VirtualPathData GetVirtualPath(VirtualPathContext context) { return null; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Routing/IRouter.cs b/src/Microsoft.AspNet.Routing/IRouter.cs index dc81ee6d04..bd1a7e4e89 100644 --- a/src/Microsoft.AspNet.Routing/IRouter.cs +++ b/src/Microsoft.AspNet.Routing/IRouter.cs @@ -9,6 +9,6 @@ namespace Microsoft.AspNet.Routing { Task RouteAsync(RouteContext context); - string GetVirtualPath(VirtualPathContext context); + VirtualPathData GetVirtualPath(VirtualPathContext context); } } diff --git a/src/Microsoft.AspNet.Routing/RouteCollection.cs b/src/Microsoft.AspNet.Routing/RouteCollection.cs index 53a464cbf8..ff98b81c94 100644 --- a/src/Microsoft.AspNet.Routing/RouteCollection.cs +++ b/src/Microsoft.AspNet.Routing/RouteCollection.cs @@ -98,11 +98,11 @@ namespace Microsoft.AspNet.Routing } } - public virtual string GetVirtualPath(VirtualPathContext context) + public virtual VirtualPathData GetVirtualPath(VirtualPathContext context) { EnsureOptions(context.Context); - // If we're using Best-Effort link generation then it means that we'll first look for a route where + // If we're using Best-Effort link generation then it means that we'll first look for a route where // the route values are validated (context.IsBound == true). If we can't find a match like that, then // we'll return the path from the first route to return one. var useBestEffort = _options.UseBestEffortLinkGeneration; @@ -110,11 +110,11 @@ namespace Microsoft.AspNet.Routing if (!string.IsNullOrEmpty(context.RouteName)) { var isValidated = false; - string bestPath = null; + VirtualPathData bestPathData = null; INamedRouter matchedNamedRoute; if (_namedRoutes.TryGetValue(context.RouteName, out matchedNamedRoute)) { - bestPath = matchedNamedRoute.GetVirtualPath(context); + bestPathData = matchedNamedRoute.GetVirtualPath(context); isValidated = context.IsBound; } @@ -125,13 +125,13 @@ namespace Microsoft.AspNet.Routing // reset because we're sharing the context context.IsBound = false; - var path = unnamedRoute.GetVirtualPath(context); - if (path == null) + var pathData = unnamedRoute.GetVirtualPath(context); + if (pathData == null) { continue; } - if (bestPath != null) + if (bestPathData != null) { // There was already a previous route which matched the name. throw new InvalidOperationException( @@ -140,15 +140,15 @@ namespace Microsoft.AspNet.Routing else if (context.IsBound) { // This is the first 'validated' match that we've found. - bestPath = path; + bestPathData = pathData; isValidated = true; } else { - Debug.Assert(bestPath == null); + Debug.Assert(bestPathData == null); // This is the first 'unvalidated' match that we've found. - bestPath = path; + bestPathData = pathData; isValidated = false; } } @@ -156,7 +156,16 @@ namespace Microsoft.AspNet.Routing if (isValidated || useBestEffort) { context.IsBound = isValidated; - return NormalizeVirtualPath(bestPath); + + if (bestPathData != null) + { + bestPathData = new VirtualPathData( + bestPathData.Router, + NormalizeVirtualPath(bestPathData.VirtualPath), + bestPathData.DataTokens); + } + + return bestPathData; } else { @@ -165,13 +174,13 @@ namespace Microsoft.AspNet.Routing } else { - string bestPath = null; + VirtualPathData bestPathData = null; for (var i = 0; i < Count; i++) { var route = this[i]; - var path = route.GetVirtualPath(context); - if (path == null) + var pathData = route.GetVirtualPath(context); + if (pathData == null) { continue; } @@ -179,18 +188,24 @@ namespace Microsoft.AspNet.Routing if (context.IsBound) { // This route has validated route values, short circuit. - return NormalizeVirtualPath(path); + return new VirtualPathData( + pathData.Router, + NormalizeVirtualPath(pathData.VirtualPath), + pathData.DataTokens); } - else if (bestPath == null) + else if (bestPathData == null) { // The values aren't validated, but this is the best we've seen so far - bestPath = path; + bestPathData = pathData; } } if (useBestEffort) { - return NormalizeVirtualPath(bestPath); + return new VirtualPathData( + bestPathData.Router, + NormalizeVirtualPath(bestPathData.VirtualPath), + bestPathData.DataTokens); } else { @@ -223,9 +238,9 @@ namespace Microsoft.AspNet.Routing } return url; - } + } - private void EnsureLogger(HttpContext context) + private void EnsureLogger(HttpContext context) { if (_logger == null) { diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs index 88f98a99e5..a65ef61bba 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs @@ -181,7 +181,7 @@ namespace Microsoft.AspNet.Routing.Template } } - public virtual string GetVirtualPath(VirtualPathContext context) + public virtual VirtualPathData GetVirtualPath(VirtualPathContext context) { var values = _binder.GetValues(context.AmbientValues, context.Values); if (values == null) @@ -204,19 +204,35 @@ namespace Microsoft.AspNet.Routing.Template // Validate that the target can accept these values. var childContext = CreateChildVirtualPathContext(context, values.AcceptedValues); - var path = _target.GetVirtualPath(childContext); - if (path != null) + var pathData = _target.GetVirtualPath(childContext); + if (pathData != null) { // If the target generates a value then that can short circuit. - return path; + return pathData; } // If we can produce a value go ahead and do it, the caller can check context.IsBound // to see if the values were validated. - path = _binder.BindValues(values.AcceptedValues); + + // When we still cannot produce a value, this should return null. + var tempPath = _binder.BindValues(values.AcceptedValues); + if (tempPath == null) + { + return null; + } + + pathData = new VirtualPathData(this, tempPath); + if (DataTokens != null) + { + foreach (var dataToken in DataTokens) + { + pathData.DataTokens.Add(dataToken.Key, dataToken.Value); + } + } + context.IsBound = childContext.IsBound; - return path; + return pathData; } private VirtualPathContext CreateChildVirtualPathContext( diff --git a/src/Microsoft.AspNet.Routing/VirtualPathData.cs b/src/Microsoft.AspNet.Routing/VirtualPathData.cs new file mode 100644 index 0000000000..c050a78182 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/VirtualPathData.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Routing +{ + /// + /// Represents information about the route and virtual path that are the result of + /// generating a URL with the ASP.NET routing middleware. + /// + public class VirtualPathData + { + private string _virtualPath; + private readonly IDictionary _dataToken; + + /// + /// Initializes a new instance of the class. + /// + /// The object that is used to generate the URL. + /// The generated URL. + public VirtualPathData([NotNull] IRouter router, string virtualPath) + : this(router, virtualPath, dataTokens: new RouteValueDictionary()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object that is used to generate the URL. + /// The generated URL. + /// The collection of custom values. + public VirtualPathData( + [NotNull] IRouter router, + string virtualPath, + IDictionary dataTokens) + { + Router = router; + VirtualPath = virtualPath; + + _dataToken = new RouteValueDictionary(); + if (dataTokens != null) + { + foreach (var dataToken in dataTokens) + { + _dataToken.Add(dataToken.Key, dataToken.Value); + } + } + } + + /// + /// Gets the collection of custom values for the . + /// + public IDictionary DataTokens + { + get { return _dataToken; } + } + + /// + /// Gets or sets the that was used to generate the URL. + /// + public IRouter Router { get; set; } + + /// + /// Gets or sets the URL that was generated from the . + /// + public string VirtualPath + { + get + { + return _virtualPath ?? string.Empty; + } + set + { + _virtualPath = value; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Routing.Tests/RouteCollectionTest.cs b/test/Microsoft.AspNet.Routing.Tests/RouteCollectionTest.cs index 52e36b232f..11727ae986 100644 --- a/test/Microsoft.AspNet.Routing.Tests/RouteCollectionTest.cs +++ b/test/Microsoft.AspNet.Routing.Tests/RouteCollectionTest.cs @@ -27,25 +27,27 @@ namespace Microsoft.AspNet.Routing [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "Home/Index/23#Param1=ABC&Param2=Xyz", false)] [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "home/index/23#Param1=ABC&Param2=Xyz", true)] public void GetVirtualPath_CanLowerCaseUrls_BasedOnOptions( - string returnUrl, - string lowercaseUrl, + string returnUrl, + string lowercaseUrl, bool lowercaseUrls) { // Arrange var target = new Mock(MockBehavior.Strict); target .Setup(e => e.GetVirtualPath(It.IsAny())) - .Returns(returnUrl); + .Returns(new VirtualPathData(target.Object, returnUrl)); var routeCollection = new RouteCollection(); routeCollection.Add(target.Object); var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls)); // Act - var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); // Assert - Assert.Equal(lowercaseUrl, stringVirtualPath); + Assert.Equal(lowercaseUrl, pathData.VirtualPath); + Assert.Same(target.Object, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Theory] @@ -61,17 +63,56 @@ namespace Microsoft.AspNet.Routing var target = new Mock(MockBehavior.Strict); target .Setup(e => e.GetVirtualPath(It.IsAny())) - .Returns(returnUrl); + .Returns(new VirtualPathData(target.Object, returnUrl)); var routeCollection = new RouteCollection(); routeCollection.Add(target.Object); var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls)); - + // Act - var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); // Assert - Assert.Equal(lowercaseUrl, stringVirtualPath); + Assert.Equal(lowercaseUrl, pathData.VirtualPath); + Assert.Same(target.Object, pathData.Router); + Assert.Empty(pathData.DataTokens); + } + + [Theory] + [MemberData("DataTokensTestData")] + public void GetVirtualPath_ReturnsDataTokens(RouteValueDictionary dataTokens, string routerName) + { + // Arrange + var virtualPath = "TestVirtualPath"; + + var pathContextValues = new RouteValueDictionary { { "controller", virtualPath } }; + + var pathContext = CreateVirtualPathContext( + pathContextValues, + GetRouteOptions(), + routerName); + + var route = CreateTemplateRoute("{controller}", routerName, dataTokens); + var routeCollection = new RouteCollection(); + routeCollection.Add(route); + + var expectedDataTokens = dataTokens ?? new RouteValueDictionary(); + + // Act + var pathData = routeCollection.GetVirtualPath(pathContext); + + // Assert + Assert.NotNull(pathData); + Assert.Same(route, pathData.Router); + + Assert.Equal(virtualPath, pathData.VirtualPath); + + Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count); + foreach (var dataToken in expectedDataTokens) + { + Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key)); + Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]); + } } [Fact] @@ -235,14 +276,17 @@ namespace Microsoft.AspNet.Routing // Arrange var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "RouteName", "Route3" }); var virtualPathContext = CreateVirtualPathContext( - routeName: "RouteName", + routeName: "RouteName", options: GetRouteOptions(lowercaseUrls)); // Act - var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); // Assert - Assert.Equal(expectedUrl, stringVirtualPath); + Assert.Equal(expectedUrl, pathData.VirtualPath); + var namedRouter = Assert.IsAssignableFrom(pathData.Router); + Assert.Equal(virtualPathContext.RouteName, namedRouter.Name); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -270,10 +314,13 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext(routeName: "Route1", options: GetRouteOptions(true)); // Act - var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); // Assert - Assert.Equal("route1", stringVirtualPath); + Assert.Equal("route1", pathData.VirtualPath); + var namedRouter = Assert.IsAssignableFrom(pathData.Router); + Assert.Equal("Route1", namedRouter.Name); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -290,7 +337,7 @@ namespace Microsoft.AspNet.Routing // Act & Assert var ex = Assert.Throws(() => routeCollection.GetVirtualPath(virtualPathContext)); Assert.Equal( - "The supplied route name 'ambiguousRoute' is ambiguous and matched more than one route.", + "The supplied route name 'ambiguousRoute' is ambiguous and matched more than one route.", ex.Message); } @@ -341,9 +388,12 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext("Match", options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + var namedRouter = Assert.IsAssignableFrom(pathData.Router); + Assert.Equal("Match", namedRouter.Name); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -368,9 +418,12 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext("Match", options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + var namedRouter = Assert.IsAssignableFrom(pathData.Router); + Assert.Equal("Match", namedRouter.Name); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -395,9 +448,12 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext("Match", options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + var namedRouter = Assert.IsAssignableFrom(pathData.Router); + Assert.Equal("Match", namedRouter.Name); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -421,9 +477,11 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext(options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + Assert.Same(route1.Object, pathData.Router); + Assert.Empty(pathData.DataTokens); // All of these should be called route1.Verify(r => r.GetVirtualPath(It.IsAny()), Times.Once()); @@ -483,9 +541,11 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext(options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + Assert.Same(route2.Object, pathData.Router); + Assert.Empty(pathData.DataTokens); // All of these should be called route1.Verify(r => r.GetVirtualPath(It.IsAny()), Times.Once()); @@ -514,9 +574,11 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext(options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + Assert.Same(route3.Object, pathData.Router); + Assert.Empty(pathData.DataTokens); // All of these should be called route1.Verify(r => r.GetVirtualPath(It.IsAny()), Times.Once()); @@ -545,9 +607,11 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext(options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + Assert.Same(route2.Object, pathData.Router); + Assert.Empty(pathData.DataTokens); route1.Verify(r => r.GetVirtualPath(It.IsAny()), Times.Once()); route2.Verify(r => r.GetVirtualPath(It.IsAny()), Times.Once()); @@ -578,9 +642,11 @@ namespace Microsoft.AspNet.Routing var virtualPathContext = CreateVirtualPathContext(options: options); // Act - var path = routeCollection.GetVirtualPath(virtualPathContext); + var pathData = routeCollection.GetVirtualPath(virtualPathContext); - Assert.Equal("best", path); + Assert.Equal("best", pathData.VirtualPath); + Assert.Same(route3.Object, pathData.Router); + Assert.Empty(pathData.DataTokens); // All of these should be called route1.Verify(r => r.GetVirtualPath(It.IsAny()), Times.Once()); @@ -650,11 +716,28 @@ namespace Microsoft.AspNet.Routing var context = CreateVirtualPathContext(values, options: GetRouteOptions(lowercaseUrls)); // Act - var path = routeCollection.GetVirtualPath(context); + var pathData = routeCollection.GetVirtualPath(context); // Assert Assert.True(context.IsBound); - Assert.Equal(expectedUrl, path); + Assert.Equal(expectedUrl, pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + } + + // DataTokens test data for RouterCollection.GetVirtualPath + public static IEnumerable DataTokensTestData + { + get + { + yield return new object[] { null, null }; + yield return new object[] { new RouteValueDictionary(), null }; + yield return new object[] { new RouteValueDictionary() { { "tokenKey", "tokenValue" } }, null }; + + yield return new object[] { null, "routerA" }; + yield return new object[] { new RouteValueDictionary(), "routerA" }; + yield return new object[] { new RouteValueDictionary() { { "tokenKey", "tokenValue" } }, "routerA" }; + } } private static async Task SetUp(bool enabled, bool handled) @@ -730,7 +813,8 @@ namespace Microsoft.AspNet.Routing target .Setup(e => e.GetVirtualPath(It.IsAny())) .Callback(c => c.IsBound = accept && c.RouteName == name) - .Returns(c => c.RouteName == name ? matchValue : null) + .Returns(c => + c.RouteName == name ? new VirtualPathData(target.Object, matchValue) : null) .Verifiable(); target @@ -746,7 +830,10 @@ namespace Microsoft.AspNet.Routing return target.Object; } - private static TemplateRoute CreateTemplateRoute(string template) + private static TemplateRoute CreateTemplateRoute( + string template, + string routerName = null, + RouteValueDictionary dataTokens = null) { var target = new Mock(MockBehavior.Strict); target @@ -755,7 +842,15 @@ namespace Microsoft.AspNet.Routing .Returns(rc => null); var resolverMock = new Mock(); - return new TemplateRoute(target.Object, template, resolverMock.Object); + + return new TemplateRoute( + target.Object, + routerName, + template, + defaults: null, + constraints: null, + dataTokens: dataTokens, + inlineConstraintResolver: resolverMock.Object); } private static VirtualPathContext CreateVirtualPathContext( @@ -791,7 +886,8 @@ namespace Microsoft.AspNet.Routing private static VirtualPathContext CreateVirtualPathContext( RouteValueDictionary values, - RouteOptions options = null) + RouteOptions options = null, + string routeName = null) { var optionsAccessor = new Mock>(MockBehavior.Strict); optionsAccessor.SetupGet(o => o.Options).Returns(options); @@ -802,8 +898,11 @@ namespace Microsoft.AspNet.Routing context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory))) .Returns(NullLoggerFactory.Instance); - - return new VirtualPathContext(context.Object, null, values); + return new VirtualPathContext( + context.Object, + ambientValues: null, + values: values, + routeName: routeName); } private static RouteContext CreateRouteContext( @@ -846,7 +945,7 @@ namespace Microsoft.AspNet.Routing target .Setup(e => e.GetVirtualPath(It.IsAny())) .Callback(c => c.IsBound = accept) - .Returns(accept || match ? matchValue : null) + .Returns(accept || match ? new VirtualPathData(target.Object, matchValue) : null) .Verifiable(); target diff --git a/test/Microsoft.AspNet.Routing.Tests/RouterMiddlewareTest.cs b/test/Microsoft.AspNet.Routing.Tests/RouterMiddlewareTest.cs index e217af91e3..75bc99f374 100644 --- a/test/Microsoft.AspNet.Routing.Tests/RouterMiddlewareTest.cs +++ b/test/Microsoft.AspNet.Routing.Tests/RouterMiddlewareTest.cs @@ -180,9 +180,9 @@ namespace Microsoft.AspNet.Routing _isHandled = isHandled; } - public string GetVirtualPath(VirtualPathContext context) + public VirtualPathData GetVirtualPath(VirtualPathContext context) { - return ""; + return new VirtualPathData(this, ""); } public Task RouteAsync(RouteContext context) diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs index 727d7e06b7..a36c9c5dda 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #if DNX451 @@ -563,7 +563,7 @@ namespace Microsoft.AspNet.Routing.Template // Act await route.RouteAsync(context); - // Assert + // Assert Assert.True(context.IsHandled); Assert.True(routeValues.ContainsKey("ssn")); Assert.Equal("123-456-7890", routeValues["ssn"]); @@ -949,11 +949,13 @@ namespace Microsoft.AspNet.Routing.Template var context = CreateVirtualPathContext(new { controller = "Home" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert Assert.True(context.IsBound); - Assert.Equal("Home", path); + Assert.Equal("Home", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -971,6 +973,91 @@ namespace Microsoft.AspNet.Routing.Template Assert.Null(path); } + [Theory] + [MemberData("DataTokensTestData")] + public void GetVirtualPath_ReturnsDataTokens_WhenTargetReturnsVirtualPathData( + RouteValueDictionary dataTokens) + { + // Arrange + var path = "TestPath"; + + var target = new Mock(MockBehavior.Strict); + target + .Setup(r => r.GetVirtualPath(It.IsAny())) + .Callback(c => c.IsBound = true) + .Returns(() => new VirtualPathData(target.Object, path, dataTokens)); + + var routeDataTokens = + new RouteValueDictionary() { { "ThisShouldBeIgnored", "" } }; + + var route = CreateRoute( + target.Object, + "{controller}", + defaults: null, + dataTokens: routeDataTokens); + var context = CreateVirtualPathContext(new { controller = path }); + + var expectedDataTokens = dataTokens ?? new RouteValueDictionary(); + + // Act + var pathData = route.GetVirtualPath(context); + + // Assert + Assert.NotNull(pathData); + Assert.Same(target.Object, pathData.Router); + Assert.Equal(path, pathData.VirtualPath); + Assert.NotNull(pathData.DataTokens); + + Assert.DoesNotContain(routeDataTokens.First().Key, pathData.DataTokens.Keys); + + Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count); + foreach (var dataToken in expectedDataTokens) + { + Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key)); + Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]); + } + } + + [Theory] + [MemberData("DataTokensTestData")] + public void GetVirtualPath_ReturnsDataTokens_WhenTargetReturnsNullVirtualPathData( + RouteValueDictionary dataTokens) + { + // Arrange + var path = "TestPath"; + + var target = new Mock(MockBehavior.Strict); + target + .Setup(r => r.GetVirtualPath(It.IsAny())) + .Callback(c => c.IsBound = true) + .Returns(() => null); + + var route = CreateRoute( + target.Object, + "{controller}", + defaults: null, + dataTokens: dataTokens); + var context = CreateVirtualPathContext(new { controller = path }); + + var expectedDataTokens = dataTokens ?? new RouteValueDictionary(); + + // Act + var pathData = route.GetVirtualPath(context); + + // Assert + Assert.NotNull(pathData); + Assert.Same(route, pathData.Router); + Assert.Equal(path, pathData.VirtualPath); + Assert.NotNull(pathData.DataTokens); + + Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count); + foreach (var dataToken in expectedDataTokens) + { + Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key)); + Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]); + } + } + [Fact] public void GetVirtualPath_ValuesRejectedByHandler_StillGeneratesPath() { @@ -979,11 +1066,13 @@ namespace Microsoft.AspNet.Routing.Template var context = CreateVirtualPathContext(new { controller = "Home" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert Assert.False(context.IsBound); - Assert.Equal("Home", path); + Assert.Equal("Home", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -994,11 +1083,13 @@ namespace Microsoft.AspNet.Routing.Template var context = CreateVirtualPathContext(new { action = "Index" }, new { controller = "Home" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert Assert.True(context.IsBound); - Assert.Equal("Home/Index", path); + Assert.Equal("Home/Index", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -1027,19 +1118,21 @@ namespace Microsoft.AspNet.Routing.Template // Arrange var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" }); - var r = CreateRoute( + var route = CreateRoute( "{p1}/{p2}", new { p2 = "catchall" }, true, new RouteValueDictionary(new { p2 = "\\d{4}" })); // Act - var virtualPath = r.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert Assert.True(context.IsBound); - Assert.NotNull(virtualPath); - Assert.Equal("hello/1234", virtualPath); + Assert.NotNull(pathData); + Assert.Equal("hello/1234", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -1065,23 +1158,24 @@ namespace Microsoft.AspNet.Routing.Template [Fact] public void RouteWithCatchAllAcceptsConstraints() { - // Arrange // Arrange var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" }); - TemplateRoute r = CreateRoute( + TemplateRoute route = CreateRoute( "{p1}/{*p2}", new { p2 = "catchall" }, true, new RouteValueDictionary(new { p2 = "\\d{4}" })); // Act - var virtualPath = r.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert Assert.True(context.IsBound); - Assert.NotNull(virtualPath); - Assert.Equal("hello/1234", virtualPath); + Assert.NotNull(pathData); + Assert.Equal("hello/1234", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -1099,19 +1193,21 @@ namespace Microsoft.AspNet.Routing.Template .Returns(true) .Verifiable(); - TemplateRoute r = CreateRoute( + TemplateRoute route = CreateRoute( "{p1}/{p2}", new { p2 = "catchall" }, true, new RouteValueDictionary(new { p2 = target.Object })); // Act - var virtualPath = r.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert Assert.True(context.IsBound); - Assert.NotNull(virtualPath); - Assert.Equal("hello/1234", virtualPath); + Assert.NotNull(pathData); + Assert.Equal("hello/1234", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); target.VerifyAll(); } @@ -1135,10 +1231,13 @@ namespace Microsoft.AspNet.Routing.Template var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Store", path); + Assert.Equal("Home/Store", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + Assert.Equal(expectedValues, childContext.ProvidedValues); } @@ -1161,10 +1260,13 @@ namespace Microsoft.AspNet.Routing.Template new { controller = "Home", action = "Store", area = "Admin" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Admin/Home/Store", path); + Assert.Equal("Admin/Home/Store", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + Assert.Equal(expectedValues, childContext.ProvidedValues); } @@ -1186,10 +1288,13 @@ namespace Microsoft.AspNet.Routing.Template var expectedValues = new RouteValueDictionary(new { controller = "Home", action = "Store" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Store?id=5", path); + Assert.Equal("Home/Store?id=5", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + Assert.Equal(expectedValues, childContext.ProvidedValues); } @@ -1214,10 +1319,13 @@ namespace Microsoft.AspNet.Routing.Template new { controller = "Home", action = "Store", extra = "42" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("slug/Home/Store", path); + Assert.Equal("slug/Home/Store", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + Assert.Equal(expectedValues, constraint.Values); } @@ -1242,10 +1350,13 @@ namespace Microsoft.AspNet.Routing.Template new { controller = "Home", action = "Store" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("slug/Home/Store", path); + Assert.Equal("slug/Home/Store", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + Assert.Equal(expectedValues, constraint.Values); } @@ -1269,10 +1380,13 @@ namespace Microsoft.AspNet.Routing.Template new { controller = "Shopping", action = "Index" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("slug/Shopping", path); + Assert.Equal("slug/Shopping", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + Assert.Equal(expectedValues, constraint.Values); } @@ -1297,10 +1411,13 @@ namespace Microsoft.AspNet.Routing.Template new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("slug/Home/Store", path); + Assert.Equal("slug/Home/Store", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); + Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key)); } @@ -1313,10 +1430,12 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", id = 4 }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/4", path); + Assert.Equal("Home/Index/4", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -1343,10 +1462,12 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", id = 98 }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/98", path); + Assert.Equal("Home/Index/98", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -1358,10 +1479,12 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index", path); + Assert.Equal("Home/Index", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -1388,10 +1511,12 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", id = 14 }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/14", path); + Assert.Equal("Home/Index/14", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] @@ -1409,16 +1534,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", name = "products" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/products", path); + Assert.Equal("Home/Index/products", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_ParameterPresentInValues() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/{name}.{format?}", defaults: null, @@ -1429,16 +1556,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", name = "products", format = "xml" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/products.xml", path); + Assert.Equal("Home/Index/products.xml", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/{name}.{format?}", defaults: null, @@ -1449,16 +1578,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", name = "products" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/products", path); + Assert.Equal("Home/Index/products", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_ParameterPresentInValuesAndDefaults() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/{name}.{format?}", defaults: new { format = "json" }, @@ -1469,16 +1600,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", name = "products", format = "xml" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/products.xml", path); + Assert.Equal("Home/Index/products.xml", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/{name}.{format?}", defaults: new { format = "json" }, @@ -1489,16 +1622,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", name = "products" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/products", path); + Assert.Equal("Home/Index/products", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_ParameterNotPresentInTemplate_PresentInValues() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/{name}", defaults: null, @@ -1509,16 +1644,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", name = "products", format = "json" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/products?format=json", path); + Assert.Equal("Home/Index/products?format=json", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/.{name?}", defaults: null, @@ -1529,16 +1666,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home", name = "products" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/.products", path); + Assert.Equal("Home/Index/.products", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/.{name?}", defaults: null, @@ -1549,16 +1688,18 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index/", path); + Assert.Equal("Home/Index/", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } [Fact] public void GetVirtualPath_OptionalParameter_InSimpleSegment() { - // Arrange + // Arrange var route = CreateRoute( template: "{controller}/{action}/{name?}", defaults: null, @@ -1569,10 +1710,12 @@ namespace Microsoft.AspNet.Routing.Template values: new { action = "Index", controller = "Home" }); // Act - var path = route.GetVirtualPath(context); + var pathData = route.GetVirtualPath(context); // Assert - Assert.Equal("Home/Index", path); + Assert.Equal("Home/Index", pathData.VirtualPath); + Assert.Same(route, pathData.Router); + Assert.Empty(pathData.DataTokens); } private static VirtualPathContext CreateVirtualPathContext(object values) @@ -1771,6 +1914,17 @@ namespace Microsoft.AspNet.Routing.Template #endregion + // DataTokens test data for TemplateRoute.GetVirtualPath + public static IEnumerable DataTokensTestData + { + get + { + yield return new object[] { null }; + yield return new object[] { new RouteValueDictionary() }; + yield return new object[] { new RouteValueDictionary() { { "tokenKeyA", "tokenValueA" } } }; + } + } + private static IRouteBuilder CreateRouteBuilder() { var routeBuilder = new RouteBuilder(); @@ -1816,13 +1970,17 @@ namespace Microsoft.AspNet.Routing.Template inlineConstraintResolver: _inlineConstraintResolver); } - private static TemplateRoute CreateRoute(IRouter target, string template, object defaults) + private static TemplateRoute CreateRoute( + IRouter target, + string template, + object defaults, + RouteValueDictionary dataTokens = null) { return new TemplateRoute(target, template, new RouteValueDictionary(defaults), constraints: null, - dataTokens: null, + dataTokens: dataTokens, inlineConstraintResolver: _inlineConstraintResolver); } diff --git a/test/Microsoft.AspNet.Routing.Tests/VirtualPathDataTests.cs b/test/Microsoft.AspNet.Routing.Tests/VirtualPathDataTests.cs new file mode 100644 index 0000000000..af5f1dad02 --- /dev/null +++ b/test/Microsoft.AspNet.Routing.Tests/VirtualPathDataTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if DNX451 +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Routing +{ + public class VirtualPathDataTests + { + [Fact] + public void Constructor_CreatesEmptyDataTokensIfNull() + { + // Arrange + var router = new Mock().Object; + var path = "virtual path"; + + // Act + var pathData = new VirtualPathData(router, path, null); + + // Assert + Assert.Same(router, pathData.Router); + Assert.Same(path, pathData.VirtualPath); + Assert.NotNull(pathData.DataTokens); + Assert.Empty(pathData.DataTokens); + } + + [Fact] + public void Constructor_CopiesDataTokens() + { + // Arrange + var router = new Mock().Object; + var path = "virtual path"; + var dataTokens = new RouteValueDictionary(); + dataTokens["TestKey"] = "TestValue"; + + // Act + var pathData = new VirtualPathData(router, path, dataTokens); + + // Assert + Assert.Same(router, pathData.Router); + Assert.Same(path, pathData.VirtualPath); + Assert.NotNull(pathData.DataTokens); + Assert.Equal("TestValue", pathData.DataTokens["TestKey"]); + Assert.Equal(1, pathData.DataTokens.Count); + Assert.NotSame(dataTokens, pathData.DataTokens); + } + + [Fact] + public void VirtualPath_ReturnsEmptyStringIfNull() + { + // Arrange + var router = new Mock().Object; + + // Act + var pathData = new VirtualPathData(router, virtualPath: null); + + // Assert + Assert.Same(router, pathData.Router); + Assert.Equal(string.Empty, pathData.VirtualPath); + Assert.NotNull(pathData.DataTokens); + Assert.Empty(pathData.DataTokens); + } + } +} +#endif \ No newline at end of file