Add support for best-effort link-generation
This change adds a feature needed for aspnet/Mvc#302 There's a new option in routing that allows link-generation to proceed when the route values cannot be validated. The key scenario for this is during development of an MVC site. Routing will refuse to generate a link to actions which don't exists, this is a breaking change from the MVC5 behavior. Setting UseBestEffortLinkGeneration will allow routing to return a value even when we can't match the action. This option will remain off by default - setting this to on will impact link-generation in a bunch of scenarios involving areas where we've improved the logic for MVC6. If you're considering leaving this on outside of development scenarios, then make sure to be as explicit with route values as possible (don't rely on ambient values). Functional tests to follow up in the MVC repo.
This commit is contained in:
parent
4e5fc2e2dd
commit
9ee946073a
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Logging;
|
||||
using Microsoft.AspNet.Routing.Logging.Internal;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
|
|
@ -18,7 +20,9 @@ namespace Microsoft.AspNet.Routing
|
|||
private readonly List<IRouter> _unnamedRoutes = new List<IRouter>();
|
||||
private readonly Dictionary<string, INamedRouter> _namedRoutes =
|
||||
new Dictionary<string, INamedRouter>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private ILogger _logger;
|
||||
private RouteOptions _options;
|
||||
|
||||
public IRouter this[int index]
|
||||
{
|
||||
|
|
@ -94,45 +98,103 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
public virtual string 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
|
||||
// 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;
|
||||
|
||||
if (!string.IsNullOrEmpty(context.RouteName))
|
||||
{
|
||||
var isValidated = false;
|
||||
string bestPath = null;
|
||||
INamedRouter matchedNamedRoute;
|
||||
_namedRoutes.TryGetValue(context.RouteName, out matchedNamedRoute);
|
||||
if (_namedRoutes.TryGetValue(context.RouteName, out matchedNamedRoute))
|
||||
{
|
||||
bestPath = matchedNamedRoute.GetVirtualPath(context);
|
||||
isValidated = context.IsBound;
|
||||
}
|
||||
|
||||
var virtualPath = matchedNamedRoute != null ? matchedNamedRoute.GetVirtualPath(context) : null;
|
||||
// If we get here and context.IsBound == true, then we know we have a match, we want to keep
|
||||
// iterating to see if we have multiple matches.
|
||||
foreach (var unnamedRoute in _unnamedRoutes)
|
||||
{
|
||||
var tempVirtualPath = unnamedRoute.GetVirtualPath(context);
|
||||
if (tempVirtualPath != null)
|
||||
{
|
||||
if (virtualPath != null)
|
||||
{
|
||||
// There was already a previous route which matched the name.
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatNamedRoutes_AmbiguousRoutesFound(context.RouteName));
|
||||
}
|
||||
// reset because we're sharing the context
|
||||
context.IsBound = false;
|
||||
|
||||
virtualPath = tempVirtualPath;
|
||||
var path = unnamedRoute.GetVirtualPath(context);
|
||||
if (path == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bestPath != null)
|
||||
{
|
||||
// There was already a previous route which matched the name.
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatNamedRoutes_AmbiguousRoutesFound(context.RouteName));
|
||||
}
|
||||
else if (context.IsBound)
|
||||
{
|
||||
// This is the first 'validated' match that we've found.
|
||||
bestPath = path;
|
||||
isValidated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(bestPath == null);
|
||||
|
||||
// This is the first 'unvalidated' match that we've found.
|
||||
bestPath = path;
|
||||
isValidated = false;
|
||||
}
|
||||
}
|
||||
|
||||
return virtualPath;
|
||||
if (isValidated || useBestEffort)
|
||||
{
|
||||
context.IsBound = isValidated;
|
||||
return bestPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string bestPath = null;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var route = this[i];
|
||||
|
||||
var path = route.GetVirtualPath(context);
|
||||
if (path != null)
|
||||
if (path == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.IsBound)
|
||||
{
|
||||
// This route has validated route values, short circuit.
|
||||
return path;
|
||||
}
|
||||
else if (bestPath == null)
|
||||
{
|
||||
// The values aren't validated, but this is the best we've seen so far
|
||||
bestPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
if (useBestEffort)
|
||||
{
|
||||
return bestPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void EnsureLogger(HttpContext context)
|
||||
|
|
@ -143,5 +205,13 @@ namespace Microsoft.AspNet.Routing
|
|||
_logger = factory.Create<RouteCollection>();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureOptions(HttpContext context)
|
||||
{
|
||||
if (_options == null)
|
||||
{
|
||||
_options = context.RequestServices.GetRequiredService<IOptions<RouteOptions>>().Options;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,5 +61,13 @@ namespace Microsoft.AspNet.Routing
|
|||
{"required", typeof(RequiredRouteConstraint) },
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value that enables best-effort link generation.
|
||||
///
|
||||
/// If enabled, link generation will use allow link generation to succeed when the set of values provided
|
||||
/// cannot be validated.
|
||||
/// </summary>
|
||||
public bool UseBestEffortLinkGeneration { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,24 +202,18 @@ 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)
|
||||
{
|
||||
// If the target generates a value then that can short circuit.
|
||||
context.IsBound = true;
|
||||
return path;
|
||||
}
|
||||
else if (!childContext.IsBound)
|
||||
{
|
||||
// The target has rejected these values.
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (path != null)
|
||||
{
|
||||
context.IsBound = true;
|
||||
}
|
||||
context.IsBound = childContext.IsBound;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
|
@ -282,7 +276,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
foreach (var inlineConstraint in parameter.InlineConstraints)
|
||||
{
|
||||
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return constraintBuilder.Build();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Logging;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -231,6 +232,300 @@ namespace Microsoft.AspNet.Routing
|
|||
Assert.Equal("The supplied route name 'ambiguousRoute' is ambiguous and matched more than one route.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_AmbiguousRoutes_RequiresRouteValueValidation_Error()
|
||||
{
|
||||
// Arrange
|
||||
var namedRoute = CreateNamedRoute("Ambiguous", accept: false);
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(namedRoute);
|
||||
|
||||
var innerRouteCollection = new RouteCollection();
|
||||
innerRouteCollection.Add(namedRoute);
|
||||
routeCollection.Add(innerRouteCollection);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext("Ambiguous", options: options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => routeCollection.GetVirtualPath(virtualPathContext));
|
||||
Assert.Equal("The supplied route name 'Ambiguous' is ambiguous and matched more than one route.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_NamedRoute_BestEffort_BestInTopCollection()
|
||||
{
|
||||
// Arrange
|
||||
var bestMatch = CreateNamedRoute("Match", accept: true, matchValue: "best");
|
||||
var noMatch = CreateNamedRoute("NoMatch", accept: true, matchValue: "bad");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(bestMatch);
|
||||
|
||||
var innerRouteCollection = new RouteCollection();
|
||||
innerRouteCollection.Add(noMatch);
|
||||
routeCollection.Add(innerRouteCollection);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext("Match", options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_NamedRoute_BestEffort_BestMatchInNestedCollection()
|
||||
{
|
||||
// Arrange
|
||||
var bestMatch = CreateNamedRoute("NoMatch", accept: true, matchValue: "bad");
|
||||
var noMatch = CreateNamedRoute("Match", accept: true, matchValue: "best");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(noMatch);
|
||||
|
||||
var innerRouteCollection = new RouteCollection();
|
||||
innerRouteCollection.Add(bestMatch);
|
||||
routeCollection.Add(innerRouteCollection);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext("Match", options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_NamedRoute_BestEffort_FirstRouteWins()
|
||||
{
|
||||
// Arrange
|
||||
var bestMatch = CreateNamedRoute("Match", accept: false, matchValue: "best");
|
||||
var noMatch = CreateNamedRoute("NoMatch", accept: false, matchValue: "bad");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(noMatch);
|
||||
|
||||
var innerRouteCollection = new RouteCollection();
|
||||
innerRouteCollection.Add(bestMatch);
|
||||
routeCollection.Add(innerRouteCollection);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext("Match", options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_BestEffort_FirstRouteWins()
|
||||
{
|
||||
// Arrange
|
||||
var route1 = CreateRoute(accept: false, match: true, matchValue: "best");
|
||||
var route2 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
var route3 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(route1.Object);
|
||||
routeCollection.Add(route2.Object);
|
||||
routeCollection.Add(route3.Object);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext(options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
|
||||
// All of these should be called
|
||||
route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_NoBestEffort_NoMatch()
|
||||
{
|
||||
// Arrange
|
||||
var route1 = CreateRoute(accept: false, match: true, matchValue: "best");
|
||||
var route2 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
var route3 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(route1.Object);
|
||||
routeCollection.Add(route2.Object);
|
||||
routeCollection.Add(route3.Object);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = false,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext(options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Null(path);
|
||||
|
||||
// All of these should be called
|
||||
route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_BestEffort_FirstRouteWins_WithNonMatchingRoutes()
|
||||
{
|
||||
// Arrange
|
||||
var route1 = CreateRoute(accept: false, match: false, matchValue: "bad");
|
||||
var route2 = CreateRoute(accept: false, match: true, matchValue: "best");
|
||||
var route3 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(route1.Object);
|
||||
routeCollection.Add(route2.Object);
|
||||
routeCollection.Add(route3.Object);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext(options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
|
||||
// All of these should be called
|
||||
route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_BestEffort_FirstValidatedValuesWins()
|
||||
{
|
||||
// Arrange
|
||||
var route1 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
var route2 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
var route3 = CreateRoute(accept: true, match: true, matchValue: "best");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(route1.Object);
|
||||
routeCollection.Add(route2.Object);
|
||||
routeCollection.Add(route3.Object);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext(options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
|
||||
// All of these should be called
|
||||
route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_BestEffort_FirstValidatedValuesWins_ShortCircuit()
|
||||
{
|
||||
// Arrange
|
||||
var route1 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
var route2 = CreateRoute(accept: true, match: true, matchValue: "best");
|
||||
var route3 = CreateRoute(accept: true, match: true, matchValue: "bad");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(route1.Object);
|
||||
routeCollection.Add(route2.Object);
|
||||
routeCollection.Add(route3.Object);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext(options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
|
||||
route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_BestEffort_FirstValidatedValuesWins_Nested()
|
||||
{
|
||||
// Arrange
|
||||
var route1 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
var route2 = CreateRoute(accept: false, match: true, matchValue: "bad");
|
||||
var route3 = CreateRoute(accept: true, match: true, matchValue: "best");
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(route1.Object);
|
||||
|
||||
var innerRouteCollection = new RouteCollection();
|
||||
innerRouteCollection.Add(route2.Object);
|
||||
innerRouteCollection.Add(route3.Object);
|
||||
routeCollection.Add(innerRouteCollection);
|
||||
|
||||
var options = new RouteOptions()
|
||||
{
|
||||
UseBestEffortLinkGeneration = true,
|
||||
};
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext(options: options);
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
Assert.Equal("best", path);
|
||||
|
||||
// All of these should be called
|
||||
route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
}
|
||||
|
||||
private static async Task<TestSink> SetUp(bool enabled, bool handled)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -256,7 +551,7 @@ namespace Microsoft.AspNet.Routing
|
|||
var routes = new RouteCollection();
|
||||
foreach (var routeName in routeNames)
|
||||
{
|
||||
var route1 = CreateNamedRoute(routeName);
|
||||
var route1 = CreateNamedRoute(routeName, accept: true);
|
||||
routes.Add(route1);
|
||||
}
|
||||
|
||||
|
|
@ -265,8 +560,8 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
private static RouteCollection GetNestedRouteCollection(string[] routeNames)
|
||||
{
|
||||
var rnd = new Random();
|
||||
int index = rnd.Next(0, routeNames.Length - 1);
|
||||
var random = new Random();
|
||||
int index = random.Next(0, routeNames.Length - 1);
|
||||
var first = routeNames.Take(index).ToArray();
|
||||
var second = routeNames.Skip(index).ToArray();
|
||||
|
||||
|
|
@ -279,12 +574,12 @@ namespace Microsoft.AspNet.Routing
|
|||
rc4.Add(rc2);
|
||||
|
||||
// Add a few unnamedRoutes.
|
||||
rc1.Add(CreateRoute().Object);
|
||||
rc2.Add(CreateRoute().Object);
|
||||
rc3.Add(CreateRoute().Object);
|
||||
rc3.Add(CreateRoute().Object);
|
||||
rc4.Add(CreateRoute().Object);
|
||||
rc4.Add(CreateRoute().Object);
|
||||
rc1.Add(CreateRoute(accept: false).Object);
|
||||
rc2.Add(CreateRoute(accept: false).Object);
|
||||
rc3.Add(CreateRoute(accept: false).Object);
|
||||
rc3.Add(CreateRoute(accept: false).Object);
|
||||
rc4.Add(CreateRoute(accept: false).Object);
|
||||
rc4.Add(CreateRoute(accept: false).Object);
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(rc1);
|
||||
|
|
@ -293,13 +588,18 @@ namespace Microsoft.AspNet.Routing
|
|||
return routeCollection;
|
||||
}
|
||||
|
||||
private static INamedRouter CreateNamedRoute(string name, bool accept = false)
|
||||
private static INamedRouter CreateNamedRoute(string name, bool accept = false, string matchValue = null)
|
||||
{
|
||||
if (matchValue == null)
|
||||
{
|
||||
matchValue = name;
|
||||
}
|
||||
|
||||
var target = new Mock<INamedRouter>(MockBehavior.Strict);
|
||||
target
|
||||
.Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||
.Callback<VirtualPathContext>(c => c.IsBound = accept)
|
||||
.Returns<VirtualPathContext>(c => name)
|
||||
.Callback<VirtualPathContext>(c => c.IsBound = accept && c.RouteName == name)
|
||||
.Returns<VirtualPathContext>(c => c.RouteName == name ? matchValue : null)
|
||||
.Verifiable();
|
||||
|
||||
target
|
||||
|
|
@ -315,36 +615,78 @@ namespace Microsoft.AspNet.Routing
|
|||
return target.Object;
|
||||
}
|
||||
|
||||
private static VirtualPathContext CreateVirtualPathContext(string routeName)
|
||||
private static VirtualPathContext CreateVirtualPathContext(
|
||||
string routeName = null,
|
||||
ILoggerFactory loggerFactory = null,
|
||||
RouteOptions options = null)
|
||||
{
|
||||
return new VirtualPathContext(null, null, null, routeName);
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
loggerFactory = NullLoggerFactory.Instance;
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
options = new RouteOptions();
|
||||
}
|
||||
|
||||
|
||||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||
|
||||
var optionsAccessor = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
|
||||
optionsAccessor.SetupGet(o => o.Options).Returns(options);
|
||||
|
||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
||||
.Returns(loggerFactory);
|
||||
context.Setup(m => m.RequestServices.GetService(typeof(IOptions<RouteOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
context.SetupGet(c => c.Request).Returns(request.Object);
|
||||
|
||||
return new VirtualPathContext(context.Object, null, null, routeName);
|
||||
}
|
||||
|
||||
private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
|
||||
private static RouteContext CreateRouteContext(
|
||||
string requestPath,
|
||||
ILoggerFactory loggerFactory = null,
|
||||
RouteOptions options = null)
|
||||
{
|
||||
if (factory == null)
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
factory = NullLoggerFactory.Instance;
|
||||
loggerFactory = NullLoggerFactory.Instance;
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
options = new RouteOptions();
|
||||
}
|
||||
|
||||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||
request.SetupGet(r => r.Path).Returns(new PathString(requestPath));
|
||||
|
||||
var optionsAccessor = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
|
||||
optionsAccessor.SetupGet(o => o.Options).Returns(options);
|
||||
|
||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
||||
.Returns(factory);
|
||||
.Returns(loggerFactory);
|
||||
context.Setup(m => m.RequestServices.GetService(typeof(IOptions<RouteOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
context.SetupGet(c => c.Request).Returns(request.Object);
|
||||
|
||||
return new RouteContext(context.Object);
|
||||
}
|
||||
|
||||
private static Mock<IRouter> CreateRoute(bool accept = true)
|
||||
private static Mock<IRouter> CreateRoute(
|
||||
bool accept = true,
|
||||
bool match = false,
|
||||
string matchValue = "value")
|
||||
{
|
||||
var target = new Mock<IRouter>(MockBehavior.Strict);
|
||||
target
|
||||
.Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||
.Callback<VirtualPathContext>(c => c.IsBound = accept)
|
||||
.Returns<VirtualPathContext>(null)
|
||||
.Returns(accept || match ? matchValue : null)
|
||||
.Verifiable();
|
||||
|
||||
target
|
||||
|
|
|
|||
|
|
@ -971,7 +971,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_RejectedByHandler()
|
||||
public void GetVirtualPath_ValuesRejectedByHandler_StillGeneratesPath()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}", accept: false);
|
||||
|
|
@ -982,7 +982,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
// Assert
|
||||
Assert.False(context.IsBound);
|
||||
Assert.Null(path);
|
||||
Assert.Equal("Home", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1026,7 +1026,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
// Arrange
|
||||
var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
|
||||
|
||||
TemplateRoute r = CreateRoute(
|
||||
var r = CreateRoute(
|
||||
"{p1}/{p2}",
|
||||
new { p2 = "catchall" },
|
||||
true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue