Reacting to Routing repo's EndpointFinder changes
This commit is contained in:
parent
4dd4e5ef3e
commit
54c14b8782
|
|
@ -30,7 +30,8 @@ namespace Microsoft.AspNetCore.Builder
|
|||
ParsedTemplate = TemplateParser.Parse(template);
|
||||
|
||||
Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
|
||||
Defaults = GetDefaults(ParsedTemplate, defaults);
|
||||
Defaults = defaults;
|
||||
MergedDefaults = GetDefaults(ParsedTemplate, defaults);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
|
@ -41,7 +42,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
public string Name { get; }
|
||||
public string Template { get; }
|
||||
|
||||
// Non-inline defaults
|
||||
public RouteValueDictionary Defaults { get; }
|
||||
|
||||
// Inline and non-inline defaults merged into one
|
||||
public RouteValueDictionary MergedDefaults { get; }
|
||||
|
||||
public IDictionary<string, IRouteConstraint> Constraints { get; }
|
||||
public RouteValueDictionary DataTokens { get; }
|
||||
internal RouteTemplate ParsedTemplate { get; private set; }
|
||||
|
|
|
|||
|
|
@ -68,6 +68,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
if (action.AttributeRouteInfo == null)
|
||||
{
|
||||
// In traditional conventional routing setup, the routes defined by a user have a static order
|
||||
// defined by how they are added into the list. We would like to maintain the same order when building
|
||||
// up the endpoints too.
|
||||
//
|
||||
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
|
||||
// This is for scenarios dealing with migrating existing Routing based code to Dispatcher world.
|
||||
var conventionalRouteOrder = 0;
|
||||
|
||||
// Check each of the conventional templates to see if the action would be reachable
|
||||
// If the action and template are compatible then create an endpoint with the
|
||||
// area/controller/action parameter parts replaced with literals
|
||||
|
|
@ -100,7 +108,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
var subTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments.Take(i));
|
||||
|
||||
var subEndpoint = CreateEndpoint(action, subTemplate, 0, endpointInfo);
|
||||
var subEndpoint = CreateEndpoint(
|
||||
action,
|
||||
endpointInfo.Name,
|
||||
subTemplate,
|
||||
endpointInfo.Defaults,
|
||||
++conventionalRouteOrder,
|
||||
endpointInfo);
|
||||
_endpoints.Add(subEndpoint);
|
||||
}
|
||||
|
||||
|
|
@ -119,14 +133,26 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var newTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments);
|
||||
|
||||
var endpoint = CreateEndpoint(action, newTemplate, 0, endpointInfo);
|
||||
var endpoint = CreateEndpoint(
|
||||
action,
|
||||
endpointInfo.Name,
|
||||
newTemplate,
|
||||
endpointInfo.Defaults,
|
||||
++conventionalRouteOrder,
|
||||
endpointInfo);
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var endpoint = CreateEndpoint(action, action.AttributeRouteInfo.Template, action.AttributeRouteInfo.Order, action.AttributeRouteInfo);
|
||||
var endpoint = CreateEndpoint(
|
||||
action,
|
||||
action.AttributeRouteInfo.Name,
|
||||
action.AttributeRouteInfo.Template,
|
||||
nonInlineDefaults: null,
|
||||
action.AttributeRouteInfo.Order,
|
||||
action.AttributeRouteInfo);
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool UseDefaultValuePlusRemainingSegementsOptional(int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, RouteTemplate template)
|
||||
private bool UseDefaultValuePlusRemainingSegementsOptional(
|
||||
int segmentIndex,
|
||||
ActionDescriptor action,
|
||||
MvcEndpointInfo endpointInfo,
|
||||
RouteTemplate template)
|
||||
{
|
||||
// Check whether the remaining segments are all optional and one or more of them is
|
||||
// for area/controller/action and has a default value
|
||||
|
|
@ -164,7 +194,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
if (IsMvcParameter(part.Name))
|
||||
{
|
||||
if (endpointInfo.Defaults[part.Name] is string defaultValue
|
||||
if (endpointInfo.MergedDefaults[part.Name] is string defaultValue
|
||||
&& action.RouteValues.TryGetValue(part.Name, out var routeValue)
|
||||
&& string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
@ -196,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
else
|
||||
{
|
||||
if (endpointInfo.Defaults != null && string.Equals(actionValue, endpointInfo.Defaults[routeKey] as string, StringComparison.OrdinalIgnoreCase))
|
||||
if (endpointInfo.MergedDefaults != null && string.Equals(actionValue, endpointInfo.MergedDefaults[routeKey] as string, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -235,7 +265,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
private MatcherEndpoint CreateEndpoint(ActionDescriptor action, string template, int order, object source)
|
||||
private MatcherEndpoint CreateEndpoint(
|
||||
ActionDescriptor action,
|
||||
string routeName,
|
||||
string template,
|
||||
object nonInlineDefaults,
|
||||
int order,
|
||||
object source)
|
||||
{
|
||||
RequestDelegate invokerDelegate = (context) =>
|
||||
{
|
||||
|
|
@ -260,10 +296,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
metadata.Add(source);
|
||||
metadata.Add(action);
|
||||
|
||||
if (!string.IsNullOrEmpty(routeName))
|
||||
{
|
||||
metadata.Add(new RouteNameMetadata(routeName));
|
||||
}
|
||||
|
||||
// Add filter descriptors to endpoint metadata
|
||||
if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0)
|
||||
{
|
||||
metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter));
|
||||
metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer)
|
||||
.Select(f => f.Filter));
|
||||
}
|
||||
|
||||
if (action.ActionConstraints != null && action.ActionConstraints.Count > 0)
|
||||
|
|
@ -281,14 +323,41 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var endpoint = new MatcherEndpoint(
|
||||
next => invokerDelegate,
|
||||
template,
|
||||
action.RouteValues,
|
||||
new RouteValueDictionary(nonInlineDefaults),
|
||||
new RouteValueDictionary(action.RouteValues),
|
||||
order,
|
||||
metadataCollection,
|
||||
action.DisplayName,
|
||||
address: null);
|
||||
action.DisplayName);
|
||||
|
||||
// Use defaults after the endpoint is created as it merges both the inline and
|
||||
// non-inline defaults into one.
|
||||
EnsureRequiredValuesInDefaults(endpoint.RequiredValues, endpoint.Defaults);
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
// Ensure required values are a subset of defaults
|
||||
// Examples:
|
||||
//
|
||||
// Template: {controller}/{action}/{category}/{id?}
|
||||
// Defaults(in-line or non in-line): category=products
|
||||
// Required values: controller=foo, action=bar
|
||||
// Final constructed template: foo/bar/{category}/{id?}
|
||||
// Final defaults: controller=foo, action=bar, category=products
|
||||
//
|
||||
// Template: {controller=Home}/{action=Index}/{category=products}/{id?}
|
||||
// Defaults: controller=Home, action=Index, category=products
|
||||
// Required values: controller=foo, action=bar
|
||||
// Final constructed template: foo/bar/{category}/{id?}
|
||||
// Final defaults: controller=foo, action=bar, category=products
|
||||
private void EnsureRequiredValuesInDefaults(RouteValueDictionary requiredValues, RouteValueDictionary defaults)
|
||||
{
|
||||
foreach (var kvp in requiredValues)
|
||||
{
|
||||
defaults[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private IChangeToken GetCompositeChangeToken()
|
||||
{
|
||||
if (_actionDescriptorChangeProviders.Length == 1)
|
||||
|
|
@ -321,5 +390,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
|
||||
|
||||
public List<MvcEndpointInfo> ConventionalEndpointInfos { get; }
|
||||
|
||||
private class RouteNameMetadata : IRouteNameMetadata
|
||||
{
|
||||
public RouteNameMetadata(string routeName)
|
||||
{
|
||||
Name = routeName;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.EndpointFinders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
|
|
@ -15,16 +16,21 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
{
|
||||
private readonly ILogger<DispatcherUrlHelper> _logger;
|
||||
private readonly ILinkGenerator _linkGenerator;
|
||||
private readonly IEndpointFinder<RouteValuesBasedEndpointFinderContext> _routeValuesBasedEndpointFinder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DispatcherUrlHelper"/> class using the specified
|
||||
/// <paramref name="actionContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="actionContext">The <see cref="Mvc.ActionContext"/> for the current request.</param>
|
||||
/// <param name="routeValuesBasedEndpointFinder">
|
||||
/// The <see cref="IEndpointFinder{T}"/> which finds endpoints by required route values.
|
||||
/// </param>
|
||||
/// <param name="linkGenerator">The <see cref="ILinkGenerator"/> used to generate the link.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||
public DispatcherUrlHelper(
|
||||
ActionContext actionContext,
|
||||
IEndpointFinder<RouteValuesBasedEndpointFinderContext> routeValuesBasedEndpointFinder,
|
||||
ILinkGenerator linkGenerator,
|
||||
ILogger<DispatcherUrlHelper> logger)
|
||||
: base(actionContext)
|
||||
|
|
@ -40,6 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
}
|
||||
|
||||
_linkGenerator = linkGenerator;
|
||||
_routeValuesBasedEndpointFinder = routeValuesBasedEndpointFinder;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +86,17 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
valuesDictionary["controller"] = urlActionContext.Controller;
|
||||
}
|
||||
|
||||
var successfullyGeneratedLink = _linkGenerator.TryGetLink(
|
||||
new LinkGeneratorContext()
|
||||
var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints(
|
||||
new RouteValuesBasedEndpointFinderContext()
|
||||
{
|
||||
SuppliedValues = valuesDictionary,
|
||||
ExplicitValues = valuesDictionary,
|
||||
AmbientValues = AmbientValues
|
||||
},
|
||||
});
|
||||
|
||||
var successfullyGeneratedLink = _linkGenerator.TryGetLink(
|
||||
endpoints,
|
||||
valuesDictionary,
|
||||
AmbientValues,
|
||||
out var link);
|
||||
|
||||
if (!successfullyGeneratedLink)
|
||||
|
|
@ -107,13 +119,18 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
|
||||
var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values);
|
||||
|
||||
var successfullyGeneratedLink = _linkGenerator.TryGetLink(
|
||||
new LinkGeneratorContext()
|
||||
var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints(
|
||||
new RouteValuesBasedEndpointFinderContext()
|
||||
{
|
||||
Address = new Address(routeContext.RouteName),
|
||||
SuppliedValues = valuesDictionary,
|
||||
RouteName = routeContext.RouteName,
|
||||
ExplicitValues = valuesDictionary,
|
||||
AmbientValues = AmbientValues
|
||||
},
|
||||
});
|
||||
|
||||
var successfullyGeneratedLink = _linkGenerator.TryGetLink(
|
||||
endpoints,
|
||||
valuesDictionary,
|
||||
AmbientValues,
|
||||
out var link);
|
||||
|
||||
if (!successfullyGeneratedLink)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.EndpointFinders;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -49,9 +50,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
|
||||
if (endpointFeature?.Endpoint != null)
|
||||
{
|
||||
var linkGenerator = httpContext.RequestServices.GetRequiredService<ILinkGenerator>();
|
||||
var logger = httpContext.RequestServices.GetRequiredService<ILogger<DispatcherUrlHelper>>();
|
||||
urlHelper = new DispatcherUrlHelper(context, linkGenerator, logger);
|
||||
var services = httpContext.RequestServices;
|
||||
var linkGenerator = services.GetRequiredService<ILinkGenerator>();
|
||||
var routeValuesBasedEndpointFinder = services.GetRequiredService<IEndpointFinder<RouteValuesBasedEndpointFinderContext>>();
|
||||
var logger = services.GetRequiredService<ILogger<DispatcherUrlHelper>>();
|
||||
|
||||
urlHelper = new DispatcherUrlHelper(
|
||||
context,
|
||||
routeValuesBasedEndpointFinder,
|
||||
linkGenerator,
|
||||
logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matchers;
|
||||
|
|
@ -20,7 +19,7 @@ using Microsoft.Extensions.Primitives;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class MvcEndpointDataSourceTests
|
||||
{
|
||||
|
|
@ -29,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
{
|
||||
// Arrange
|
||||
var routeValue = "Value";
|
||||
var routeValues = new Dictionary<string, string>
|
||||
var requiredValues = new Dictionary<string, string>
|
||||
{
|
||||
["Name"] = routeValue
|
||||
};
|
||||
|
|
@ -43,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
RouteValues = routeValues,
|
||||
RouteValues = requiredValues,
|
||||
DisplayName = displayName,
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
|
|
@ -66,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
|
||||
var endpointValue = matcherEndpoint.Values["Name"];
|
||||
var endpointValue = matcherEndpoint.RequiredValues["Name"];
|
||||
Assert.Equal(routeValue, endpointValue);
|
||||
|
||||
Assert.Equal(displayName, matcherEndpoint.DisplayName);
|
||||
|
|
@ -187,13 +186,9 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
public void InitializeEndpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController", action = "TestAction" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
|
||||
|
||||
// Act
|
||||
|
|
@ -218,13 +213,9 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
public void InitializeEndpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction", "TestArea")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController", action = "TestAction", area = "TestArea" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
|
||||
|
||||
// Act
|
||||
|
|
@ -243,13 +234,9 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
public void InitializeEndpoints_SingleAction_WithActionDefault()
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController", action = "TestAction" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}",
|
||||
|
|
@ -268,15 +255,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
public void InitializeEndpoints_MultipleActions_WithActionConstraint()
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction"),
|
||||
CreateActionDescriptor("TestController", "TestAction1"),
|
||||
CreateActionDescriptor("TestController", "TestAction2")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController", action = "TestAction" },
|
||||
new { controller = "TestController", action = "TestAction1" },
|
||||
new { controller = "TestController", action = "TestAction2" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}",
|
||||
|
|
@ -297,16 +280,12 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
public void InitializeEndpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController1", "TestAction1"),
|
||||
CreateActionDescriptor("TestController1", "TestAction2"),
|
||||
CreateActionDescriptor("TestController1", "TestAction3"),
|
||||
CreateActionDescriptor("TestController2", "TestAction1")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController1", action = "TestAction1" },
|
||||
new { controller = "TestController1", action = "TestAction2" },
|
||||
new { controller = "TestController1", action = "TestAction3" },
|
||||
new { controller = "TestController2", action = "TestAction1" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
endpointInfoRoute));
|
||||
|
|
@ -322,6 +301,276 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
Assert.Collection(dataSource.Endpoints, inspectors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionalRoute_WithNoRouteName_DoesNotAddRouteNameMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "Home", action = "Index" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(string.Empty, "named/{controller}/{action}/{id?}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
||||
Assert.Null(routeNameMetadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCreateMultipleEndpoints_WithSameRouteName()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "Home", action = "Index" },
|
||||
new { controller = "Products", action = "Details" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo("namedRoute", "named/{controller}/{action}/{id?}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
dataSource.Endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
||||
Assert.NotNull(routeNameMetadata);
|
||||
Assert.Equal("namedRoute", routeNameMetadata.Name);
|
||||
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.Template);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
||||
Assert.NotNull(routeNameMetadata);
|
||||
Assert.Equal("namedRoute", routeNameMetadata.Name);
|
||||
Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.Template);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeEndpoints_ConventionalRoutes_StaticallyDefinedOrder_IsMaintained()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "Home", action = "Index" },
|
||||
new { controller = "Products", action = "Details" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
name: string.Empty,
|
||||
template: "{controller}/{action}/{id?}"));
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
name: "namedRoute",
|
||||
"named/{controller}/{action}/{id?}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
dataSource.Endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
Assert.Equal("Home/Index/{id?}", matcherEndpoint.Template);
|
||||
Assert.Equal(1, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.Template);
|
||||
Assert.Equal(2, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
Assert.Equal("Products/Details/{id?}", matcherEndpoint.Template);
|
||||
Assert.Equal(1, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.Template);
|
||||
Assert.Equal(2, matcherEndpoint.Order);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { area = "admin", controller = "home", action = "index" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(dataSource.Endpoints);
|
||||
}
|
||||
|
||||
// Since area, controller, action and page are special, check to see if the followin test succeeds for a
|
||||
// custom required value too.
|
||||
[Fact(Skip = "Needs review")]
|
||||
public void NonReservedRequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index", foo = "bar" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(dataSource.Endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TemplateParameter_WithNoDefaultOrRequiredValue_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(dataSource.Endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TemplateParameter_WithDefaultValue_AndNullRequiredValue_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { area = (string)null, controller = "home", action = "index" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area=admin}/{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(dataSource.Endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TemplateParameter_WithNullRequiredValue_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { area = (string)null, controller = "home", action = "index" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(dataSource.Endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoDefaultValues_RequiredValues_UsedToCreateDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var expectedDefaults = new RouteValueDictionary(new { controller = "Foo", action = "Bar" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: expectedDefaults);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
Assert.Equal("Foo/Bar", matcherEndpoint.Template);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValues_NotPresent_InDefaultValues_IsAddedToDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Bar", subarea = "test" });
|
||||
var expectedDefaults = requiredValues;
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
Assert.Equal("Foo/Bar", matcherEndpoint.Template);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValues_IsSubsetOf_DefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Bar", subarea = "test" });
|
||||
var expectedDefaults = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Bar", subarea = "test", subscription = "general" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}/{subscription=general}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.Template);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValues_HavingNull_AndNotPresentInDefaultValues_IsAddedToDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(
|
||||
new { area = (string)null, controller = "Foo", action = "Bar", page = (string)null });
|
||||
var expectedDefaults = requiredValues;
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}"));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
Assert.Equal("Foo/Bar", matcherEndpoint.Template);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);
|
||||
}
|
||||
|
||||
private MvcEndpointDataSource CreateMvcEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null,
|
||||
MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null,
|
||||
|
|
@ -362,18 +611,45 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, constraintResolver);
|
||||
}
|
||||
|
||||
private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params object[] requiredValues)
|
||||
{
|
||||
var actionDescriptors = new List<ActionDescriptor>();
|
||||
foreach (var requiredValue in requiredValues)
|
||||
{
|
||||
actionDescriptors.Add(CreateActionDescriptor(requiredValue));
|
||||
}
|
||||
|
||||
var actionDescriptorCollectionProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
actionDescriptorCollectionProvider
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(actionDescriptors, version: 0));
|
||||
return actionDescriptorCollectionProvider.Object;
|
||||
}
|
||||
|
||||
private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null)
|
||||
{
|
||||
return new ActionDescriptor
|
||||
return CreateActionDescriptor(new { controller = controller, action = action, area = area });
|
||||
}
|
||||
|
||||
private ActionDescriptor CreateActionDescriptor(object requiredValues)
|
||||
{
|
||||
var actionDescriptor = new ActionDescriptor();
|
||||
var routeValues = new RouteValueDictionary(requiredValues);
|
||||
foreach (var kvp in routeValues)
|
||||
{
|
||||
RouteValues =
|
||||
{
|
||||
["controller"] = controller,
|
||||
["action"] = action,
|
||||
["area"] = area
|
||||
},
|
||||
DisplayName = string.Empty,
|
||||
};
|
||||
actionDescriptor.RouteValues[kvp.Key] = kvp.Value?.ToString();
|
||||
}
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
||||
private void AssertIsSubset(RouteValueDictionary subset, RouteValueDictionary fullSet)
|
||||
{
|
||||
foreach (var subsetPair in subset)
|
||||
{
|
||||
var isPresent = fullSet.TryGetValue(subsetPair.Key, out var fullSetPairValue);
|
||||
Assert.True(isPresent);
|
||||
Assert.Equal(subsetPair.Value, fullSetPairValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,12 +36,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
endpoints.Add(new MatcherEndpoint(
|
||||
next => httpContext => Task.CompletedTask,
|
||||
template,
|
||||
null,
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
null,
|
||||
new Address(routeName)
|
||||
));
|
||||
null));
|
||||
return CreateUrlHelper(endpoints, appRoot, host, protocol);
|
||||
}
|
||||
|
||||
|
|
@ -53,10 +52,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
Endpoint = new MatcherEndpoint(
|
||||
next => cntxt => Task.CompletedTask,
|
||||
"/",
|
||||
new { },
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
null,
|
||||
null)
|
||||
});
|
||||
|
||||
|
|
@ -79,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
string template,
|
||||
object defaults)
|
||||
{
|
||||
var endpoint = GetEndpoint(routeName, template, defaults);
|
||||
var endpoint = GetEndpoint(routeName, template, new RouteValueDictionary(defaults));
|
||||
var services = CreateServices(new[] { endpoint });
|
||||
var httpContext = CreateHttpContext(services, appRoot: "", host: null, protocol: null);
|
||||
var actionContext = CreateActionContext(httpContext);
|
||||
|
|
@ -101,25 +100,36 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
private List<MatcherEndpoint> GetDefaultEndpoints()
|
||||
{
|
||||
var endpoints = new List<MatcherEndpoint>();
|
||||
endpoints.Add(new MatcherEndpoint(
|
||||
next => (httpContext) => Task.CompletedTask,
|
||||
"{controller}/{action}/{id}",
|
||||
new { id = "defaultid" },
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"RouteWithNoName",
|
||||
address: null));
|
||||
endpoints.Add(new MatcherEndpoint(
|
||||
next => (httpContext) => Task.CompletedTask,
|
||||
"named/{controller}/{action}/{id}",
|
||||
new { id = "defaultid" },
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"RouteWithNoName",
|
||||
new Address("namedroute")));
|
||||
endpoints.Add(CreateEndpoint(null, "home/newaction/{id?}", new { id = "defaultid", controller = "home", action = "newaction" }, 1));
|
||||
endpoints.Add(CreateEndpoint(null, "home/contact/{id?}", new { id = "defaultid", controller = "home", action = "contact" }, 2));
|
||||
endpoints.Add(CreateEndpoint(null, "home2/newaction/{id?}", new { id = "defaultid", controller = "home2", action = "newaction" }, 3));
|
||||
endpoints.Add(CreateEndpoint(null, "home2/contact/{id?}", new { id = "defaultid", controller = "home2", action = "contact" }, 4));
|
||||
endpoints.Add(CreateEndpoint(null, "home3/contact/{id?}", new { id = "defaultid", controller = "home3", action = "contact" }, 5));
|
||||
endpoints.Add(CreateEndpoint("namedroute", "named/home/newaction/{id?}", new { id = "defaultid", controller = "home", action = "newaction" }, 6));
|
||||
endpoints.Add(CreateEndpoint("namedroute", "named/home2/newaction/{id?}", new { id = "defaultid", controller = "home2", action = "newaction" }, 7));
|
||||
endpoints.Add(CreateEndpoint("namedroute", "named/home/contact/{id?}", new { id = "defaultid", controller = "home", action = "contact" }, 8));
|
||||
endpoints.Add(CreateEndpoint("MyRouteName", "any/url", new { }, 9));
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private MatcherEndpoint CreateEndpoint(string routeName, string template, object defaults, int order)
|
||||
{
|
||||
var metadata = EndpointMetadataCollection.Empty;
|
||||
if (!string.IsNullOrEmpty(routeName))
|
||||
{
|
||||
metadata = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) });
|
||||
}
|
||||
|
||||
return new MatcherEndpoint(
|
||||
next => (httpContext) => Task.CompletedTask,
|
||||
template,
|
||||
new RouteValueDictionary(defaults),
|
||||
new RouteValueDictionary(),
|
||||
order,
|
||||
metadata,
|
||||
"DisplayName");
|
||||
}
|
||||
|
||||
private IServiceProvider CreateServices(IEnumerable<Endpoint> endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
|
|
@ -135,16 +145,26 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private MatcherEndpoint GetEndpoint(string name, string template, object defaults)
|
||||
private MatcherEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults)
|
||||
{
|
||||
return new MatcherEndpoint(
|
||||
next => c => Task.CompletedTask,
|
||||
template,
|
||||
defaults,
|
||||
new RouteValueDictionary(),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
null,
|
||||
new Address(name));
|
||||
null);
|
||||
}
|
||||
|
||||
private class RouteNameMetadata : IRouteNameMetadata
|
||||
{
|
||||
public RouteNameMetadata(string routeName)
|
||||
{
|
||||
Name = routeName;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue