зеркало из https://github.com/aspnet/Routing.git
Passing more data to on GetVirtualPath
For link generation to areas, we need to provide the set of values that the route could potentially provide. Basically if we know what action we want to reach, we want to know whether or not a given route could hit that action before giving it the OK to generate a link. For instance a route like '{controller}' couldn't hit an action like 'HomeController:DoACoolThing', since it can never provide a value for 'action'. This makes it possible for WebFX to make the right decision without changing the behavior of any of the routing constructs. This also has the side-effect of removing a class of order dependencies in routing that cause bad links to be generated.
This commit is contained in:
Родитель
90864bcd9c
Коммит
87a47f50cc
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing.Template
|
namespace Microsoft.AspNet.Routing.Template
|
||||||
|
@ -11,6 +12,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
private readonly IDictionary<string, object> _defaults;
|
private readonly IDictionary<string, object> _defaults;
|
||||||
private readonly IDictionary<string, IRouteConstraint> _constraints;
|
private readonly IDictionary<string, IRouteConstraint> _constraints;
|
||||||
private readonly IRouter _target;
|
private readonly IRouter _target;
|
||||||
|
private readonly Template _parsedTemplate;
|
||||||
private readonly string _routeTemplate;
|
private readonly string _routeTemplate;
|
||||||
private readonly TemplateMatcher _matcher;
|
private readonly TemplateMatcher _matcher;
|
||||||
private readonly TemplateBinder _binder;
|
private readonly TemplateBinder _binder;
|
||||||
|
@ -29,10 +31,10 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate);
|
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate);
|
||||||
|
|
||||||
// The parser will throw for invalid routes.
|
// The parser will throw for invalid routes.
|
||||||
var parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
||||||
|
|
||||||
_matcher = new TemplateMatcher(parsedTemplate);
|
_matcher = new TemplateMatcher(_parsedTemplate);
|
||||||
_binder = new TemplateBinder(parsedTemplate, _defaults);
|
_binder = new TemplateBinder(_parsedTemplate, _defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDictionary<string, object> Defaults
|
public IDictionary<string, object> Defaults
|
||||||
|
@ -85,7 +87,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
var values = _binder.GetAcceptedValues(context.AmbientValues, context.Values);
|
var values = _binder.GetAcceptedValues(context.AmbientValues, context.Values);
|
||||||
if (values == null)
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,25 +101,59 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the target can accept these values.
|
// 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 (path != null)
|
||||||
{
|
{
|
||||||
// If the target generates a value then that can short circuit.
|
// If the target generates a value then that can short circuit.
|
||||||
|
context.IsBound = true;
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
else if (!context.IsBound)
|
else if (!childContext.IsBound)
|
||||||
{
|
{
|
||||||
// The target has rejected these values.
|
// The target has rejected these values.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
path = _binder.BindValues(values);
|
path = _binder.BindValues(values);
|
||||||
if (path == null)
|
if (path != null)
|
||||||
{
|
{
|
||||||
context.IsBound = false;
|
context.IsBound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private VirtualPathContext CreateChildVirtualPathContext(
|
||||||
|
VirtualPathContext context,
|
||||||
|
IDictionary<string, object> 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ namespace Microsoft.AspNet.Routing
|
||||||
Values = values;
|
Values = values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IDictionary<string, object> ProvidedValues { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, object> AmbientValues { get; private set; }
|
public IDictionary<string, object> AmbientValues { get; private set; }
|
||||||
|
|
||||||
public HttpContext Context { get; private set; }
|
public HttpContext Context { get; private set; }
|
||||||
|
|
|
@ -284,6 +284,78 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
Assert.Equal("hello/1234", virtualPath);
|
Assert.Equal("hello/1234", virtualPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetVirtualPath_Sends_ProvidedValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
VirtualPathContext childContext = null;
|
||||||
|
var target = new Mock<IRouter>(MockBehavior.Strict);
|
||||||
|
target
|
||||||
|
.Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||||
|
.Callback<VirtualPathContext>(c => { childContext = c; c.IsBound = true; })
|
||||||
|
.Returns<string>(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<IRouter>(MockBehavior.Strict);
|
||||||
|
target
|
||||||
|
.Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||||
|
.Callback<VirtualPathContext>(c => { childContext = c; c.IsBound = true; })
|
||||||
|
.Returns<string>(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<IRouter>(MockBehavior.Strict);
|
||||||
|
target
|
||||||
|
.Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||||
|
.Callback<VirtualPathContext>(c => { childContext = c; c.IsBound = true; })
|
||||||
|
.Returns<string>(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)
|
private static VirtualPathContext CreateVirtualPathContext(object values)
|
||||||
{
|
{
|
||||||
return CreateVirtualPathContext(new RouteValueDictionary(values), null);
|
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);
|
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)
|
private static IRouter CreateTarget(bool accept = true)
|
||||||
{
|
{
|
||||||
var target = new Mock<IRouter>(MockBehavior.Strict);
|
var target = new Mock<IRouter>(MockBehavior.Strict);
|
||||||
|
@ -373,4 +455,4 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Загрузка…
Ссылка в новой задаче