Update MvcEndpointDataSource to use RoutePattern (#8249)
This commit is contained in:
parent
03da30f3bf
commit
e2de54a92d
|
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Performance
|
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
{
|
{
|
||||||
|
|
@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
new RouteValueDictionary(),
|
new RouteValueDictionary(),
|
||||||
new Dictionary<string, object>(),
|
new Dictionary<string, object>(),
|
||||||
new RouteValueDictionary(),
|
new RouteValueDictionary(),
|
||||||
new MockInlineConstraintResolver())
|
new MockParameterPolicyFactory())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,9 +135,14 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MockInlineConstraintResolver : IInlineConstraintResolver
|
private class MockParameterPolicyFactory : ParameterPolicyFactory
|
||||||
{
|
{
|
||||||
public IRouteConstraint ResolveConstraint(string inlineConstraint)
|
public override IParameterPolicy Create(RoutePatternParameterPart parameter, string inlineText)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IParameterPolicy Create(RoutePatternParameterPart parameter, IParameterPolicy parameterPolicy)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@
|
||||||
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
||||||
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
<MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
||||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-a-preview2-matcherendpoint-rename-16892</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-a-preview2-routepattern-defaults-16901</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-a-preview2-matcherendpoint-rename-16892</MicrosoftAspNetCoreRoutingPackageVersion>
|
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-a-preview2-routepattern-defaults-16901</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||||
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreSessionPackageVersion>
|
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview1-34967</MicrosoftAspNetCoreSessionPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,8 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
.GetRequiredService<IEnumerable<EndpointDataSource>>()
|
.GetRequiredService<IEnumerable<EndpointDataSource>>()
|
||||||
.OfType<MvcEndpointDataSource>()
|
.OfType<MvcEndpointDataSource>()
|
||||||
.First();
|
.First();
|
||||||
var constraintResolver = app.ApplicationServices
|
var parameterPolicyFactory = app.ApplicationServices
|
||||||
.GetRequiredService<IInlineConstraintResolver>();
|
.GetRequiredService<ParameterPolicyFactory>();
|
||||||
|
|
||||||
var endpointRouteBuilder = new EndpointRouteBuilder(app);
|
var endpointRouteBuilder = new EndpointRouteBuilder(app);
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
route.Defaults,
|
route.Defaults,
|
||||||
route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value),
|
route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value),
|
||||||
route.DataTokens,
|
route.DataTokens,
|
||||||
constraintResolver);
|
parameterPolicyFactory);
|
||||||
|
|
||||||
mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
|
mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
|
|
@ -13,35 +13,36 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
public MvcEndpointInfo(
|
public MvcEndpointInfo(
|
||||||
string name,
|
string name,
|
||||||
string template,
|
string pattern,
|
||||||
RouteValueDictionary defaults,
|
RouteValueDictionary defaults,
|
||||||
IDictionary<string, object> constraints,
|
IDictionary<string, object> constraints,
|
||||||
RouteValueDictionary dataTokens,
|
RouteValueDictionary dataTokens,
|
||||||
IInlineConstraintResolver constraintResolver)
|
ParameterPolicyFactory parameterPolicyFactory)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Template = template ?? string.Empty;
|
Pattern = pattern ?? string.Empty;
|
||||||
DataTokens = dataTokens;
|
DataTokens = dataTokens;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Data we parse from the template will be used to fill in the rest of the constraints or
|
// Data we parse from the pattern will be used to fill in the rest of the constraints or
|
||||||
// defaults. The parser will throw for invalid routes.
|
// defaults. The parser will throw for invalid routes.
|
||||||
ParsedTemplate = TemplateParser.Parse(template);
|
ParsedPattern = RoutePatternFactory.Parse(pattern, defaults, constraints);
|
||||||
|
Constraints = BuildConstraints(parameterPolicyFactory);
|
||||||
|
|
||||||
Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
|
|
||||||
Defaults = defaults;
|
Defaults = defaults;
|
||||||
MergedDefaults = GetDefaults(ParsedTemplate, defaults);
|
// Merge defaults outside of RoutePattern because the defaults will already have values from pattern
|
||||||
|
MergedDefaults = new RouteValueDictionary(ParsedPattern.Defaults);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
throw new RouteCreationException(
|
throw new RouteCreationException(
|
||||||
string.Format(CultureInfo.CurrentCulture, "An error occurred while creating the route with name '{0}' and template '{1}'.", name, template), exception);
|
string.Format(CultureInfo.CurrentCulture, "An error occurred while creating the route with name '{0}' and pattern '{1}'.", name, pattern), exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string Template { get; }
|
public string Pattern { get; }
|
||||||
|
|
||||||
// Non-inline defaults
|
// Non-inline defaults
|
||||||
public RouteValueDictionary Defaults { get; }
|
public RouteValueDictionary Defaults { get; }
|
||||||
|
|
@ -49,67 +50,33 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
// Inline and non-inline defaults merged into one
|
// Inline and non-inline defaults merged into one
|
||||||
public RouteValueDictionary MergedDefaults { get; }
|
public RouteValueDictionary MergedDefaults { get; }
|
||||||
|
|
||||||
public IDictionary<string, IRouteConstraint> Constraints { get; }
|
public IDictionary<string, IList<IRouteConstraint>> Constraints { get; }
|
||||||
public RouteValueDictionary DataTokens { get; }
|
public RouteValueDictionary DataTokens { get; }
|
||||||
internal RouteTemplate ParsedTemplate { get; private set; }
|
public RoutePattern ParsedPattern { get; private set; }
|
||||||
|
|
||||||
private static IDictionary<string, IRouteConstraint> GetConstraints(
|
private Dictionary<string, IList<IRouteConstraint>> BuildConstraints(ParameterPolicyFactory parameterPolicyFactory)
|
||||||
IInlineConstraintResolver inlineConstraintResolver,
|
|
||||||
RouteTemplate parsedTemplate,
|
|
||||||
IDictionary<string, object> constraints)
|
|
||||||
{
|
{
|
||||||
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText);
|
var constraints = new Dictionary<string, IList<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (constraints != null)
|
foreach (var parameter in ParsedPattern.Parameters)
|
||||||
{
|
{
|
||||||
foreach (var kvp in constraints)
|
foreach (var parameterPolicy in parameter.ParameterPolicies)
|
||||||
{
|
{
|
||||||
constraintBuilder.AddConstraint(kvp.Key, kvp.Value);
|
var createdPolicy = parameterPolicyFactory.Create(parameter, parameterPolicy);
|
||||||
}
|
if (createdPolicy is IRouteConstraint routeConstraint)
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var parameter in parsedTemplate.Parameters)
|
|
||||||
{
|
|
||||||
if (parameter.IsOptional)
|
|
||||||
{
|
|
||||||
constraintBuilder.SetOptional(parameter.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var inlineConstraint in parameter.InlineConstraints)
|
|
||||||
{
|
|
||||||
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return constraintBuilder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RouteValueDictionary GetDefaults(
|
|
||||||
RouteTemplate parsedTemplate,
|
|
||||||
RouteValueDictionary defaults)
|
|
||||||
{
|
|
||||||
var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
|
|
||||||
|
|
||||||
foreach (var parameter in parsedTemplate.Parameters)
|
|
||||||
{
|
|
||||||
if (parameter.DefaultValue != null)
|
|
||||||
{
|
|
||||||
if (result.TryGetValue(parameter.Name, out var value))
|
|
||||||
{
|
{
|
||||||
if (!object.Equals(value, parameter.DefaultValue))
|
if (!constraints.TryGetValue(parameter.Name, out var paramConstraints))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
paramConstraints = new List<IRouteConstraint>();
|
||||||
string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name));
|
constraints.Add(parameter.Name, paramConstraints);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
paramConstraints.Add(routeConstraint);
|
||||||
{
|
|
||||||
result.Add(parameter.Name, parameter.DefaultValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return constraints;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
@ -12,7 +13,6 @@ 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.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new object();
|
||||||
private readonly IActionDescriptorCollectionProvider _actions;
|
private readonly IActionDescriptorCollectionProvider _actions;
|
||||||
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly DefaultHttpContext _httpContextInstance;
|
||||||
private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders;
|
private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders;
|
||||||
|
|
||||||
private List<Endpoint> _endpoints;
|
private List<Endpoint> _endpoints;
|
||||||
|
|
@ -55,8 +55,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
_actions = actions;
|
_actions = actions;
|
||||||
_invokerFactory = invokerFactory;
|
_invokerFactory = invokerFactory;
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();
|
_actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();
|
||||||
|
_httpContextInstance = new DefaultHttpContext() { RequestServices = serviceProvider };
|
||||||
|
|
||||||
ConventionalEndpointInfos = new List<MvcEndpointInfo>();
|
ConventionalEndpointInfos = new List<MvcEndpointInfo>();
|
||||||
|
|
||||||
|
|
@ -67,7 +67,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
private List<Endpoint> CreateEndpoints()
|
private List<Endpoint> CreateEndpoints()
|
||||||
{
|
{
|
||||||
List<Endpoint> endpoints = new List<Endpoint>();
|
var endpoints = new List<Endpoint>();
|
||||||
|
StringBuilder patternStringBuilder = null;
|
||||||
|
|
||||||
foreach (var action in _actions.ActionDescriptors.Items)
|
foreach (var action in _actions.ActionDescriptors.Items)
|
||||||
{
|
{
|
||||||
|
|
@ -81,8 +82,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
|
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
|
||||||
var conventionalRouteOrder = 0;
|
var conventionalRouteOrder = 0;
|
||||||
|
|
||||||
// Check each of the conventional templates to see if the action would be reachable
|
// Check each of the conventional patterns to see if the action would be reachable
|
||||||
// If the action and template are compatible then create an endpoint with the
|
// If the action and pattern are compatible then create an endpoint with the
|
||||||
// area/controller/action parameter parts replaced with literals
|
// area/controller/action parameter parts replaced with literals
|
||||||
//
|
//
|
||||||
// e.g. {controller}/{action} with HomeController.Index and HomeController.Login
|
// e.g. {controller}/{action} with HomeController.Index and HomeController.Login
|
||||||
|
|
@ -91,32 +92,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
// - Home/Login
|
// - Home/Login
|
||||||
foreach (var endpointInfo in ConventionalEndpointInfos)
|
foreach (var endpointInfo in ConventionalEndpointInfos)
|
||||||
{
|
{
|
||||||
var actionRouteValues = action.RouteValues;
|
|
||||||
var endpointTemplateSegments = endpointInfo.ParsedTemplate.Segments;
|
|
||||||
|
|
||||||
if (MatchRouteValue(action, endpointInfo, "Area")
|
if (MatchRouteValue(action, endpointInfo, "Area")
|
||||||
&& MatchRouteValue(action, endpointInfo, "Controller")
|
&& MatchRouteValue(action, endpointInfo, "Controller")
|
||||||
&& MatchRouteValue(action, endpointInfo, "Action"))
|
&& MatchRouteValue(action, endpointInfo, "Action"))
|
||||||
{
|
{
|
||||||
var newEndpointTemplate = TemplateParser.Parse(endpointInfo.Template);
|
var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList();
|
||||||
|
|
||||||
for (var i = 0; i < newEndpointTemplate.Segments.Count; i++)
|
for (var i = 0; i < newPathSegments.Count; i++)
|
||||||
{
|
{
|
||||||
// Check if the template can be shortened because the remaining parameters are optional
|
// Check if the pattern can be shortened because the remaining parameters are optional
|
||||||
//
|
//
|
||||||
// e.g. Matching template {controller=Home}/{action=Index}/{id?} against HomeController.Index
|
// e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index
|
||||||
// can resolve to the following endpoints:
|
// can resolve to the following endpoints:
|
||||||
// - /Home/Index/{id?}
|
// - /Home/Index/{id?}
|
||||||
// - /Home
|
// - /Home
|
||||||
// - /
|
// - /
|
||||||
if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newEndpointTemplate))
|
if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments))
|
||||||
{
|
{
|
||||||
var subTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments.Take(i));
|
var subPathSegments = newPathSegments.Take(i);
|
||||||
|
|
||||||
var subEndpoint = CreateEndpoint(
|
var subEndpoint = CreateEndpoint(
|
||||||
action,
|
action,
|
||||||
endpointInfo.Name,
|
endpointInfo.Name,
|
||||||
subTemplate,
|
GetPattern(ref patternStringBuilder, subPathSegments),
|
||||||
|
subPathSegments,
|
||||||
endpointInfo.Defaults,
|
endpointInfo.Defaults,
|
||||||
++conventionalRouteOrder,
|
++conventionalRouteOrder,
|
||||||
endpointInfo,
|
endpointInfo,
|
||||||
|
|
@ -124,25 +123,36 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
endpoints.Add(subEndpoint);
|
endpoints.Add(subEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
var segment = newEndpointTemplate.Segments[i];
|
List<RoutePatternPart> segmentParts = null; // Initialize only as needed
|
||||||
|
var segment = newPathSegments[i];
|
||||||
for (var j = 0; j < segment.Parts.Count; j++)
|
for (var j = 0; j < segment.Parts.Count; j++)
|
||||||
{
|
{
|
||||||
var part = segment.Parts[j];
|
var part = segment.Parts[j];
|
||||||
|
|
||||||
if (part.IsParameter && IsMvcParameter(part.Name))
|
if (part.IsParameter && part is RoutePatternParameterPart parameterPart && IsMvcParameter(parameterPart.Name))
|
||||||
{
|
{
|
||||||
|
if (segmentParts == null)
|
||||||
|
{
|
||||||
|
segmentParts = segment.Parts.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// Replace parameter with literal value
|
// Replace parameter with literal value
|
||||||
segment.Parts[j] = TemplatePart.CreateLiteral(action.RouteValues[part.Name]);
|
segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var newTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments);
|
// A parameter part was replaced so replace segment with updated parts
|
||||||
|
if (segmentParts != null)
|
||||||
|
{
|
||||||
|
newPathSegments[i] = RoutePatternFactory.Segment(segmentParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var endpoint = CreateEndpoint(
|
var endpoint = CreateEndpoint(
|
||||||
action,
|
action,
|
||||||
endpointInfo.Name,
|
endpointInfo.Name,
|
||||||
newTemplate,
|
GetPattern(ref patternStringBuilder, newPathSegments),
|
||||||
|
newPathSegments,
|
||||||
endpointInfo.Defaults,
|
endpointInfo.Defaults,
|
||||||
++conventionalRouteOrder,
|
++conventionalRouteOrder,
|
||||||
endpointInfo,
|
endpointInfo,
|
||||||
|
|
@ -157,6 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
action,
|
action,
|
||||||
action.AttributeRouteInfo.Name,
|
action.AttributeRouteInfo.Name,
|
||||||
action.AttributeRouteInfo.Template,
|
action.AttributeRouteInfo.Template,
|
||||||
|
RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments,
|
||||||
nonInlineDefaults: null,
|
nonInlineDefaults: null,
|
||||||
action.AttributeRouteInfo.Order,
|
action.AttributeRouteInfo.Order,
|
||||||
action.AttributeRouteInfo,
|
action.AttributeRouteInfo,
|
||||||
|
|
@ -166,6 +177,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints;
|
return endpoints;
|
||||||
|
|
||||||
|
string GetPattern(ref StringBuilder sb, IEnumerable<RoutePatternPathSegment> segments)
|
||||||
|
{
|
||||||
|
if (sb == null)
|
||||||
|
{
|
||||||
|
sb = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
RoutePatternWriter.WriteString(sb, segments);
|
||||||
|
var rawPattern = sb.ToString();
|
||||||
|
sb.Length = 0;
|
||||||
|
|
||||||
|
return rawPattern;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMvcParameter(string name)
|
private bool IsMvcParameter(string name)
|
||||||
|
|
@ -184,28 +209,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
int segmentIndex,
|
int segmentIndex,
|
||||||
ActionDescriptor action,
|
ActionDescriptor action,
|
||||||
MvcEndpointInfo endpointInfo,
|
MvcEndpointInfo endpointInfo,
|
||||||
RouteTemplate template)
|
List<RoutePatternPathSegment> pathSegments)
|
||||||
{
|
{
|
||||||
// Check whether the remaining segments are all optional and one or more of them is
|
// Check whether the remaining segments are all optional and one or more of them is
|
||||||
// for area/controller/action and has a default value
|
// for area/controller/action and has a default value
|
||||||
var usedDefaultValue = false;
|
var usedDefaultValue = false;
|
||||||
|
|
||||||
for (var i = segmentIndex; i < template.Segments.Count; i++)
|
for (var i = segmentIndex; i < pathSegments.Count; i++)
|
||||||
{
|
{
|
||||||
var segment = template.Segments[i];
|
var segment = pathSegments[i];
|
||||||
for (var j = 0; j < segment.Parts.Count; j++)
|
for (var j = 0; j < segment.Parts.Count; j++)
|
||||||
{
|
{
|
||||||
var part = segment.Parts[j];
|
var part = segment.Parts[j];
|
||||||
if (part.IsOptional || part.IsOptionalSeperator || part.IsCatchAll)
|
if (part.IsParameter && part is RoutePatternParameterPart parameterPart)
|
||||||
{
|
{
|
||||||
continue;
|
if (parameterPart.IsOptional || parameterPart.IsCatchAll)
|
||||||
}
|
|
||||||
if (part.IsParameter)
|
|
||||||
{
|
|
||||||
if (IsMvcParameter(part.Name))
|
|
||||||
{
|
{
|
||||||
if (endpointInfo.MergedDefaults[part.Name] is string defaultValue
|
continue;
|
||||||
&& action.RouteValues.TryGetValue(part.Name, out var routeValue)
|
}
|
||||||
|
|
||||||
|
if (IsMvcParameter(parameterPart.Name))
|
||||||
|
{
|
||||||
|
if (endpointInfo.MergedDefaults[parameterPart.Name] is string defaultValue
|
||||||
|
&& action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue)
|
||||||
&& string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
usedDefaultValue = true;
|
usedDefaultValue = true;
|
||||||
|
|
@ -213,6 +239,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart
|
||||||
|
&& separatorPart.Content == ".")
|
||||||
|
{
|
||||||
|
// Check if this pattern ends in an optional extension, e.g. ".{ext?}"
|
||||||
|
// Current literal must be "." and followed by a single optional parameter part
|
||||||
|
var nextPartIndex = j + 1;
|
||||||
|
|
||||||
|
if (nextPartIndex == segment.Parts.Count - 1
|
||||||
|
&& segment.Parts[nextPartIndex].IsParameter
|
||||||
|
&& segment.Parts[nextPartIndex] is RoutePatternParameterPart extensionParameterPart
|
||||||
|
&& extensionParameterPart.IsOptional)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop because there is a non-optional/non-defaulted trailing value
|
// Stop because there is a non-optional/non-defaulted trailing value
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -227,8 +268,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue))
|
if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue))
|
||||||
{
|
{
|
||||||
// Action does not have a value for this routeKey, most likely because action is not in an area
|
// Action does not have a value for this routeKey, most likely because action is not in an area
|
||||||
// Check that the template does not have a parameter for the routeKey
|
// Check that the pattern does not have a parameter for the routeKey
|
||||||
var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
|
var matchingParameter = endpointInfo.ParsedPattern.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
|
||||||
if (matchingParameter == null)
|
if (matchingParameter == null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -241,18 +282,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
|
var matchingParameter = endpointInfo.ParsedPattern.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
|
||||||
if (matchingParameter != null)
|
if (matchingParameter != null)
|
||||||
{
|
{
|
||||||
// Check that the value matches against constraints on that parameter
|
// Check that the value matches against constraints on that parameter
|
||||||
// e.g. For {controller:regex((Home|Login))} the controller value must match the regex
|
// e.g. For {controller:regex((Home|Login))} the controller value must match the regex
|
||||||
//
|
if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraints))
|
||||||
// REVIEW: This is really ugly
|
|
||||||
if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint)
|
|
||||||
&& !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest))
|
|
||||||
{
|
{
|
||||||
// Did not match constraint
|
foreach (var constraint in constraints)
|
||||||
return false;
|
{
|
||||||
|
if (!constraint.Match(_httpContextInstance, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest))
|
||||||
|
{
|
||||||
|
// Did not match constraint
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -265,7 +309,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private RouteEndpoint CreateEndpoint(
|
private RouteEndpoint CreateEndpoint(
|
||||||
ActionDescriptor action,
|
ActionDescriptor action,
|
||||||
string routeName,
|
string routeName,
|
||||||
string template,
|
string patternRawText,
|
||||||
|
IEnumerable<RoutePatternPathSegment> segments,
|
||||||
object nonInlineDefaults,
|
object nonInlineDefaults,
|
||||||
int order,
|
int order,
|
||||||
object source,
|
object source,
|
||||||
|
|
@ -301,7 +346,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
var endpoint = new RouteEndpoint(
|
var endpoint = new RouteEndpoint(
|
||||||
requestDelegate,
|
requestDelegate,
|
||||||
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments),
|
||||||
order,
|
order,
|
||||||
metadataCollection,
|
metadataCollection,
|
||||||
action.DisplayName);
|
action.DisplayName);
|
||||||
|
|
@ -377,13 +422,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
// Template: {controller}/{action}/{category}/{id?}
|
// Template: {controller}/{action}/{category}/{id?}
|
||||||
// Defaults(in-line or non in-line): category=products
|
// Defaults(in-line or non in-line): category=products
|
||||||
// Required values: controller=foo, action=bar
|
// Required values: controller=foo, action=bar
|
||||||
// Final constructed template: foo/bar/{category}/{id?}
|
// Final constructed pattern: foo/bar/{category}/{id?}
|
||||||
// Final defaults: controller=foo, action=bar, category=products
|
// Final defaults: controller=foo, action=bar, category=products
|
||||||
//
|
//
|
||||||
// Template: {controller=Home}/{action=Index}/{category=products}/{id?}
|
// Template: {controller=Home}/{action=Index}/{category=products}/{id?}
|
||||||
// Defaults: controller=Home, action=Index, category=products
|
// Defaults: controller=Home, action=Index, category=products
|
||||||
// Required values: controller=foo, action=bar
|
// Required values: controller=foo, action=bar
|
||||||
// Final constructed template: foo/bar/{category}/{id?}
|
// Final constructed pattern: foo/bar/{category}/{id?}
|
||||||
// Final defaults: controller=foo, action=bar, category=products
|
// Final defaults: controller=foo, action=bar, category=products
|
||||||
private void EnsureRequiredValuesInDefaults(IDictionary<string, string> requiredValues, RouteValueDictionary defaults)
|
private void EnsureRequiredValuesInDefaults(IDictionary<string, string> requiredValues, RouteValueDictionary defaults)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// 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.Text;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
{
|
||||||
|
internal static class RoutePatternWriter
|
||||||
|
{
|
||||||
|
public static void WriteString(StringBuilder sb, IEnumerable<RoutePatternPathSegment> routeSegments)
|
||||||
|
{
|
||||||
|
foreach (var segment in routeSegments)
|
||||||
|
{
|
||||||
|
if (sb.Length > 0)
|
||||||
|
{
|
||||||
|
sb.Append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteString(sb, segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteString(StringBuilder sb, RoutePatternPathSegment segment)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < segment.Parts.Count; i++)
|
||||||
|
{
|
||||||
|
WriteString(sb, segment.Parts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteString(StringBuilder sb, RoutePatternPart part)
|
||||||
|
{
|
||||||
|
if (part.IsParameter && part is RoutePatternParameterPart parameterPart)
|
||||||
|
{
|
||||||
|
sb.Append("{");
|
||||||
|
if (parameterPart.IsCatchAll)
|
||||||
|
{
|
||||||
|
sb.Append("*");
|
||||||
|
}
|
||||||
|
sb.Append(parameterPart.Name);
|
||||||
|
foreach (var item in parameterPart.ParameterPolicies)
|
||||||
|
{
|
||||||
|
sb.Append(":");
|
||||||
|
sb.Append(item.Content);
|
||||||
|
}
|
||||||
|
if (parameterPart.Default != null)
|
||||||
|
{
|
||||||
|
sb.Append("=");
|
||||||
|
sb.Append(parameterPart.Default);
|
||||||
|
}
|
||||||
|
if (parameterPart.IsOptional)
|
||||||
|
{
|
||||||
|
sb.Append("?");
|
||||||
|
}
|
||||||
|
sb.Append("}");
|
||||||
|
}
|
||||||
|
else if (part is RoutePatternLiteralPart literalPart)
|
||||||
|
{
|
||||||
|
sb.Append(literalPart.Content);
|
||||||
|
}
|
||||||
|
else if (part is RoutePatternSeparatorPart separatorPart)
|
||||||
|
{
|
||||||
|
sb.Append(separatorPart.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
|
||||||
{
|
|
||||||
internal static class RouteTemplateWriter
|
|
||||||
{
|
|
||||||
public static string ToString(IEnumerable<TemplateSegment> routeSegments)
|
|
||||||
{
|
|
||||||
return string.Join("/", routeSegments.Select(s => ToString(s)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ToString(TemplateSegment templateSegment)
|
|
||||||
{
|
|
||||||
return string.Join(string.Empty, templateSegment.Parts.Select(p => ToString(p)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ToString(TemplatePart templatePart)
|
|
||||||
{
|
|
||||||
if (templatePart.IsParameter)
|
|
||||||
{
|
|
||||||
var partText = "{";
|
|
||||||
if (templatePart.IsCatchAll)
|
|
||||||
{
|
|
||||||
partText += "*";
|
|
||||||
}
|
|
||||||
partText += templatePart.Name;
|
|
||||||
foreach (var item in templatePart.InlineConstraints)
|
|
||||||
{
|
|
||||||
partText += ":";
|
|
||||||
partText += item.Constraint;
|
|
||||||
}
|
|
||||||
if (templatePart.DefaultValue != null)
|
|
||||||
{
|
|
||||||
partText += "=";
|
|
||||||
partText += templatePart.DefaultValue;
|
|
||||||
}
|
|
||||||
if (templatePart.IsOptional)
|
|
||||||
{
|
|
||||||
partText += "?";
|
|
||||||
}
|
|
||||||
partText += "}";
|
|
||||||
|
|
||||||
return partText;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return templatePart.Text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
// Run really late.
|
// Run really late.
|
||||||
public override int Order => 100000;
|
public override int Order => 100000;
|
||||||
|
|
||||||
public void Apply(HttpContext httpContext, CandidateSet candidateSet)
|
public Task ApplyAsync(HttpContext httpContext, EndpointFeature endpointFeature, CandidateSet candidateSet)
|
||||||
{
|
{
|
||||||
// PERF: we can skip over action constraints if there aren't any app-wide.
|
// PERF: we can skip over action constraints if there aren't any app-wide.
|
||||||
//
|
//
|
||||||
|
|
@ -47,6 +47,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
ApplyActionConstraints(httpContext, candidateSet);
|
ApplyActionConstraints(httpContext, candidateSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyActionConstraints(
|
private void ApplyActionConstraints(
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder
|
||||||
|
|
||||||
var endpointInfo = Assert.Single(mvcEndpointDataSource.ConventionalEndpointInfos);
|
var endpointInfo = Assert.Single(mvcEndpointDataSource.ConventionalEndpointInfos);
|
||||||
Assert.Equal("default", endpointInfo.Name);
|
Assert.Equal("default", endpointInfo.Name);
|
||||||
Assert.Equal("{controller=Home}/{action=Index}/{id?}", endpointInfo.Template);
|
Assert.Equal("{controller=Home}/{action=Index}/{id?}", endpointInfo.Pattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ 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.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -185,9 +186,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
[InlineData("{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" })]
|
[InlineData("{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" })]
|
||||||
[InlineData("{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" })]
|
[InlineData("{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" })]
|
||||||
[InlineData("{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" })]
|
[InlineData("{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" })]
|
||||||
//[InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })]
|
[InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })]
|
||||||
//[InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })]
|
[InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })]
|
||||||
public void Endpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates)
|
public void Endpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||||
|
|
@ -199,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var endpoints = dataSource.Endpoints;
|
var endpoints = dataSource.Endpoints;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var inspectors = finalEndpointTemplates
|
var inspectors = finalEndpointPatterns
|
||||||
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
|
@ -694,12 +695,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
IDictionary<string, object> constraints = null,
|
IDictionary<string, object> constraints = null,
|
||||||
RouteValueDictionary dataTokens = null)
|
RouteValueDictionary dataTokens = null)
|
||||||
{
|
{
|
||||||
var routeOptions = new RouteOptions();
|
var serviceCollection = new ServiceCollection();
|
||||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
serviceCollection.AddRouting();
|
||||||
routeOptionsSetup.Configure(routeOptions);
|
|
||||||
|
|
||||||
var constraintResolver = new DefaultInlineConstraintResolver(Options.Create<RouteOptions>(routeOptions));
|
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||||
return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, constraintResolver);
|
serviceCollection.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||||
|
|
||||||
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
var parameterPolicyFactory = serviceProvider.GetRequiredService<ParameterPolicyFactory>();
|
||||||
|
return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, parameterPolicyFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params object[] requiredValues)
|
private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params object[] requiredValues)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
||||||
{
|
{
|
||||||
public class RouteTemplateWriterTests
|
public class RoutePatternWriterTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(@"")]
|
[InlineData(@"")]
|
||||||
|
|
@ -23,11 +24,12 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
||||||
[InlineData(@"{p1}.{p2}.{p3}")]
|
[InlineData(@"{p1}.{p2}.{p3}")]
|
||||||
public void ToString_TemplateRoundtrips(string template)
|
public void ToString_TemplateRoundtrips(string template)
|
||||||
{
|
{
|
||||||
var routeTemplate = TemplateParser.Parse(template);
|
var routePattern = RoutePatternFactory.Parse(template);
|
||||||
|
|
||||||
var output = RouteTemplateWriter.ToString(routeTemplate.Segments);
|
var sb = new StringBuilder();
|
||||||
|
RoutePatternWriter.WriteString(sb, routePattern.PathSegments);
|
||||||
|
|
||||||
Assert.Equal(template, output);
|
Assert.Equal(template, sb.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
public class ActionConstraintMatcherPolicyTest
|
public class ActionConstraintMatcherPolicyTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_CanBeAmbiguous()
|
public async Task Apply_CanBeAmbiguous()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var actions = new ActionDescriptor[]
|
var actions = new ActionDescriptor[]
|
||||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var selector = CreateSelector(actions);
|
var selector = CreateSelector(actions);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(new DefaultHttpContext(), candidateSet);
|
await selector.ApplyAsync(new DefaultHttpContext(), new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(candidateSet[0].IsValidCandidate);
|
Assert.True(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_PrefersActionWithConstraints()
|
public async Task Apply_PrefersActionWithConstraints()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var actionWithConstraints = new ActionDescriptor()
|
var actionWithConstraints = new ActionDescriptor()
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(candidateSet[0].IsValidCandidate);
|
Assert.True(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_ConstraintsRejectAll()
|
public async Task Apply_ConstraintsRejectAll()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var action1 = new ActionDescriptor()
|
var action1 = new ActionDescriptor()
|
||||||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(candidateSet[0].IsValidCandidate);
|
Assert.False(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_ConstraintsRejectAll_DifferentStages()
|
public async Task Apply_ConstraintsRejectAll_DifferentStages()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var action1 = new ActionDescriptor()
|
var action1 = new ActionDescriptor()
|
||||||
|
|
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(candidateSet[0].IsValidCandidate);
|
Assert.False(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
// Due to ordering of stages, the first action will be better.
|
// Due to ordering of stages, the first action will be better.
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_ConstraintsInOrder()
|
public async Task Apply_ConstraintsInOrder()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var best = new ActionDescriptor()
|
var best = new ActionDescriptor()
|
||||||
|
|
@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(candidateSet[0].IsValidCandidate);
|
Assert.True(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_SkipsOverInvalidEndpoints()
|
public async Task Apply_SkipsOverInvalidEndpoints()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var best = new ActionDescriptor()
|
var best = new ActionDescriptor()
|
||||||
|
|
@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(candidateSet[0].IsValidCandidate);
|
Assert.False(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -220,7 +220,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_IncludesNonMvcEndpoints()
|
public async Task Apply_IncludesNonMvcEndpoints()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var action1 = new ActionDescriptor()
|
var action1 = new ActionDescriptor()
|
||||||
|
|
@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(candidateSet[0].IsValidCandidate);
|
Assert.False(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -256,7 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
// Due to ordering of stages, the first action will be better.
|
// Due to ordering of stages, the first action will be better.
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_ConstraintsInOrder_MultipleStages()
|
public async Task Apply_ConstraintsInOrder_MultipleStages()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var best = new ActionDescriptor()
|
var best = new ActionDescriptor()
|
||||||
|
|
@ -287,7 +287,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(candidateSet[0].IsValidCandidate);
|
Assert.True(candidateSet[0].IsValidCandidate);
|
||||||
|
|
@ -295,7 +295,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Apply_Fallback_ToActionWithoutConstraints()
|
public async Task Apply_Fallback_ToActionWithoutConstraints()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var nomatch1 = new ActionDescriptor()
|
var nomatch1 = new ActionDescriptor()
|
||||||
|
|
@ -328,7 +328,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = CreateHttpContext("POST");
|
var httpContext = CreateHttpContext("POST");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
selector.Apply(httpContext, candidateSet);
|
await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(candidateSet[0].IsValidCandidate);
|
Assert.True(candidateSet[0].IsValidCandidate);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue