React to routing cleanup

This commit is contained in:
Ryan Nowak 2016-04-20 17:04:01 -07:00
parent d9141d6bf8
commit 87704c67e1
5 changed files with 129 additions and 269 deletions

View File

@ -4,43 +4,34 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Template; using Microsoft.AspNetCore.Routing.Template;
using Microsoft.AspNetCore.Routing.Tree; using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
namespace Microsoft.AspNetCore.Mvc.Internal namespace Microsoft.AspNetCore.Mvc.Internal
{ {
public class AttributeRoute : IRouter public class AttributeRoute : IRouter
{ {
private readonly IRouter _target; private readonly IRouter _handler;
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
private readonly IInlineConstraintResolver _constraintResolver; private readonly IServiceProvider _services;
private readonly ObjectPool<UriBuildingContext> _contextPool;
private readonly UrlEncoder _urlEncoder;
private readonly ILoggerFactory _loggerFactory;
private TreeRouter _router; private TreeRouter _router;
public AttributeRoute( public AttributeRoute(
IRouter target, IRouter handler,
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
IInlineConstraintResolver constraintResolver, IServiceProvider services)
ObjectPool<UriBuildingContext> contextPool,
UrlEncoder urlEncoder,
ILoggerFactory loggerFactory)
{ {
if (target == null) if (handler == null)
{ {
throw new ArgumentNullException(nameof(target)); throw new ArgumentNullException(nameof(handler));
} }
if (actionDescriptorCollectionProvider == null) if (actionDescriptorCollectionProvider == null)
@ -48,32 +39,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider)); throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider));
} }
if (constraintResolver == null) if (services == null)
{ {
throw new ArgumentNullException(nameof(constraintResolver)); throw new ArgumentNullException(nameof(services));
} }
if (contextPool == null) _handler = handler;
{
throw new ArgumentNullException(nameof(contextPool));
}
if (urlEncoder == null)
{
throw new ArgumentNullException(nameof(urlEncoder));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_target = target;
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
_constraintResolver = constraintResolver; _services = services;
_contextPool = contextPool;
_urlEncoder = urlEncoder;
_loggerFactory = loggerFactory;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -98,38 +71,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// it on startup. // it on startup.
if (_router == null || _router.Version != actions.Version) if (_router == null || _router.Version != actions.Version)
{ {
var entries = GetEntries(actions); var builder = _services.GetRequiredService<TreeRouteBuilder>();
_router = BuildRoute(entries, actions.Version); AddEntries(builder, actions);
_router = builder.Build(actions.Version);
} }
return _router; return _router;
} }
// internal for testing // internal for testing
internal AttributeRouteEntries GetEntries(ActionDescriptorCollection actions) internal void AddEntries(TreeRouteBuilder builder, ActionDescriptorCollection actions)
{ {
var entries = new AttributeRouteEntries(); var routeInfos = GetRouteInfos(actions.Items);
var routeInfos = GetRouteInfos(_constraintResolver, actions.Items);
// We're creating one TreeRouteLinkGenerationEntry per action. This allows us to match the intended // We're creating one TreeRouteLinkGenerationEntry per action. This allows us to match the intended
// action by expected route values, and then use the TemplateBinder to generate the link. // action by expected route values, and then use the TemplateBinder to generate the link.
foreach (var routeInfo in routeInfos) foreach (var routeInfo in routeInfos)
{ {
entries.LinkGenerationEntries.Add(new TreeRouteLinkGenerationEntry() builder.MapOutbound(
{ _handler,
// Using routeInfo.Defaults here WITHOUT adding the RouteGroupKey. We don't want to impact the routeInfo.RouteTemplate,
// behavior of link generation. new RouteValueDictionary(routeInfo.ActionDescriptor.RouteValueDefaults),
Binder = new TemplateBinder(_urlEncoder, _contextPool, routeInfo.ParsedTemplate, routeInfo.Defaults), routeInfo.RouteName,
Defaults = routeInfo.Defaults, routeInfo.Order);
Constraints = routeInfo.Constraints,
Order = routeInfo.Order,
GenerationPrecedence = routeInfo.GenerationPrecedence,
RequiredLinkValues = routeInfo.ActionDescriptor.RouteValueDefaults,
RouteGroup = routeInfo.RouteGroup,
Template = routeInfo.ParsedTemplate,
Name = routeInfo.Name,
});
} }
// We're creating one AttributeRouteMatchingEntry per group, so we need to identify the distinct set of // We're creating one AttributeRouteMatchingEntry per group, so we need to identify the distinct set of
@ -143,39 +107,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// //
// We then inject the route group as a default for the matcher so it gets passed back to MVC // We then inject the route group as a default for the matcher so it gets passed back to MVC
// for use in action selection. // for use in action selection.
var defaults = new RouteValueDictionary(routeInfo.Defaults); var entry = builder.MapInbound(
defaults[TreeRouter.RouteGroupKey] = routeInfo.RouteGroup; _handler,
routeInfo.RouteTemplate,
routeInfo.RouteName,
routeInfo.Order);
entries.MatchingEntries.Add(new TreeRouteMatchingEntry() entry.Defaults[TreeRouter.RouteGroupKey] = routeInfo.RouteGroup;
{
Order = routeInfo.Order,
Precedence = routeInfo.MatchPrecedence,
Target = _target,
RouteName = routeInfo.Name,
RouteTemplate = routeInfo.ParsedTemplate,
TemplateMatcher = new TemplateMatcher(routeInfo.ParsedTemplate, defaults),
Constraints = routeInfo.Constraints,
});
} }
return entries;
}
private TreeRouter BuildRoute(AttributeRouteEntries entries, int version)
{
var routeBuilder = new TreeRouteBuilder(_target, _loggerFactory);
foreach (var entry in entries.LinkGenerationEntries)
{
routeBuilder.Add(entry);
}
foreach (var entry in entries.MatchingEntries)
{
routeBuilder.Add(entry);
}
return routeBuilder.Build(version);
} }
private static IEnumerable<RouteInfo> GroupRouteInfosByGroupId(List<RouteInfo> routeInfos) private static IEnumerable<RouteInfo> GroupRouteInfosByGroupId(List<RouteInfo> routeInfos)
@ -193,9 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return routeInfosByGroupId.Values; return routeInfosByGroupId.Values;
} }
private static List<RouteInfo> GetRouteInfos( private static List<RouteInfo> GetRouteInfos(IReadOnlyList<ActionDescriptor> actions)
IInlineConstraintResolver constraintResolver,
IReadOnlyList<ActionDescriptor> actions)
{ {
var routeInfos = new List<RouteInfo>(); var routeInfos = new List<RouteInfo>();
var errors = new List<RouteInfo>(); var errors = new List<RouteInfo>();
@ -210,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var attributeRoutedActions = actions.Where(a => a.AttributeRouteInfo?.Template != null); var attributeRoutedActions = actions.Where(a => a.AttributeRouteInfo?.Template != null);
foreach (var action in attributeRoutedActions) foreach (var action in attributeRoutedActions)
{ {
var routeInfo = GetRouteInfo(constraintResolver, templateCache, action); var routeInfo = GetRouteInfo(templateCache, action);
if (routeInfo.ErrorMessage == null) if (routeInfo.ErrorMessage == null)
{ {
routeInfos.Add(routeInfo); routeInfos.Add(routeInfo);
@ -239,7 +176,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
private static RouteInfo GetRouteInfo( private static RouteInfo GetRouteInfo(
IInlineConstraintResolver constraintResolver,
Dictionary<string, RouteTemplate> templateCache, Dictionary<string, RouteTemplate> templateCache,
ActionDescriptor action) ActionDescriptor action)
{ {
@ -261,7 +197,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
ActionDescriptor = action, ActionDescriptor = action,
RouteGroup = constraint.RouteValue, RouteGroup = constraint.RouteValue,
RouteTemplate = action.AttributeRouteInfo.Template,
}; };
try try
@ -274,7 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
templateCache.Add(action.AttributeRouteInfo.Template, parsedTemplate); templateCache.Add(action.AttributeRouteInfo.Template, parsedTemplate);
} }
routeInfo.ParsedTemplate = parsedTemplate; routeInfo.RouteTemplate = parsedTemplate;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -284,12 +219,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
foreach (var kvp in action.RouteValueDefaults) foreach (var kvp in action.RouteValueDefaults)
{ {
foreach (var parameter in routeInfo.ParsedTemplate.Parameters) foreach (var parameter in routeInfo.RouteTemplate.Parameters)
{ {
if (string.Equals(kvp.Key, parameter.Name, StringComparison.OrdinalIgnoreCase)) if (string.Equals(kvp.Key, parameter.Name, StringComparison.OrdinalIgnoreCase))
{ {
routeInfo.ErrorMessage = Resources.FormatAttributeRoute_CannotContainParameter( routeInfo.ErrorMessage = Resources.FormatAttributeRoute_CannotContainParameter(
routeInfo.RouteTemplate, routeInfo.RouteTemplate.TemplateText,
kvp.Key, kvp.Key,
kvp.Value); kvp.Value);
@ -299,40 +234,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
routeInfo.Order = action.AttributeRouteInfo.Order; routeInfo.Order = action.AttributeRouteInfo.Order;
routeInfo.RouteName = action.AttributeRouteInfo.Name;
routeInfo.MatchPrecedence = RoutePrecedence.ComputeMatched(routeInfo.ParsedTemplate);
routeInfo.GenerationPrecedence = RoutePrecedence.ComputeGenerated(routeInfo.ParsedTemplate);
routeInfo.Name = action.AttributeRouteInfo.Name;
var constraintBuilder = new RouteConstraintBuilder(constraintResolver, routeInfo.RouteTemplate);
foreach (var parameter in routeInfo.ParsedTemplate.Parameters)
{
if (parameter.InlineConstraints != null)
{
if (parameter.IsOptional)
{
constraintBuilder.SetOptional(parameter.Name);
}
foreach (var inlineConstraint in parameter.InlineConstraints)
{
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
}
}
}
routeInfo.Constraints = constraintBuilder.Build();
routeInfo.Defaults = new RouteValueDictionary();
foreach (var parameter in routeInfo.ParsedTemplate.Parameters)
{
if (parameter.DefaultValue != null)
{
routeInfo.Defaults.Add(parameter.Name, parameter.DefaultValue);
}
}
return routeInfo; return routeInfo;
} }
@ -341,25 +243,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
public ActionDescriptor ActionDescriptor { get; set; } public ActionDescriptor ActionDescriptor { get; set; }
public IDictionary<string, IRouteConstraint> Constraints { get; set; }
public RouteValueDictionary Defaults { get; set; }
public string ErrorMessage { get; set; } public string ErrorMessage { get; set; }
public RouteTemplate ParsedTemplate { get; set; }
public int Order { get; set; } public int Order { get; set; }
public decimal MatchPrecedence { get; set; }
public decimal GenerationPrecedence { get; set; }
public string RouteGroup { get; set; } public string RouteGroup { get; set; }
public string RouteTemplate { get; set; } public string RouteName { get; set; }
public string Name { get; set; } public RouteTemplate RouteTemplate { get; set; }
} }
} }
} }

View File

@ -8,8 +8,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
public class AttributeRouteEntries public class AttributeRouteEntries
{ {
public List<TreeRouteLinkGenerationEntry> LinkGenerationEntries { get; } = new List<TreeRouteLinkGenerationEntry>(); public List<InboundRouteEntry> InboundEntries { get; } = new List<InboundRouteEntry>();
public List<TreeRouteMatchingEntry> MatchingEntries { get; } = new List<TreeRouteMatchingEntry>(); public List<OutboundRouteEntry> OutboundEntries { get; } = new List<OutboundRouteEntry>();
} }
} }

View File

@ -2,14 +2,9 @@
// 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;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
namespace Microsoft.AspNetCore.Mvc.Internal namespace Microsoft.AspNetCore.Mvc.Internal
{ {
@ -33,19 +28,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(services));
} }
var actionDescriptorProvider = services.GetRequiredService<IActionDescriptorCollectionProvider>();
var inlineConstraintResolver = services.GetRequiredService<IInlineConstraintResolver>();
var pool = services.GetRequiredService<ObjectPool<UriBuildingContext>>();
var urlEncoder = services.GetRequiredService<UrlEncoder>();
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
return new AttributeRoute( return new AttributeRoute(
target, target,
actionDescriptorProvider, services.GetRequiredService<IActionDescriptorCollectionProvider>(),
inlineConstraintResolver, services);
pool,
urlEncoder,
loggerFactory);
} }
} }
} }

View File

@ -10,14 +10,12 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Template; using Microsoft.AspNetCore.Routing.Template;
using Microsoft.AspNetCore.Routing.Tree; using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -105,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesLinkGenerationEntry() public void AttributeRoute_GetEntries_CreatesOutboundEntry()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -130,32 +128,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object); var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.LinkGenerationEntries, builder.OutboundEntries,
e => e =>
{ {
Assert.NotNull(e.Binder);
Assert.Empty(e.Constraints); Assert.Empty(e.Constraints);
Assert.Empty(e.Defaults); Assert.Empty(e.Defaults);
Assert.Equal(RoutePrecedence.ComputeGenerated(e.Template), e.GenerationPrecedence); Assert.Equal(RoutePrecedence.ComputeOutbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.Name); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(actions[0].RouteValueDefaults, e.RequiredLinkValues); Assert.Equal(actions[0].RouteValueDefaults.ToArray(), e.RequiredLinkValues.ToArray());
Assert.Equal("1", e.RouteGroup); Assert.Equal("api/Blog/{id}", e.RouteTemplate.TemplateText);
Assert.Equal("api/Blog/{id}", e.Template.TemplateText);
}); });
} }
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesLinkGenerationEntry_WithConstraint() public void AttributeRoute_GetEntries_CreatesOutboundEntry_WithConstraint()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -180,32 +176,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object); var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.LinkGenerationEntries, builder.OutboundEntries,
e => e =>
{ {
Assert.NotNull(e.Binder);
Assert.Single(e.Constraints, kvp => kvp.Key == "id"); Assert.Single(e.Constraints, kvp => kvp.Key == "id");
Assert.Empty(e.Defaults); Assert.Empty(e.Defaults);
Assert.Equal(RoutePrecedence.ComputeGenerated(e.Template), e.GenerationPrecedence); Assert.Equal(RoutePrecedence.ComputeOutbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.Name); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(actions[0].RouteValueDefaults, e.RequiredLinkValues); Assert.Equal(actions[0].RouteValueDefaults.ToArray(), e.RequiredLinkValues.ToArray());
Assert.Equal("1", e.RouteGroup); Assert.Equal("api/Blog/{id:int}", e.RouteTemplate.TemplateText);
Assert.Equal("api/Blog/{id:int}", e.Template.TemplateText);
}); });
} }
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesLinkGenerationEntry_WithDefault() public void AttributeRoute_GetEntries_CreatesOutboundEntry_WithDefault()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -230,27 +224,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object); var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.LinkGenerationEntries, builder.OutboundEntries,
e => e =>
{ {
Assert.NotNull(e.Binder);
Assert.Empty(e.Constraints); Assert.Empty(e.Constraints);
Assert.Equal(new RouteValueDictionary(new { slug = "hello" }), e.Defaults); Assert.Equal(new RouteValueDictionary(new { slug = "hello" }), e.Defaults);
Assert.Equal(RoutePrecedence.ComputeGenerated(e.Template), e.GenerationPrecedence); Assert.Equal(RoutePrecedence.ComputeOutbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.Name); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(actions[0].RouteValueDefaults, e.RequiredLinkValues); Assert.Equal(actions[0].RouteValueDefaults.ToArray(), e.RequiredLinkValues.ToArray());
Assert.Equal("1", e.RouteGroup); Assert.Equal("api/Blog/{*slug=hello}", e.RouteTemplate.TemplateText);
Assert.Equal("api/Blog/{*slug=hello}", e.Template.TemplateText);
}); });
} }
@ -258,7 +250,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// actions define the same route info. Link generation happens based on the action name + controller // actions define the same route info. Link generation happens based on the action name + controller
// name. // name.
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesLinkGenerationEntry_ForEachAction() public void AttributeRoute_GetEntries_CreatesOutboundEntry_ForEachAction()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -301,44 +293,40 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object); var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.LinkGenerationEntries, builder.OutboundEntries,
e => e =>
{ {
Assert.NotNull(e.Binder);
Assert.Empty(e.Constraints); Assert.Empty(e.Constraints);
Assert.Empty(e.Defaults); Assert.Empty(e.Defaults);
Assert.Equal(RoutePrecedence.ComputeGenerated(e.Template), e.GenerationPrecedence); Assert.Equal(RoutePrecedence.ComputeOutbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.Name); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(actions[0].RouteValueDefaults, e.RequiredLinkValues); Assert.Equal(actions[0].RouteValueDefaults.ToArray(), e.RequiredLinkValues.ToArray());
Assert.Equal("1", e.RouteGroup); Assert.Equal("api/Blog/{id}", e.RouteTemplate.TemplateText);
Assert.Equal("api/Blog/{id}", e.Template.TemplateText);
}, },
e => e =>
{ {
Assert.NotNull(e.Binder);
Assert.Empty(e.Constraints); Assert.Empty(e.Constraints);
Assert.Empty(e.Defaults); Assert.Empty(e.Defaults);
Assert.Equal(RoutePrecedence.ComputeGenerated(e.Template), e.GenerationPrecedence); Assert.Equal(RoutePrecedence.ComputeOutbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.Name); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(actions[1].RouteValueDefaults, e.RequiredLinkValues); Assert.Equal(actions[1].RouteValueDefaults.ToArray(), e.RequiredLinkValues.ToArray());
Assert.Equal("1", e.RouteGroup); Assert.Equal("api/Blog/{id}", e.RouteTemplate.TemplateText);
Assert.Equal("api/Blog/{id}", e.Template.TemplateText);
}); });
} }
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesMatchingEntry() public void AttributeRoute_GetEntries_CreatesInboundEntry()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -363,34 +351,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
var handler = CreateHandler().Object;
var route = CreateRoute(handler, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.MatchingEntries, builder.InboundEntries,
e => e =>
{ {
Assert.Empty(e.Constraints); Assert.Empty(e.Constraints);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(RoutePrecedence.ComputeMatched(e.RouteTemplate), e.Precedence); Assert.Equal(RoutePrecedence.ComputeInbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.RouteName); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal("api/Blog/{id}", e.RouteTemplate.TemplateText); Assert.Equal("api/Blog/{id}", e.RouteTemplate.TemplateText);
Assert.Same(handler, e.Target);
Assert.Collection( Assert.Collection(
e.TemplateMatcher.Defaults.OrderBy(kvp => kvp.Key), e.Defaults.OrderBy(kvp => kvp.Key),
kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp)); kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp));
Assert.Same(e.RouteTemplate, e.TemplateMatcher.Template);
}); });
} }
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesMatchingEntry_WithConstraint() public void AttributeRoute_GetEntries_CreatesInboundEntry_WithConstraint()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -415,34 +400,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
var handler = CreateHandler().Object;
var route = CreateRoute(handler, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.MatchingEntries, builder.InboundEntries,
e => e =>
{ {
Assert.Single(e.Constraints, kvp => kvp.Key == "id"); Assert.Single(e.Constraints, kvp => kvp.Key == "id");
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(RoutePrecedence.ComputeMatched(e.RouteTemplate), e.Precedence); Assert.Equal(RoutePrecedence.ComputeInbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.RouteName); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal("api/Blog/{id:int}", e.RouteTemplate.TemplateText); Assert.Equal("api/Blog/{id:int}", e.RouteTemplate.TemplateText);
Assert.Same(handler, e.Target);
Assert.Collection( Assert.Collection(
e.TemplateMatcher.Defaults.OrderBy(kvp => kvp.Key), e.Defaults.OrderBy(kvp => kvp.Key),
kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp)); kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp));
Assert.Same(e.RouteTemplate, e.TemplateMatcher.Template);
}); });
} }
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesMatchingEntry_WithDefault() public void AttributeRoute_GetEntries_CreatesInboundEntry_WithDefault()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -467,30 +449,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
var handler = CreateHandler().Object;
var route = CreateRoute(handler, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.MatchingEntries, builder.InboundEntries,
e => e =>
{ {
Assert.Empty(e.Constraints); Assert.Empty(e.Constraints);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(RoutePrecedence.ComputeMatched(e.RouteTemplate), e.Precedence); Assert.Equal(RoutePrecedence.ComputeInbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.RouteName); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal("api/Blog/{*slug=hello}", e.RouteTemplate.TemplateText); Assert.Equal("api/Blog/{*slug=hello}", e.RouteTemplate.TemplateText);
Assert.Same(handler, e.Target);
Assert.Collection( Assert.Collection(
e.TemplateMatcher.Defaults.OrderBy(kvp => kvp.Key), e.Defaults.OrderBy(kvp => kvp.Key),
kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp), kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp),
kvp => Assert.Equal(new KeyValuePair<string, object>("slug", "hello"), kvp)); kvp => Assert.Equal(new KeyValuePair<string, object>("slug", "hello"), kvp));
Assert.Same(e.RouteTemplate, e.TemplateMatcher.Template);
}); });
} }
@ -498,7 +477,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// actions define the same route info. Link generation happens based on the action name + controller // actions define the same route info. Link generation happens based on the action name + controller
// name. // name.
[Fact] [Fact]
public void AttributeRoute_GetEntries_CreatesMatchingEntry_CombinesLikeActions() public void AttributeRoute_GetEntries_CreatesInboundEntry_CombinesLikeActions()
{ {
// Arrange // Arrange
var actions = new List<ActionDescriptor>() var actions = new List<ActionDescriptor>()
@ -541,32 +520,41 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}, },
}; };
var builder = CreateBuilder();
var actionDescriptorProvider = CreateActionDescriptorProvider(actions); var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
var handler = CreateHandler().Object;
var route = CreateRoute(handler, actionDescriptorProvider.Object);
// Act // Act
var entries = route.GetEntries(actionDescriptorProvider.Object.ActionDescriptors); route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
// Assert // Assert
Assert.Collection( Assert.Collection(
entries.MatchingEntries, builder.InboundEntries,
e => e =>
{ {
Assert.Empty(e.Constraints); Assert.Empty(e.Constraints);
Assert.Equal(17, e.Order); Assert.Equal(17, e.Order);
Assert.Equal(RoutePrecedence.ComputeMatched(e.RouteTemplate), e.Precedence); Assert.Equal(RoutePrecedence.ComputeInbound(e.RouteTemplate), e.Precedence);
Assert.Equal("BLOG_INDEX", e.RouteName); Assert.Equal("BLOG_INDEX", e.RouteName);
Assert.Equal("api/Blog/{id}", e.RouteTemplate.TemplateText); Assert.Equal("api/Blog/{id}", e.RouteTemplate.TemplateText);
Assert.Same(handler, e.Target);
Assert.Collection( Assert.Collection(
e.TemplateMatcher.Defaults.OrderBy(kvp => kvp.Key), e.Defaults.OrderBy(kvp => kvp.Key),
kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp)); kvp => Assert.Equal(new KeyValuePair<string, object>(TreeRouter.RouteGroupKey, "1"), kvp));
Assert.Same(e.RouteTemplate, e.TemplateMatcher.Template);
}); });
} }
private static TreeRouteBuilder CreateBuilder()
{
var services = new ServiceCollection()
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
.AddLogging()
.AddRouting()
.AddOptions()
.BuildServiceProvider();
return services.GetRequiredService<TreeRouteBuilder>();
}
private static Mock<IRouter> CreateHandler() private static Mock<IRouter> CreateHandler()
{ {
var handler = new Mock<IRouter>(MockBehavior.Strict); var handler = new Mock<IRouter>(MockBehavior.Strict);
@ -593,23 +581,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
IRouter handler, IRouter handler,
IActionDescriptorCollectionProvider actionDescriptorProvider) IActionDescriptorCollectionProvider actionDescriptorProvider)
{ {
var constraintResolver = new Mock<IInlineConstraintResolver>(); var services = new ServiceCollection()
constraintResolver .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
.Setup(c => c.ResolveConstraint("int")) .AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
.Returns(new IntRouteConstraint()); .AddLogging()
.AddRouting()
var policy = new UriBuilderContextPooledObjectPolicy(new UrlTestEncoder()); .AddOptions()
var pool = new DefaultObjectPool<UriBuildingContext>(policy); .BuildServiceProvider();
return new AttributeRoute(handler, actionDescriptorProvider, services);
var route = new AttributeRoute(
handler,
actionDescriptorProvider,
constraintResolver.Object,
pool,
new UrlTestEncoder(),
NullLoggerFactory.Instance);
return route;
} }
} }
} }

View File

@ -199,7 +199,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>() .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
.AddSingleton<UrlEncoder>(new UrlTestEncoder()); .AddSingleton<UrlEncoder>(new UrlTestEncoder());
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.AddRouting(); services.AddRouting();
services.AddOptions();
services.AddLogging();
return services.AddSingleton(actionDescriptorProvider.Object) return services.AddSingleton(actionDescriptorProvider.Object)
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance) .AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)