Fix non-parameter route constraints not called with endpoint routing for 2.2 (#6587)
This commit is contained in:
parent
874a67a242
commit
180f735ac8
|
|
@ -31,6 +31,7 @@ Later on, this will be checked using this condition:
|
||||||
Microsoft.AspNetCore.AspNetCoreModuleV2;
|
Microsoft.AspNetCore.AspNetCoreModuleV2;
|
||||||
Microsoft.AspNetCore.Authentication.Google;
|
Microsoft.AspNetCore.Authentication.Google;
|
||||||
Microsoft.AspNetCore.Http;
|
Microsoft.AspNetCore.Http;
|
||||||
|
Microsoft.AspNetCore.Mvc.Core;
|
||||||
Microsoft.AspNetCore.Server.IIS;
|
Microsoft.AspNetCore.Server.IIS;
|
||||||
java:signalr;
|
java:signalr;
|
||||||
</PackagesInPatch>
|
</PackagesInPatch>
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var newPathSegments = routePattern.PathSegments.ToList();
|
var newPathSegments = routePattern.PathSegments.ToList();
|
||||||
var hasLinkGenerationEndpoint = false;
|
var hasLinkGenerationEndpoint = false;
|
||||||
|
|
||||||
|
// This is required because we create modified copies of the route pattern using its segments
|
||||||
|
// A segment with a parameter will automatically include its policies
|
||||||
|
// Non-parameter policies need to be manually included
|
||||||
|
var nonParameterPolicyValues = routePattern.ParameterPolicies
|
||||||
|
.Where(p => routePattern.GetParameter(p.Key ?? string.Empty) == null && p.Value.Count > 0 && p.Value.First().ParameterPolicy != null) // Only GetParameter is required. Extra is for safety
|
||||||
|
.Select(p => new KeyValuePair<string, object>(p.Key, p.Value.First().ParameterPolicy)) // Can only pass a single non-parameter to RouteParameter
|
||||||
|
.ToArray();
|
||||||
|
var nonParameterPolicies = RouteValueDictionary.FromArray(nonParameterPolicyValues);
|
||||||
|
|
||||||
// Create a mutable copy
|
// Create a mutable copy
|
||||||
var nonInlineDefaultsCopy = nonInlineDefaults != null
|
var nonInlineDefaultsCopy = nonInlineDefaults != null
|
||||||
? new RouteValueDictionary(nonInlineDefaults)
|
? new RouteValueDictionary(nonInlineDefaults)
|
||||||
|
|
@ -259,6 +268,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
resolvedRouteValues,
|
resolvedRouteValues,
|
||||||
name,
|
name,
|
||||||
GetPattern(ref patternStringBuilder, newPathSegments),
|
GetPattern(ref patternStringBuilder, newPathSegments),
|
||||||
|
nonParameterPolicies,
|
||||||
newPathSegments,
|
newPathSegments,
|
||||||
nonInlineDefaultsCopy,
|
nonInlineDefaultsCopy,
|
||||||
routeOrder++,
|
routeOrder++,
|
||||||
|
|
@ -277,6 +287,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
resolvedRouteValues,
|
resolvedRouteValues,
|
||||||
name,
|
name,
|
||||||
GetPattern(ref patternStringBuilder, subPathSegments),
|
GetPattern(ref patternStringBuilder, subPathSegments),
|
||||||
|
nonParameterPolicies,
|
||||||
subPathSegments,
|
subPathSegments,
|
||||||
nonInlineDefaultsCopy,
|
nonInlineDefaultsCopy,
|
||||||
routeOrder++,
|
routeOrder++,
|
||||||
|
|
@ -294,6 +305,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
resolvedRouteValues,
|
resolvedRouteValues,
|
||||||
name,
|
name,
|
||||||
GetPattern(ref patternStringBuilder, newPathSegments),
|
GetPattern(ref patternStringBuilder, newPathSegments),
|
||||||
|
nonParameterPolicies,
|
||||||
newPathSegments,
|
newPathSegments,
|
||||||
nonInlineDefaultsCopy,
|
nonInlineDefaultsCopy,
|
||||||
routeOrder++,
|
routeOrder++,
|
||||||
|
|
@ -531,6 +543,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
IDictionary<string, string> actionRouteValues,
|
IDictionary<string, string> actionRouteValues,
|
||||||
string routeName,
|
string routeName,
|
||||||
string patternRawText,
|
string patternRawText,
|
||||||
|
object nonParameterPolicies,
|
||||||
IEnumerable<RoutePatternPathSegment> segments,
|
IEnumerable<RoutePatternPathSegment> segments,
|
||||||
object nonInlineDefaults,
|
object nonInlineDefaults,
|
||||||
int order,
|
int order,
|
||||||
|
|
@ -561,7 +574,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
var endpoint = new RouteEndpoint(
|
var endpoint = new RouteEndpoint(
|
||||||
requestDelegate,
|
requestDelegate,
|
||||||
RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments),
|
RoutePatternFactory.Pattern(patternRawText, defaults, nonParameterPolicies, segments),
|
||||||
order,
|
order,
|
||||||
metadataCollection,
|
metadataCollection,
|
||||||
action.DisplayName);
|
action.DisplayName);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
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.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -311,6 +312,109 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Empty(endpoints);
|
Assert.Empty(endpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Endpoints_SingleAction_ConventionalRoute_ContainsNonParameterConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||||
|
new { controller = "TestController", action = "TestAction", page = (string)null });
|
||||||
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||||
|
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||||
|
string.Empty,
|
||||||
|
"{controller}/{action}/{id:range(0, 100)}",
|
||||||
|
new RouteValueDictionary(new { action = "TestAction" }),
|
||||||
|
new RouteValueDictionary(new { controller = "TestController", nonParameter = new CustomConstraint(), id = new IntRouteConstraint() })));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpoints = dataSource.Endpoints;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var endpoint = Assert.IsType<RouteEndpoint>(Assert.Single(endpoints));
|
||||||
|
|
||||||
|
var routePattern = endpoint.RoutePattern;
|
||||||
|
|
||||||
|
Assert.Equal("TestController/TestAction/{id::range(0, 100)}", routePattern.RawText);
|
||||||
|
Assert.Collection(routePattern.ParameterPolicies.OrderBy(p => p.Key),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("id", p.Key);
|
||||||
|
Assert.Collection(p.Value,
|
||||||
|
c => Assert.IsType<IntRouteConstraint>(c.ParameterPolicy),
|
||||||
|
c => Assert.Equal("range(0, 100)", c.Content));
|
||||||
|
},
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("nonParameter", p.Key);
|
||||||
|
Assert.IsType<CustomConstraint>(p.Value.Single().ParameterPolicy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Endpoints_SingleAction_ConventionalRouteWithOptional_ContainsNonParameterConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||||
|
new { controller = "TestController", action = "TestAction", page = (string)null });
|
||||||
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||||
|
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||||
|
string.Empty,
|
||||||
|
"{controller}/{action}/{id?}",
|
||||||
|
new RouteValueDictionary(new { action = "TestAction" }),
|
||||||
|
new RouteValueDictionary(new { controller = "TestController", nonParameter = new CustomConstraint(), id = new IntRouteConstraint() })));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpoints = dataSource.Endpoints;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var endpoint1 = Assert.IsType<RouteEndpoint>(endpoints[0]);
|
||||||
|
var routePattern1 = endpoint1.RoutePattern;
|
||||||
|
Assert.Equal("TestController/{action=TestAction}/{id:?}", routePattern1.RawText);
|
||||||
|
Assert.Collection(routePattern1.ParameterPolicies.OrderBy(p => p.Key),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("id", p.Key);
|
||||||
|
Assert.IsType<IntRouteConstraint>(p.Value.Single().ParameterPolicy);
|
||||||
|
},
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("nonParameter", p.Key);
|
||||||
|
Assert.IsType<CustomConstraint>(p.Value.Single().ParameterPolicy);
|
||||||
|
});
|
||||||
|
|
||||||
|
var endpoint2 = Assert.IsType<RouteEndpoint>(endpoints[1]);
|
||||||
|
var routePattern2 = endpoint2.RoutePattern;
|
||||||
|
Assert.Equal("TestController", routePattern2.RawText);
|
||||||
|
Assert.Collection(routePattern2.ParameterPolicies.OrderBy(p => p.Key),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("nonParameter", p.Key);
|
||||||
|
Assert.IsType<CustomConstraint>(p.Value.Single().ParameterPolicy);
|
||||||
|
});
|
||||||
|
|
||||||
|
var endpoint3 = Assert.IsType<RouteEndpoint>(endpoints[2]);
|
||||||
|
var routePattern3 = endpoint3.RoutePattern;
|
||||||
|
Assert.Equal("TestController/TestAction/{id:?}", routePattern3.RawText);
|
||||||
|
Assert.Collection(routePattern3.ParameterPolicies.OrderBy(p => p.Key),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("id", p.Key);
|
||||||
|
Assert.IsType<IntRouteConstraint>(p.Value.Single().ParameterPolicy);
|
||||||
|
},
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("nonParameter", p.Key);
|
||||||
|
Assert.IsType<CustomConstraint>(p.Value.Single().ParameterPolicy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomConstraint : IRouteConstraint
|
||||||
|
{
|
||||||
|
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Endpoints_SingleAction_AttributeRoute_ContainsParameterWithNullRequiredRouteValue()
|
public void Endpoints_SingleAction_AttributeRoute_ContainsParameterWithNullRequiredRouteValue()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,32 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConventionalRoutedAction_RouteHasNonParameterConstraint_RouteConstraintRun_Allowed()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/NonParameterConstraintRoute/NonParameterConstraint/Index?allowed=true");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||||
|
|
||||||
|
Assert.Equal("NonParameterConstraint", result.Controller);
|
||||||
|
Assert.Equal("Index", result.Action);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConventionalRoutedAction_RouteHasNonParameterConstraint_RouteConstraintRun_Denied()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/NonParameterConstraintRoute/NonParameterConstraint/Index?allowed=false");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched()
|
public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace RoutingWebSite
|
||||||
|
{
|
||||||
|
public class NonParameterConstraintController : Controller
|
||||||
|
{
|
||||||
|
private readonly TestResponseGenerator _generator;
|
||||||
|
|
||||||
|
public NonParameterConstraintController(TestResponseGenerator generator)
|
||||||
|
{
|
||||||
|
_generator = generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return _generator.Generate("/NonParameterConstraintRoute/NonParameterConstraint/Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// 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.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
|
namespace RoutingWebSite
|
||||||
|
{
|
||||||
|
public class QueryStringConstraint : IRouteConstraint
|
||||||
|
{
|
||||||
|
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
|
||||||
|
{
|
||||||
|
return httpContext.Request.Query["allowed"].ToString() == "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,12 @@ namespace RoutingWebSite
|
||||||
{
|
{
|
||||||
app.UseMvc(routes =>
|
app.UseMvc(routes =>
|
||||||
{
|
{
|
||||||
|
routes.MapRoute(
|
||||||
|
"NonParameterConstraintRoute",
|
||||||
|
"NonParameterConstraintRoute/{controller}/{action}",
|
||||||
|
defaults: null,
|
||||||
|
constraints: new { controller = "NonParameterConstraint", nonParameter = new QueryStringConstraint() });
|
||||||
|
|
||||||
routes.MapRoute(
|
routes.MapRoute(
|
||||||
"DataTokensRoute",
|
"DataTokensRoute",
|
||||||
"DataTokensRoute/{controller}/{action}",
|
"DataTokensRoute/{controller}/{action}",
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@ namespace RoutingWebSite
|
||||||
{
|
{
|
||||||
app.UseMvc(routes =>
|
app.UseMvc(routes =>
|
||||||
{
|
{
|
||||||
|
routes.MapRoute(
|
||||||
|
"NonParameterConstraintRoute",
|
||||||
|
"NonParameterConstraintRoute/{controller}/{action}",
|
||||||
|
defaults: null,
|
||||||
|
constraints: new { controller = "NonParameterConstraint", nonParameter = new QueryStringConstraint() });
|
||||||
|
|
||||||
routes.MapRoute(
|
routes.MapRoute(
|
||||||
"DataTokensRoute",
|
"DataTokensRoute",
|
||||||
"DataTokensRoute/{controller}/{action}",
|
"DataTokensRoute/{controller}/{action}",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue