Support route data tokens with Endpoint Routing (#8360)

This commit is contained in:
James Newton-King 2018-08-30 15:14:34 +12:00 committed by GitHub
parent 5cdc172b17
commit 927e7c8bfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 42 deletions

View File

@ -174,6 +174,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
endpointInfo.Defaults, endpointInfo.Defaults,
++conventionalRouteOrder, ++conventionalRouteOrder,
endpointInfo, endpointInfo,
endpointInfo.DataTokens,
suppressLinkGeneration: false, suppressLinkGeneration: false,
suppressPathMatching: false); suppressPathMatching: false);
endpoints.Add(subEndpoint); endpoints.Add(subEndpoint);
@ -214,6 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
endpointInfo.Defaults, endpointInfo.Defaults,
++conventionalRouteOrder, ++conventionalRouteOrder,
endpointInfo, endpointInfo,
endpointInfo.DataTokens,
suppressLinkGeneration: false, suppressLinkGeneration: false,
suppressPathMatching: false); suppressPathMatching: false);
endpoints.Add(endpoint); endpoints.Add(endpoint);
@ -229,6 +231,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
nonInlineDefaults: null, nonInlineDefaults: null,
action.AttributeRouteInfo.Order, action.AttributeRouteInfo.Order,
action.AttributeRouteInfo, action.AttributeRouteInfo,
dataTokens: null,
suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration, suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration,
suppressPathMatching: action.AttributeRouteInfo.SuppressPathMatching); suppressPathMatching: action.AttributeRouteInfo.SuppressPathMatching);
endpoints.Add(endpoint); endpoints.Add(endpoint);
@ -378,20 +381,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
object nonInlineDefaults, object nonInlineDefaults,
int order, int order,
object source, object source,
RouteValueDictionary dataTokens,
bool suppressLinkGeneration, bool suppressLinkGeneration,
bool suppressPathMatching) bool suppressPathMatching)
{ {
RequestDelegate requestDelegate = (context) => RequestDelegate requestDelegate = (context) =>
{ {
var values = context.Features.Get<IRouteValuesFeature>().RouteValues; var routeData = context.GetRouteData();
var routeData = new RouteData();
foreach (var kvp in values)
{
if (kvp.Value != null)
{
routeData.Values.Add(kvp.Key, kvp.Value);
}
}
var actionContext = new ActionContext(context, routeData, action); var actionContext = new ActionContext(context, routeData, action);
@ -407,6 +403,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
routeName, routeName,
new RouteValueDictionary(action.RouteValues), new RouteValueDictionary(action.RouteValues),
source, source,
dataTokens,
suppressLinkGeneration, suppressLinkGeneration,
suppressPathMatching); suppressPathMatching);
@ -425,6 +422,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
string routeName, string routeName,
RouteValueDictionary requiredValues, RouteValueDictionary requiredValues,
object source, object source,
RouteValueDictionary dataTokens,
bool suppressLinkGeneration, bool suppressLinkGeneration,
bool suppressPathMatching) bool suppressPathMatching)
{ {
@ -438,6 +436,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
metadata.AddRange(action.EndpointMetadata); metadata.AddRange(action.EndpointMetadata);
} }
if (dataTokens != null)
{
metadata.Add(new DataTokensMetadata(dataTokens));
}
metadata.Add(new RouteValuesAddressMetadata(routeName, requiredValues)); metadata.Add(new RouteValuesAddressMetadata(routeName, requiredValues));
// Add filter descriptors to endpoint metadata // Add filter descriptors to endpoint metadata
@ -506,7 +509,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
foreach (var kvp in requiredValues) foreach (var kvp in requiredValues)
{ {
defaults[kvp.Key] = kvp.Value; if (kvp.Value != null)
{
defaults[kvp.Key] = kvp.Value;
}
} }
} }
} }

View File

@ -89,6 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var featureCollection = new FeatureCollection(); var featureCollection = new FeatureCollection();
featureCollection.Set<IEndpointFeature>(endpointFeature); featureCollection.Set<IEndpointFeature>(endpointFeature);
featureCollection.Set<IRouteValuesFeature>(endpointFeature); featureCollection.Set<IRouteValuesFeature>(endpointFeature);
featureCollection.Set<IRoutingFeature>(endpointFeature);
var httpContextMock = new Mock<HttpContext>(); var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(m => m.Features).Returns(featureCollection); httpContextMock.Setup(m => m.Features).Returns(featureCollection);
@ -694,28 +695,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.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
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
}
private MvcEndpointDataSource CreateMvcEndpointDataSource( private MvcEndpointDataSource CreateMvcEndpointDataSource(
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null,
MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null) MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null)

View File

@ -72,6 +72,30 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public string[] Routers { get; set; } public string[] Routers { get; set; }
} }
[Fact]
public async Task DataTokens_ReturnsDataTokensForRoute()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/DataTokensRoute/DataTokens/Index");
// Assert
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<Dictionary<string, object>>(body);
Assert.Single(result, kvp => kvp.Key == "hasDataTokens" && ((bool)kvp.Value) == true);
}
[Fact]
public async Task DataTokens_ReturnsNoDataTokensForRoute()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/DataTokens/Index");
// Assert
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<Dictionary<string, object>>(body);
Assert.Empty(result);
}
[Fact] [Fact]
public virtual async Task ConventionalRoutedController_ActionIsReachable() public virtual async Task ConventionalRoutedController_ActionIsReachable()
{ {

View File

@ -0,0 +1,15 @@
// 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.
using Microsoft.AspNetCore.Mvc;
namespace RoutingWebSite
{
public class DataTokensController : Controller
{
public object Index()
{
return RouteData.DataTokens;
}
}
}

View File

@ -24,12 +24,19 @@ namespace RoutingWebSite
{ {
app.UseMvc(routes => app.UseMvc(routes =>
{ {
routes.MapRoute(
"DataTokensRoute",
"DataTokensRoute/{controller}/{action}",
defaults: null,
constraints: new { controller = "DataTokens" },
dataTokens: new { hasDataTokens = true });
routes.MapAreaRoute( routes.MapAreaRoute(
"flightRoute", "flightRoute",
"adminRoute", "adminRoute",
"{area:exists}/{controller}/{action}", "{area:exists}/{controller}/{action}",
new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
new { area = "Travel" }); constraints: new { area = "Travel" });
routes.MapRoute( routes.MapRoute(
"ActionAsMethod", "ActionAsMethod",

View File

@ -24,12 +24,19 @@ namespace RoutingWebSite
{ {
app.UseMvc(routes => app.UseMvc(routes =>
{ {
routes.MapRoute(
"DataTokensRoute",
"DataTokensRoute/{controller}/{action}",
defaults: null,
constraints: new { controller = "DataTokens" },
dataTokens: new { hasDataTokens = true });
routes.MapAreaRoute( routes.MapAreaRoute(
"flightRoute", "flightRoute",
"adminRoute", "adminRoute",
"{area:exists}/{controller}/{action}", "{area:exists}/{controller}/{action}",
new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
new { area = "Travel" }); constraints: new { area = "Travel" });
routes.MapRoute( routes.MapRoute(
"ActionAsMethod", "ActionAsMethod",