* Adding test
This commit is contained in:
parent
940fb7ba78
commit
cd761a644d
|
|
@ -131,24 +131,24 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
|
|
||||||
public int Compare(LinkGenerationMatch x, LinkGenerationMatch y)
|
public int Compare(LinkGenerationMatch x, LinkGenerationMatch y)
|
||||||
{
|
{
|
||||||
// For these comparisons lower is better.
|
// For this comparison lower is better.
|
||||||
|
|
||||||
if (x.Entry.Order != y.Entry.Order)
|
if (x.Entry.Order != y.Entry.Order)
|
||||||
{
|
{
|
||||||
return x.Entry.Order.CompareTo(y.Entry.Order);
|
return x.Entry.Order.CompareTo(y.Entry.Order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (x.Entry.GenerationPrecedence != y.Entry.GenerationPrecedence)
|
||||||
|
{
|
||||||
|
// Reversed because higher is better
|
||||||
|
return y.Entry.GenerationPrecedence.CompareTo(x.Entry.GenerationPrecedence);
|
||||||
|
}
|
||||||
|
|
||||||
if (x.IsFallbackMatch != y.IsFallbackMatch)
|
if (x.IsFallbackMatch != y.IsFallbackMatch)
|
||||||
{
|
{
|
||||||
// A fallback match is worse than a non-fallback
|
// A fallback match is worse than a non-fallback
|
||||||
return x.IsFallbackMatch.CompareTo(y.IsFallbackMatch);
|
return x.IsFallbackMatch.CompareTo(y.IsFallbackMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x.Entry.Precedence != y.Entry.Precedence)
|
|
||||||
{
|
|
||||||
return x.Entry.Precedence.CompareTo(y.Entry.Precedence);
|
|
||||||
}
|
|
||||||
|
|
||||||
return StringComparer.Ordinal.Compare(x.Entry.TemplateText, y.Entry.TemplateText);
|
return StringComparer.Ordinal.Compare(x.Entry.TemplateText, y.Entry.TemplateText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
Defaults = routeInfo.Defaults,
|
Defaults = routeInfo.Defaults,
|
||||||
Constraints = routeInfo.Constraints,
|
Constraints = routeInfo.Constraints,
|
||||||
Order = routeInfo.Order,
|
Order = routeInfo.Order,
|
||||||
Precedence = routeInfo.Precedence,
|
GenerationPrecedence = routeInfo.GenerationPrecedence,
|
||||||
RequiredLinkValues = routeInfo.ActionDescriptor.RouteValueDefaults,
|
RequiredLinkValues = routeInfo.ActionDescriptor.RouteValueDefaults,
|
||||||
RouteGroup = routeInfo.RouteGroup,
|
RouteGroup = routeInfo.RouteGroup,
|
||||||
Template = routeInfo.ParsedTemplate,
|
Template = routeInfo.ParsedTemplate,
|
||||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
matchingEntries.Add(new AttributeRouteMatchingEntry()
|
matchingEntries.Add(new AttributeRouteMatchingEntry()
|
||||||
{
|
{
|
||||||
Order = routeInfo.Order,
|
Order = routeInfo.Order,
|
||||||
Precedence = routeInfo.Precedence,
|
Precedence = routeInfo.MatchPrecedence,
|
||||||
Target = _target,
|
Target = _target,
|
||||||
RouteName = routeInfo.Name,
|
RouteName = routeInfo.Name,
|
||||||
RouteTemplate = routeInfo.RouteTemplate,
|
RouteTemplate = routeInfo.RouteTemplate,
|
||||||
|
|
@ -269,7 +269,8 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
|
|
||||||
routeInfo.Order = action.AttributeRouteInfo.Order;
|
routeInfo.Order = action.AttributeRouteInfo.Order;
|
||||||
|
|
||||||
routeInfo.Precedence = AttributeRoutePrecedence.Compute(routeInfo.ParsedTemplate);
|
routeInfo.MatchPrecedence = AttributeRoutePrecedence.ComputeMatched(routeInfo.ParsedTemplate);
|
||||||
|
routeInfo.GenerationPrecedence = AttributeRoutePrecedence.ComputeGenerated(routeInfo.ParsedTemplate);
|
||||||
|
|
||||||
routeInfo.Name = action.AttributeRouteInfo.Name;
|
routeInfo.Name = action.AttributeRouteInfo.Name;
|
||||||
|
|
||||||
|
|
@ -314,7 +315,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
|
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
public decimal Precedence { get; set; }
|
public decimal MatchPrecedence { get; set; }
|
||||||
|
|
||||||
|
public decimal GenerationPrecedence { get; set; }
|
||||||
|
|
||||||
public string RouteGroup { get; set; }
|
public string RouteGroup { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The precedence of the template.
|
/// The precedence of the template for link generation. Greater number means higher precedence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal Precedence { get; set; }
|
public decimal GenerationPrecedence { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the route.
|
/// The name of the route.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,12 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class AttributeRoutePrecedence
|
public static class AttributeRoutePrecedence
|
||||||
{
|
{
|
||||||
public static decimal Compute(RouteTemplate template)
|
// Compute the precedence for matching a provided url
|
||||||
|
// e.g.: /api/template == 1.1
|
||||||
|
// /api/template/{id} == 1.13
|
||||||
|
// /api/{id:int} == 1.2
|
||||||
|
// /api/template/{id:int} == 1.12
|
||||||
|
public static decimal ComputeMatched(RouteTemplate template)
|
||||||
{
|
{
|
||||||
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
||||||
// and 4 results in a combined precedence of 2.14 (decimal).
|
// and 4 results in a combined precedence of 2.14 (decimal).
|
||||||
|
|
@ -23,7 +28,31 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
var segment = template.Segments[i];
|
var segment = template.Segments[i];
|
||||||
|
|
||||||
var digit = ComputeDigit(segment);
|
var digit = ComputeMatchDigit(segment);
|
||||||
|
Debug.Assert(digit >= 0 && digit < 10);
|
||||||
|
|
||||||
|
precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return precedence;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the precedence for generating a url
|
||||||
|
// e.g.: /api/template == 5.5
|
||||||
|
// /api/template/{id} == 5.53
|
||||||
|
// /api/{id:int} == 5.4
|
||||||
|
// /api/template/{id:int} == 5.54
|
||||||
|
public static decimal ComputeGenerated(RouteTemplate template)
|
||||||
|
{
|
||||||
|
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
||||||
|
// and 4 results in a combined precedence of 2.14 (decimal).
|
||||||
|
var precedence = 0m;
|
||||||
|
|
||||||
|
for (var i = 0; i < template.Segments.Count; i++)
|
||||||
|
{
|
||||||
|
var segment = template.Segments[i];
|
||||||
|
|
||||||
|
var digit = ComputeGenerationDigit(segment);
|
||||||
Debug.Assert(digit >= 0 && digit < 10);
|
Debug.Assert(digit >= 0 && digit < 10);
|
||||||
|
|
||||||
precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
|
precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
|
||||||
|
|
@ -32,17 +61,49 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return precedence;
|
return precedence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Segments have the following order:
|
||||||
|
// 5 - Literal segments
|
||||||
|
// 4 - Multi-part segments && Constrained parameter segments
|
||||||
|
// 3 - Unconstrained parameter segements
|
||||||
|
// 2 - Constrained wildcard parameter segments
|
||||||
|
// 1 - Unconstrained wildcard parameter segments
|
||||||
|
private static int ComputeGenerationDigit(TemplateSegment segment)
|
||||||
|
{
|
||||||
|
if(segment.Parts.Count > 1)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
var part = segment.Parts[0];
|
||||||
|
if(part.IsLiteral)
|
||||||
|
{
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Assert(part.IsParameter);
|
||||||
|
var digit = part.IsCatchAll ? 1 : 3;
|
||||||
|
|
||||||
|
if (part.InlineConstraints != null && part.InlineConstraints.Any())
|
||||||
|
{
|
||||||
|
digit++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return digit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Segments have the following order:
|
// Segments have the following order:
|
||||||
// 1 - Literal segments
|
// 1 - Literal segments
|
||||||
// 2 - Constrained parameter segments / Multi-part segments
|
// 2 - Constrained parameter segments / Multi-part segments
|
||||||
// 3 - Unconstrained parameter segments
|
// 3 - Unconstrained parameter segments
|
||||||
// 4 - Constrained wildcard parameter segments
|
// 4 - Constrained wildcard parameter segments
|
||||||
// 5 - Unconstrained wildcard parameter segments
|
// 5 - Unconstrained wildcard parameter segments
|
||||||
private static int ComputeDigit(TemplateSegment segment)
|
private static int ComputeMatchDigit(TemplateSegment segment)
|
||||||
{
|
{
|
||||||
if (segment.Parts.Count > 1)
|
if (segment.Parts.Count > 1)
|
||||||
{
|
{
|
||||||
// Multi-part segments should appear after literal segments but before parameter segments
|
// Multi-part segments should appear after literal segments and along with parameter segments
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,12 +247,12 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entry1.Precedence = 1;
|
entry1.GenerationPrecedence = 0;
|
||||||
entries.Add(entry1);
|
entries.Add(entry1);
|
||||||
|
|
||||||
var entry2 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry2 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entry2.Order = 1;
|
entry2.Order = 1;
|
||||||
entry2.Precedence = 0;
|
entry2.GenerationPrecedence = 1;
|
||||||
entries.Add(entry2);
|
entries.Add(entry2);
|
||||||
|
|
||||||
var tree = new LinkGenerationDecisionTree(entries);
|
var tree = new LinkGenerationDecisionTree(entries);
|
||||||
|
|
@ -274,11 +274,11 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entry1.Precedence = 0;
|
entry1.GenerationPrecedence = 1;
|
||||||
entries.Add(entry1);
|
entries.Add(entry1);
|
||||||
|
|
||||||
var entry2 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry2 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entry2.Precedence = 1;
|
entry2.GenerationPrecedence = 0;
|
||||||
entries.Add(entry2);
|
entries.Add(entry2);
|
||||||
|
|
||||||
var tree = new LinkGenerationDecisionTree(entries);
|
var tree = new LinkGenerationDecisionTree(entries);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if DNX451
|
#if DNX451
|
||||||
|
using System;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.AspNet.Routing.Template;
|
using Microsoft.AspNet.Routing.Template;
|
||||||
using Microsoft.Extensions.OptionsModel;
|
using Microsoft.Extensions.OptionsModel;
|
||||||
|
|
@ -16,11 +17,25 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
[InlineData("Employees/{id}", "Employees/{employeeId}")]
|
[InlineData("Employees/{id}", "Employees/{employeeId}")]
|
||||||
[InlineData("abc", "def")]
|
[InlineData("abc", "def")]
|
||||||
[InlineData("{x:alpha}", "{x:int}")]
|
[InlineData("{x:alpha}", "{x:int}")]
|
||||||
public void Compute_IsEqual(string xTemplate, string yTemplate)
|
public void ComputeMatched_IsEqual(string xTemplate, string yTemplate)
|
||||||
{
|
{
|
||||||
// Arrange & Act
|
// Arrange & Act
|
||||||
var xPrededence = Compute(xTemplate);
|
var xPrededence = ComputeMatched(xTemplate);
|
||||||
var yPrededence = Compute(yTemplate);
|
var yPrededence = ComputeMatched(yTemplate);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(xPrededence, yPrededence);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Employees/{id}", "Employees/{employeeId}")]
|
||||||
|
[InlineData("abc", "def")]
|
||||||
|
[InlineData("{x:alpha}", "{x:int}")]
|
||||||
|
public void ComputeGenerated_IsEqual(string xTemplate, string yTemplate)
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var xPrededence = ComputeGenerated(xTemplate);
|
||||||
|
var yPrededence = ComputeGenerated(yTemplate);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(xPrededence, yPrededence);
|
Assert.Equal(xPrededence, yPrededence);
|
||||||
|
|
@ -47,23 +62,63 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
[InlineData("abc/{x:int}", "abc/{*x}")]
|
[InlineData("abc/{x:int}", "abc/{*x}")]
|
||||||
[InlineData("abc/{x}", "abc/{*x}")]
|
[InlineData("abc/{x}", "abc/{*x}")]
|
||||||
[InlineData("{x}/{y:int}", "{x}/{y}")]
|
[InlineData("{x}/{y:int}", "{x}/{y}")]
|
||||||
public void Compute_IsLessThan(string xTemplate, string yTemplate)
|
public void ComputeMatched_IsLessThan(string xTemplate, string yTemplate)
|
||||||
{
|
{
|
||||||
// Arrange & Act
|
// Arrange & Act
|
||||||
var xPrededence = Compute(xTemplate);
|
var xPrededence = ComputeMatched(xTemplate);
|
||||||
var yPrededence = Compute(yTemplate);
|
var yPrededence = ComputeMatched(yTemplate);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(xPrededence < yPrededence);
|
Assert.True(xPrededence < yPrededence);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static decimal Compute(string template)
|
[Theory]
|
||||||
|
[InlineData("abc", "a{x}")]
|
||||||
|
[InlineData("abc", "{x}c")]
|
||||||
|
[InlineData("abc", "{x:int}")]
|
||||||
|
[InlineData("abc", "{x}")]
|
||||||
|
[InlineData("abc", "{*x}")]
|
||||||
|
[InlineData("{x:int}", "{x}")]
|
||||||
|
[InlineData("{x:int}", "{*x}")]
|
||||||
|
[InlineData("a{x}", "{x}")]
|
||||||
|
[InlineData("{x}c", "{x}")]
|
||||||
|
[InlineData("a{x}", "{*x}")]
|
||||||
|
[InlineData("{x}c", "{*x}")]
|
||||||
|
[InlineData("{x}", "{*x}")]
|
||||||
|
[InlineData("{*x:maxlength(10)}", "{*x}")]
|
||||||
|
[InlineData("abc/def", "abc/{x:int}")]
|
||||||
|
[InlineData("abc/def", "abc/{x}")]
|
||||||
|
[InlineData("abc/def", "abc/{*x}")]
|
||||||
|
[InlineData("abc/{x:int}", "abc/{x}")]
|
||||||
|
[InlineData("abc/{x:int}", "abc/{*x}")]
|
||||||
|
[InlineData("abc/{x}", "abc/{*x}")]
|
||||||
|
[InlineData("{x}/{y:int}", "{x}/{y}")]
|
||||||
|
public void ComputeGenerated_IsGreaterThan(string xTemplate, string yTemplate)
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var xPrecedence = ComputeGenerated(xTemplate);
|
||||||
|
var yPrecedence = ComputeGenerated(yTemplate);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(xPrecedence > yPrecedence);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal ComputeMatched(string template)
|
||||||
|
{
|
||||||
|
return Compute(template, AttributeRoutePrecedence.ComputeMatched);
|
||||||
|
}
|
||||||
|
private static decimal ComputeGenerated(string template)
|
||||||
|
{
|
||||||
|
return Compute(template, AttributeRoutePrecedence.ComputeGenerated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal Compute(string template, Func<RouteTemplate, decimal> func)
|
||||||
{
|
{
|
||||||
var options = new Mock<IOptions<RouteOptions>>();
|
var options = new Mock<IOptions<RouteOptions>>();
|
||||||
options.SetupGet(o => o.Value).Returns(new RouteOptions());
|
options.SetupGet(o => o.Value).Returns(new RouteOptions());
|
||||||
|
|
||||||
var parsed = TemplateParser.Parse(template);
|
var parsed = TemplateParser.Parse(template);
|
||||||
return AttributeRoutePrecedence.Compute(parsed);
|
return func(parsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,167 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
Assert.False(context.IsHandled);
|
Assert.False(context.IsHandled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("template", "{*url:alpha}", "/template?url=dingo&id=5")]
|
||||||
|
[InlineData("{*url:alpha}", "{*url}", "/dingo?id=5")]
|
||||||
|
[InlineData("{id}", "{*url}", "/5?url=dingo")]
|
||||||
|
[InlineData("{id}", "{*url:alpha}", "/5?url=dingo")]
|
||||||
|
[InlineData("{id:int}", "{id}", "/5?url=dingo")]
|
||||||
|
[InlineData("template/api/{*url}", "template/api", "/template/api/dingo?id=5")]
|
||||||
|
[InlineData("template/api", "template/{*url}", "/template/api?url=dingo&id=5")]
|
||||||
|
[InlineData("template/api", "template/api{id}location", "/template/api?url=dingo&id=5")]
|
||||||
|
[InlineData("template/api{id}location", "template/{id:int}", "/template/api5location?url=dingo")]
|
||||||
|
public void AttributeRoute_GenerateLink(string firstTemplate, string secondTemplate, string expectedPath)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedGroup = CreateRouteGroup(0, firstTemplate);
|
||||||
|
|
||||||
|
string selectedGroup = null;
|
||||||
|
Action<VirtualPathContext> callback = ctx =>
|
||||||
|
{
|
||||||
|
selectedGroup = (string)ctx.ProvidedValues[AttributeRouting.RouteGroupKey];
|
||||||
|
ctx.IsBound = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var values = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{"url", "dingo" },
|
||||||
|
{"id", 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var route = CreateAttributeRoute(callback, firstTemplate, secondTemplate);
|
||||||
|
var context = CreateVirtualPathContext(
|
||||||
|
values: values,
|
||||||
|
ambientValues: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = route.GetVirtualPath(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(new PathString(expectedPath), result.VirtualPath);
|
||||||
|
Assert.Same(route, result.Router);
|
||||||
|
Assert.Empty(result.DataTokens);
|
||||||
|
Assert.Equal(expectedGroup, selectedGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AttributeRoute_GenerateLink_LongerTemplateWithDefaultIsMoreSpecific()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var firstTemplate = "template";
|
||||||
|
var secondTemplate = "template/{parameter:int=1003}";
|
||||||
|
|
||||||
|
var expectedGroup = CreateRouteGroup(0, secondTemplate);
|
||||||
|
|
||||||
|
string selectedGroup = null;
|
||||||
|
Action<VirtualPathContext> callback = ctx =>
|
||||||
|
{
|
||||||
|
selectedGroup = (string)ctx.ProvidedValues[AttributeRouting.RouteGroupKey];
|
||||||
|
ctx.IsBound = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var route = CreateAttributeRoute(callback, firstTemplate, secondTemplate);
|
||||||
|
var context = CreateVirtualPathContext(
|
||||||
|
values: null,
|
||||||
|
ambientValues: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = route.GetVirtualPath(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
// The Binder binds to /template
|
||||||
|
Assert.Equal(new PathString($"/template"), result.VirtualPath);
|
||||||
|
Assert.Same(route, result.Router);
|
||||||
|
Assert.Empty(result.DataTokens);
|
||||||
|
// Even though the path was /template, the group generated from was /template/{paramter:int=1003}
|
||||||
|
Assert.Equal(expectedGroup, selectedGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("template/{parameter:int=5}", "template", "/template/5")]
|
||||||
|
[InlineData("template/{parameter}", "template", "/template/5")]
|
||||||
|
[InlineData("template/{parameter}/{id}", "template/{parameter}", "/template/5/1234")]
|
||||||
|
public void AttributeRoute_GenerateLink_OrderingAgnostic(
|
||||||
|
string firstTemplate,
|
||||||
|
string secondTemplate,
|
||||||
|
string expectedPath)
|
||||||
|
{
|
||||||
|
var expectedGroup = CreateRouteGroup(0, firstTemplate);
|
||||||
|
|
||||||
|
string selectedGroup = null;
|
||||||
|
Action<VirtualPathContext> callback = ctx =>
|
||||||
|
{
|
||||||
|
selectedGroup = (string)ctx.ProvidedValues[AttributeRouting.RouteGroupKey];
|
||||||
|
ctx.IsBound = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var route = CreateAttributeRoute(callback, firstTemplate, secondTemplate);
|
||||||
|
var parameter = 5;
|
||||||
|
var id = 1234;
|
||||||
|
var values = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ nameof(parameter) , parameter},
|
||||||
|
{ nameof(id), id }
|
||||||
|
};
|
||||||
|
var context = CreateVirtualPathContext(
|
||||||
|
values: null,
|
||||||
|
ambientValues: values);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = route.GetVirtualPath(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(new PathString(expectedPath), result.VirtualPath);
|
||||||
|
Assert.Same(route, result.Router);
|
||||||
|
Assert.Empty(result.DataTokens);
|
||||||
|
Assert.Equal(expectedGroup, selectedGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("template", "template/{parameter}", "/template/5")]
|
||||||
|
[InlineData("template/{parameter}", "template/{parameter}/{id}", "/template/5/1234")]
|
||||||
|
[InlineData("template", "template/{parameter:int=5}", "/template/5")]
|
||||||
|
public void AttributeRoute_GenerateLink_UseAvailableVariables(
|
||||||
|
string firstTemplate,
|
||||||
|
string secondTemplate,
|
||||||
|
string expectedPath)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedGroup = CreateRouteGroup(0, secondTemplate);
|
||||||
|
|
||||||
|
string selectedGroup = null;
|
||||||
|
Action<VirtualPathContext> callback = ctx =>
|
||||||
|
{
|
||||||
|
selectedGroup = (string)ctx.ProvidedValues[AttributeRouting.RouteGroupKey];
|
||||||
|
ctx.IsBound = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var route = CreateAttributeRoute(callback, firstTemplate, secondTemplate);
|
||||||
|
var parameter = 5;
|
||||||
|
var id = 1234;
|
||||||
|
var values = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ nameof(parameter) , parameter},
|
||||||
|
{ nameof(id), id }
|
||||||
|
};
|
||||||
|
var context = CreateVirtualPathContext(
|
||||||
|
values: null,
|
||||||
|
ambientValues: values);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = route.GetVirtualPath(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(new PathString(expectedPath), result.VirtualPath);
|
||||||
|
Assert.Same(route, result.Router);
|
||||||
|
Assert.Empty(result.DataTokens);
|
||||||
|
Assert.Equal(expectedGroup, selectedGroup);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("template/5", "template/{parameter:int}")]
|
[InlineData("template/5", "template/{parameter:int}")]
|
||||||
[InlineData("template/5", "template/{parameter}")]
|
[InlineData("template/5", "template/{parameter}")]
|
||||||
|
|
@ -1228,10 +1389,10 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
||||||
entry1.Precedence = 1;
|
entry1.GenerationPrecedence = 2;
|
||||||
|
|
||||||
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
||||||
entry2.Precedence = 2;
|
entry2.GenerationPrecedence = 1;
|
||||||
|
|
||||||
var next = new StubRouter();
|
var next = new StubRouter();
|
||||||
|
|
||||||
|
|
@ -1254,10 +1415,10 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
||||||
entry1.Precedence = 2;
|
entry1.GenerationPrecedence = 1;
|
||||||
|
|
||||||
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
||||||
entry2.Precedence = 1;
|
entry2.GenerationPrecedence = 2;
|
||||||
|
|
||||||
var next = new StubRouter();
|
var next = new StubRouter();
|
||||||
|
|
||||||
|
|
@ -1280,10 +1441,10 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
||||||
entry1.Precedence = 1;
|
entry1.GenerationPrecedence = 2;
|
||||||
|
|
||||||
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
||||||
entry2.Precedence = 2;
|
entry2.GenerationPrecedence = 1;
|
||||||
|
|
||||||
var next = new StubRouter();
|
var next = new StubRouter();
|
||||||
|
|
||||||
|
|
@ -1308,10 +1469,10 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
var entry1 = CreateGenerationEntry("Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
|
||||||
entry1.Precedence = 1;
|
entry1.GenerationPrecedence = 2;
|
||||||
|
|
||||||
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
var entry2 = CreateGenerationEntry("Store", new { area = (string)null, action = "Edit", controller = "Store" });
|
||||||
entry2.Precedence = 2;
|
entry2.GenerationPrecedence = 1;
|
||||||
|
|
||||||
var next = new StubRouter();
|
var next = new StubRouter();
|
||||||
|
|
||||||
|
|
@ -1645,7 +1806,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
entry.TemplateMatcher = new TemplateMatcher(
|
entry.TemplateMatcher = new TemplateMatcher(
|
||||||
parsedRouteTemplate,
|
parsedRouteTemplate,
|
||||||
new RouteValueDictionary(new { test_route_group = routeGroup }));
|
new RouteValueDictionary(new { test_route_group = routeGroup }));
|
||||||
entry.Precedence = AttributeRoutePrecedence.Compute(parsedRouteTemplate);
|
entry.Precedence = AttributeRoutePrecedence.ComputeMatched(parsedRouteTemplate);
|
||||||
entry.Order = order;
|
entry.Order = order;
|
||||||
entry.Constraints = GetRouteConstriants(CreateConstraintResolver(), template, parsedRouteTemplate);
|
entry.Constraints = GetRouteConstriants(CreateConstraintResolver(), template, parsedRouteTemplate);
|
||||||
return entry;
|
return entry;
|
||||||
|
|
@ -1690,7 +1851,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
entry.Defaults = defaults;
|
entry.Defaults = defaults;
|
||||||
entry.Binder = new TemplateBinder(entry.Template, defaults);
|
entry.Binder = new TemplateBinder(entry.Template, defaults);
|
||||||
entry.Order = order;
|
entry.Order = order;
|
||||||
entry.Precedence = AttributeRoutePrecedence.Compute(entry.Template);
|
entry.GenerationPrecedence = AttributeRoutePrecedence.ComputeGenerated(entry.Template);
|
||||||
entry.RequiredLinkValues = new RouteValueDictionary(requiredValues);
|
entry.RequiredLinkValues = new RouteValueDictionary(requiredValues);
|
||||||
entry.RouteGroup = CreateRouteGroup(order, template);
|
entry.RouteGroup = CreateRouteGroup(order, template);
|
||||||
entry.Name = name;
|
entry.Name = name;
|
||||||
|
|
@ -1779,6 +1940,25 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
version: 1);
|
version: 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static InnerAttributeRoute CreateAttributeRoute(
|
||||||
|
Action<VirtualPathContext> virtualPathCallback,
|
||||||
|
string firstTemplate,
|
||||||
|
string secondTemplate)
|
||||||
|
{
|
||||||
|
var next = new Mock<IRouter>();
|
||||||
|
next.Setup(n => n.GetVirtualPath(It.IsAny<VirtualPathContext>())).Callback<VirtualPathContext>(virtualPathCallback)
|
||||||
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
|
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||||
|
var firstEntry = CreateGenerationEntry(firstTemplate, requiredValues: null);
|
||||||
|
var secondEntry = CreateGenerationEntry(secondTemplate, requiredValues: null);
|
||||||
|
|
||||||
|
return CreateAttributeRoute(
|
||||||
|
next.Object,
|
||||||
|
matchingRoutes,
|
||||||
|
new[] { secondEntry, firstEntry });
|
||||||
|
}
|
||||||
|
|
||||||
private static InnerAttributeRoute CreateRoutingAttributeRoute(
|
private static InnerAttributeRoute CreateRoutingAttributeRoute(
|
||||||
ILoggerFactory loggerFactory = null,
|
ILoggerFactory loggerFactory = null,
|
||||||
params AttributeRouteMatchingEntry[] entries)
|
params AttributeRouteMatchingEntry[] entries)
|
||||||
|
|
|
||||||
|
|
@ -741,7 +741,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Action_RouteValueInvalidation_DoesNotAffectActionAndController()
|
public void Action_RouteValueInvalidation_DoesNotAffectActionAndController()
|
||||||
{
|
{
|
||||||
// Arrage
|
// Arrange
|
||||||
var services = GetServices();
|
var services = GetServices();
|
||||||
var routeBuilder = new RouteBuilder()
|
var routeBuilder = new RouteBuilder()
|
||||||
{
|
{
|
||||||
|
|
@ -986,7 +986,6 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return services.Object;
|
return services.Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static IRouter GetRouter(
|
private static IRouter GetRouter(
|
||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
string mockRouteName,
|
string mockRouteName,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -79,16 +80,16 @@ mypartial
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var expected1 =
|
var expected1 =
|
||||||
@"Hello there!!
|
"Hello there!!" + Environment.NewLine +
|
||||||
Learn More
|
"Learn More" + Environment.NewLine +
|
||||||
Hi John ! You are in 2015 year and today is Thursday";
|
"Hi John ! You are in 2015 year and today is Thursday";
|
||||||
|
|
||||||
yield return new[] {"en-GB", expected1 };
|
yield return new[] {"en-GB", expected1 };
|
||||||
|
|
||||||
var expected2 =
|
var expected2 =
|
||||||
@"Bonjour!
|
"Bonjour!" + Environment.NewLine +
|
||||||
apprendre Encore Plus
|
"apprendre Encore Plus" + Environment.NewLine +
|
||||||
Salut John ! Vous êtes en 2015 an aujourd'hui est Thursday";
|
"Salut John ! Vous êtes en 2015 an aujourd'hui est Thursday";
|
||||||
yield return new[] { "fr", expected2 };
|
yield return new[] { "fr", expected2 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -500,7 +500,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
public async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path)
|
public async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedUrl = "/Bank";
|
var expectedUrl = "/Bank/Update";
|
||||||
var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/" + path);
|
var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/" + path);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue