Support route data tokens with Endpoint Routing (#8360)
This commit is contained in:
parent
5cdc172b17
commit
927e7c8bfc
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue