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