Added support for route constraints in Dispatcher world
This commit is contained in:
parent
576c14a1b5
commit
85e92ab3cc
|
|
@ -34,6 +34,7 @@ namespace Benchmarks
|
||||||
template: "/plaintext",
|
template: "/plaintext",
|
||||||
defaults: new RouteValueDictionary(),
|
defaults: new RouteValueDictionary(),
|
||||||
requiredValues: new RouteValueDictionary(),
|
requiredValues: new RouteValueDictionary(),
|
||||||
|
nonInlineMatchProcessorReferences: null,
|
||||||
order: 0,
|
order: 0,
|
||||||
metadata: EndpointMetadataCollection.Empty,
|
metadata: EndpointMetadataCollection.Empty,
|
||||||
displayName: "Plaintext"),
|
displayName: "Plaintext"),
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,14 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
(next) => (context) => Task.CompletedTask,
|
(next) => (context) => Task.CompletedTask,
|
||||||
template,
|
template,
|
||||||
new RouteValueDictionary(),
|
new RouteValueDictionary(),
|
||||||
new RouteValueDictionary(),
|
new RouteValueDictionary(),
|
||||||
0,
|
new List<MatchProcessorReference>(),
|
||||||
new EndpointMetadataCollection(metadata),
|
0,
|
||||||
template);
|
EndpointMetadataCollection.Empty,
|
||||||
|
template);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static int[] SampleRequests(int endpointCount, int count)
|
internal static int[] SampleRequests(int endpointCount, int count)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using Microsoft.AspNetCore.Routing.Matchers;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace DispatcherSample.Web
|
||||||
|
{
|
||||||
|
internal class EndsWithStringMatchProcessor : MatchProcessorBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<EndsWithStringMatchProcessor> _logger;
|
||||||
|
|
||||||
|
public EndsWithStringMatchProcessor(ILogger<EndsWithStringMatchProcessor> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Process(object value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
var endsWith = valueString.EndsWith(ConstraintArgument, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (!endsWith)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(
|
||||||
|
$"Parameter '{ParameterName}' with value '{valueString}' does not end with '{ConstraintArgument}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return endsWith;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
// 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;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matchers;
|
using Microsoft.AspNetCore.Routing.Matchers;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -17,7 +19,13 @@ namespace DispatcherSample.Web
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddRouting();
|
services.AddTransient<EndsWithStringMatchProcessor>();
|
||||||
|
|
||||||
|
services.AddRouting(options =>
|
||||||
|
{
|
||||||
|
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor));
|
||||||
|
});
|
||||||
|
|
||||||
services.AddDispatcher(options =>
|
services.AddDispatcher(options =>
|
||||||
{
|
{
|
||||||
options.DataSources.Add(new DefaultEndpointDataSource(new[]
|
options.DataSources.Add(new DefaultEndpointDataSource(new[]
|
||||||
|
|
@ -31,7 +39,13 @@ namespace DispatcherSample.Web
|
||||||
response.ContentLength = payloadLength;
|
response.ContentLength = payloadLength;
|
||||||
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
|
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
|
||||||
},
|
},
|
||||||
"/", new RouteValueDictionary(), new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "Home"),
|
"/",
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new List<MatchProcessorReference>(),
|
||||||
|
0,
|
||||||
|
EndpointMetadataCollection.Empty,
|
||||||
|
"Home"),
|
||||||
new MatcherEndpoint((next) => (httpContext) =>
|
new MatcherEndpoint((next) => (httpContext) =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
|
@ -41,7 +55,41 @@ namespace DispatcherSample.Web
|
||||||
response.ContentLength = payloadLength;
|
response.ContentLength = payloadLength;
|
||||||
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
||||||
},
|
},
|
||||||
"/plaintext", new RouteValueDictionary(), new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "Plaintext"),
|
"/plaintext",
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new List<MatchProcessorReference>(),
|
||||||
|
0,
|
||||||
|
EndpointMetadataCollection.Empty,
|
||||||
|
"Plaintext"),
|
||||||
|
new MatcherEndpoint((next) => (httpContext) =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
return response.WriteAsync("WithConstraints");
|
||||||
|
},
|
||||||
|
"/withconstraints/{id:endsWith(_001)}",
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new List<MatchProcessorReference>(),
|
||||||
|
0,
|
||||||
|
EndpointMetadataCollection.Empty,
|
||||||
|
"withconstraints"),
|
||||||
|
new MatcherEndpoint((next) => (httpContext) =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
return response.WriteAsync("withoptionalconstraints");
|
||||||
|
},
|
||||||
|
"/withoptionalconstraints/{id:endsWith(_001)?}",
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new List<MatchProcessorReference>(),
|
||||||
|
0,
|
||||||
|
EndpointMetadataCollection.Empty,
|
||||||
|
"withoptionalconstraints"),
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IRouteConstraint CreateConstraint(Type constraintType, string argumentString)
|
internal static IRouteConstraint CreateConstraint(Type constraintType, string argumentString)
|
||||||
{
|
{
|
||||||
// No arguments - call the default constructor
|
// No arguments - call the default constructor
|
||||||
if (argumentString == null)
|
if (argumentString == null)
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,18 @@ using Microsoft.Extensions.ObjectPool;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public class DefaultLinkGenerator : ILinkGenerator
|
internal class DefaultLinkGenerator : ILinkGenerator
|
||||||
{
|
{
|
||||||
|
private readonly MatchProcessorFactory _matchProcessorFactory;
|
||||||
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
||||||
private readonly ILogger<DefaultLinkGenerator> _logger;
|
private readonly ILogger<DefaultLinkGenerator> _logger;
|
||||||
|
|
||||||
public DefaultLinkGenerator(
|
public DefaultLinkGenerator(
|
||||||
|
MatchProcessorFactory matchProcessorFactory,
|
||||||
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
||||||
ILogger<DefaultLinkGenerator> logger)
|
ILogger<DefaultLinkGenerator> logger)
|
||||||
{
|
{
|
||||||
|
_matchProcessorFactory = matchProcessorFactory;
|
||||||
_uriBuildingContextPool = uriBuildingContextPool;
|
_uriBuildingContextPool = uriBuildingContextPool;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +64,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
foreach (var endpoint in matcherEndpoints)
|
foreach (var endpoint in matcherEndpoints)
|
||||||
{
|
{
|
||||||
link = GetLink(endpoint.ParsedTemplate, endpoint.Defaults, explicitValues, ambientValues);
|
link = GetLink(endpoint, explicitValues, ambientValues);
|
||||||
if (link != null)
|
if (link != null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -72,27 +75,55 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetLink(
|
private string GetLink(
|
||||||
RouteTemplate template,
|
MatcherEndpoint endpoint,
|
||||||
RouteValueDictionary defaults,
|
|
||||||
RouteValueDictionary explicitValues,
|
RouteValueDictionary explicitValues,
|
||||||
RouteValueDictionary ambientValues)
|
RouteValueDictionary ambientValues)
|
||||||
{
|
{
|
||||||
var templateBinder = new TemplateBinder(
|
var templateBinder = new TemplateBinder(
|
||||||
UrlEncoder.Default,
|
UrlEncoder.Default,
|
||||||
_uriBuildingContextPool,
|
_uriBuildingContextPool,
|
||||||
template,
|
endpoint.ParsedTemplate,
|
||||||
defaults);
|
endpoint.Defaults);
|
||||||
|
|
||||||
var values = templateBinder.GetValues(ambientValues, explicitValues);
|
var templateValuesResult = templateBinder.GetValues(ambientValues, explicitValues);
|
||||||
if (values == null)
|
if (templateValuesResult == null)
|
||||||
{
|
{
|
||||||
// We're missing one of the required values for this route.
|
// We're missing one of the required values for this route.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: route constraint matching here
|
if (!Match(endpoint, templateValuesResult.CombinedValues))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return templateBinder.BindValues(values.AcceptedValues);
|
return templateBinder.BindValues(templateValuesResult.AcceptedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Match(MatcherEndpoint endpoint, RouteValueDictionary routeValues)
|
||||||
|
{
|
||||||
|
if (routeValues == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(routeValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < endpoint.MatchProcessorReferences.Count; i++)
|
||||||
|
{
|
||||||
|
var matchProcessorReference = endpoint.MatchProcessorReferences[i];
|
||||||
|
var parameter = endpoint.ParsedTemplate.GetParameter(matchProcessorReference.ParameterName);
|
||||||
|
if (parameter.IsOptional && !routeValues.ContainsKey(parameter.Name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchProcessor = _matchProcessorFactory.Create(matchProcessorReference);
|
||||||
|
if (!matchProcessor.ProcessOutbound(httpContext: null, routeValues))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing.Constraints;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
|
using Microsoft.AspNetCore.Routing.Matchers;
|
||||||
using Microsoft.AspNetCore.Routing.Tree;
|
using Microsoft.AspNetCore.Routing.Tree;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -28,6 +30,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
throw new ArgumentNullException(nameof(services));
|
throw new ArgumentNullException(nameof(services));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
services.TryAddSingleton<MatchProcessorFactory, DefaultMatchProcessorFactory>();
|
||||||
services.TryAddTransient<IInlineConstraintResolver, DefaultInlineConstraintResolver>();
|
services.TryAddTransient<IInlineConstraintResolver, DefaultInlineConstraintResolver>();
|
||||||
services.TryAddSingleton<ObjectPool<UriBuildingContext>>(s =>
|
services.TryAddSingleton<ObjectPool<UriBuildingContext>>(s =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
internal class NullRouter : IRouter
|
||||||
|
{
|
||||||
|
public static readonly NullRouter Instance = new NullRouter();
|
||||||
|
|
||||||
|
private NullRouter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RouteAsync(RouteContext context)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
// 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 Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
internal class DefaultMatchProcessorFactory : MatchProcessorFactory
|
||||||
|
{
|
||||||
|
private readonly RouteOptions _options;
|
||||||
|
private readonly ILogger<DefaultMatchProcessorFactory> _logger;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public DefaultMatchProcessorFactory(
|
||||||
|
IOptions<RouteOptions> options,
|
||||||
|
ILogger<DefaultMatchProcessorFactory> logger,
|
||||||
|
IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_options = options.Value;
|
||||||
|
_logger = logger;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MatchProcessor Create(MatchProcessorReference matchProcessorReference)
|
||||||
|
{
|
||||||
|
if (matchProcessorReference == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(matchProcessorReference));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchProcessorReference.MatchProcessor != null)
|
||||||
|
{
|
||||||
|
return matchProcessorReference.MatchProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example:
|
||||||
|
// {productId:regex(\d+)}
|
||||||
|
//
|
||||||
|
// ParameterName: productId
|
||||||
|
// ConstraintText: regex(\d+)
|
||||||
|
// ConstraintName: regex
|
||||||
|
// ConstraintArgument: \d+
|
||||||
|
|
||||||
|
(var constraintName, var constraintArgument) = Parse(matchProcessorReference.ConstraintText);
|
||||||
|
|
||||||
|
if (!_options.ConstraintMap.TryGetValue(constraintName, out var constraintType))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No constraint has been registered with name '{constraintName}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var processor = ResolveMatchProcessor(
|
||||||
|
matchProcessorReference.ParameterName,
|
||||||
|
matchProcessorReference.Optional,
|
||||||
|
constraintType,
|
||||||
|
constraintArgument);
|
||||||
|
|
||||||
|
if (processor != null)
|
||||||
|
{
|
||||||
|
return processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeof(IRouteConstraint).IsAssignableFrom(constraintType))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
|
||||||
|
constraintType, constraintName, typeof(IRouteConstraint).Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return CreateMatchProcessorFromRouteConstraint(
|
||||||
|
matchProcessorReference.ParameterName,
|
||||||
|
constraintType,
|
||||||
|
constraintArgument);
|
||||||
|
}
|
||||||
|
catch (RouteCreationException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"An error occurred while trying to create an instance of route constraint '{constraintType.FullName}'.",
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MatchProcessor CreateMatchProcessorFromRouteConstraint(
|
||||||
|
string parameterName,
|
||||||
|
Type constraintType,
|
||||||
|
string constraintArgument)
|
||||||
|
{
|
||||||
|
var routeConstraint = DefaultInlineConstraintResolver.CreateConstraint(constraintType, constraintArgument);
|
||||||
|
return (new MatchProcessorReference(parameterName, routeConstraint)).MatchProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MatchProcessor ResolveMatchProcessor(
|
||||||
|
string parameterName,
|
||||||
|
bool optional,
|
||||||
|
Type constraintType,
|
||||||
|
string constraintArgument)
|
||||||
|
{
|
||||||
|
if (constraintType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(constraintType));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeof(MatchProcessor).IsAssignableFrom(constraintType))
|
||||||
|
{
|
||||||
|
// Since a constraint type could be of type IRouteConstraint, do not throw
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registeredProcessor = _serviceProvider.GetRequiredService(constraintType);
|
||||||
|
if (registeredProcessor is MatchProcessor matchProcessor)
|
||||||
|
{
|
||||||
|
if (optional)
|
||||||
|
{
|
||||||
|
matchProcessor = new OptionalMatchProcessor(matchProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchProcessor.Initialize(parameterName, constraintArgument);
|
||||||
|
return matchProcessor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Registered constraint type '{constraintType}' is not of type '{typeof(MatchProcessor)}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (string constraintName, string constraintArgument) Parse(string constraintText)
|
||||||
|
{
|
||||||
|
string constraintName;
|
||||||
|
string constraintArgument;
|
||||||
|
var indexOfFirstOpenParens = constraintText.IndexOf('(');
|
||||||
|
if (indexOfFirstOpenParens >= 0 && constraintText.EndsWith(")", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
constraintName = constraintText.Substring(0, indexOfFirstOpenParens);
|
||||||
|
constraintArgument = constraintText.Substring(
|
||||||
|
indexOfFirstOpenParens + 1,
|
||||||
|
constraintText.Length - indexOfFirstOpenParens - 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
constraintName = constraintText;
|
||||||
|
constraintArgument = null;
|
||||||
|
}
|
||||||
|
return (constraintName, constraintArgument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
public abstract class MatchProcessor
|
||||||
|
{
|
||||||
|
public virtual void Initialize(string parameterName, string constraintArgument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool ProcessInbound(HttpContext httpContext, RouteValueDictionary values);
|
||||||
|
|
||||||
|
public abstract bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
public abstract class MatchProcessorBase : MatchProcessor
|
||||||
|
{
|
||||||
|
public string ParameterName { get; private set; }
|
||||||
|
|
||||||
|
public string ConstraintArgument { get; private set; }
|
||||||
|
|
||||||
|
public override void Initialize(string parameterName, string constraintArgument)
|
||||||
|
{
|
||||||
|
ParameterName = parameterName;
|
||||||
|
ConstraintArgument = constraintArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool Process(object value);
|
||||||
|
|
||||||
|
public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
return Process(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
return Process(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Process(RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
if (!values.TryGetValue(ParameterName, out var value) || value == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Process(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
internal abstract class MatchProcessorFactory
|
||||||
|
{
|
||||||
|
public abstract MatchProcessor Create(MatchProcessorReference matchProcessorReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
public sealed class MatchProcessorReference
|
||||||
|
{
|
||||||
|
// Example:
|
||||||
|
// api/products/{productId:regex(\d+)}
|
||||||
|
//
|
||||||
|
// ParameterName = productId
|
||||||
|
// ConstraintText = regex(\d+)
|
||||||
|
// ConstraintArgument = \d+
|
||||||
|
|
||||||
|
public MatchProcessorReference(string parameterName, string constraintText)
|
||||||
|
{
|
||||||
|
ParameterName = parameterName;
|
||||||
|
ConstraintText = constraintText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatchProcessorReference(string parameterName, bool optional, string constraintText)
|
||||||
|
{
|
||||||
|
ParameterName = parameterName;
|
||||||
|
Optional = optional;
|
||||||
|
ConstraintText = constraintText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatchProcessorReference(string parameterName, MatchProcessor matchProcessor)
|
||||||
|
{
|
||||||
|
ParameterName = parameterName;
|
||||||
|
MatchProcessor = matchProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MatchProcessor MatchProcessor { get; private set; }
|
||||||
|
|
||||||
|
internal string ConstraintText { get; private set; }
|
||||||
|
|
||||||
|
internal string ParameterName { get; private set; }
|
||||||
|
|
||||||
|
internal bool Optional { get; private set; }
|
||||||
|
|
||||||
|
public MatchProcessorReference(string parameterName, IRouteConstraint routeConstraint)
|
||||||
|
: this(parameterName, new RouteConstraintMatchProcessorAdapter(parameterName, routeConstraint))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RouteConstraintMatchProcessorAdapter : MatchProcessor
|
||||||
|
{
|
||||||
|
public string ParameterName { get; private set; }
|
||||||
|
|
||||||
|
public IRouteConstraint RouteConstraint { get; }
|
||||||
|
|
||||||
|
public RouteConstraintMatchProcessorAdapter(string parameterName, IRouteConstraint routeConstraint)
|
||||||
|
{
|
||||||
|
ParameterName = parameterName;
|
||||||
|
RouteConstraint = routeConstraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize(string parameterName, string constraintArgument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary routeValues)
|
||||||
|
{
|
||||||
|
return RouteConstraint.Match(
|
||||||
|
httpContext,
|
||||||
|
NullRouter.Instance,
|
||||||
|
ParameterName,
|
||||||
|
routeValues,
|
||||||
|
RouteDirection.IncomingRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
return RouteConstraint.Match(
|
||||||
|
httpContext,
|
||||||
|
NullRouter.Instance,
|
||||||
|
ParameterName,
|
||||||
|
values,
|
||||||
|
RouteDirection.UrlGeneration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
|
@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
string template,
|
string template,
|
||||||
RouteValueDictionary defaults,
|
RouteValueDictionary defaults,
|
||||||
RouteValueDictionary requiredValues,
|
RouteValueDictionary requiredValues,
|
||||||
|
List<MatchProcessorReference> nonInlineMatchProcessorReferences,
|
||||||
int order,
|
int order,
|
||||||
EndpointMetadataCollection metadata,
|
EndpointMetadataCollection metadata,
|
||||||
string displayName)
|
string displayName)
|
||||||
|
|
@ -44,6 +46,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
RequiredValues = requiredValues;
|
RequiredValues = requiredValues;
|
||||||
var mergedDefaults = GetDefaults(ParsedTemplate, defaults);
|
var mergedDefaults = GetDefaults(ParsedTemplate, defaults);
|
||||||
Defaults = mergedDefaults;
|
Defaults = mergedDefaults;
|
||||||
|
|
||||||
|
var mergedReferences = MergeMatchProcessorReferences(ParsedTemplate, nonInlineMatchProcessorReferences);
|
||||||
|
MatchProcessorReferences = mergedReferences.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order { get; }
|
public int Order { get; }
|
||||||
|
|
@ -57,6 +62,8 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
// Todo: needs review
|
// Todo: needs review
|
||||||
public RouteTemplate ParsedTemplate { get; }
|
public RouteTemplate ParsedTemplate { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<MatchProcessorReference> MatchProcessorReferences { get; }
|
||||||
|
|
||||||
// Merge inline and non inline defaults into one
|
// Merge inline and non inline defaults into one
|
||||||
private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate, RouteValueDictionary nonInlineDefaults)
|
private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate, RouteValueDictionary nonInlineDefaults)
|
||||||
{
|
{
|
||||||
|
|
@ -81,5 +88,33 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<MatchProcessorReference> MergeMatchProcessorReferences(
|
||||||
|
RouteTemplate parsedTemplate,
|
||||||
|
List<MatchProcessorReference> nonInlineReferences)
|
||||||
|
{
|
||||||
|
var matchProcessorReferences = new List<MatchProcessorReference>();
|
||||||
|
|
||||||
|
if (nonInlineReferences != null)
|
||||||
|
{
|
||||||
|
matchProcessorReferences.AddRange(nonInlineReferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var parameter in parsedTemplate.Parameters)
|
||||||
|
{
|
||||||
|
if (parameter.InlineConstraints != null)
|
||||||
|
{
|
||||||
|
foreach (var constraint in parameter.InlineConstraints)
|
||||||
|
{
|
||||||
|
matchProcessorReferences.Add(
|
||||||
|
new MatchProcessorReference(
|
||||||
|
parameter.Name,
|
||||||
|
optional: parameter.IsOptional,
|
||||||
|
constraintText: constraint.Constraint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchProcessorReferences;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
internal class OptionalMatchProcessor : MatchProcessor
|
||||||
|
{
|
||||||
|
private readonly MatchProcessor _innerMatchProcessor;
|
||||||
|
|
||||||
|
public OptionalMatchProcessor(MatchProcessor innerMatchProcessor)
|
||||||
|
{
|
||||||
|
_innerMatchProcessor = innerMatchProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ParameterName { get; private set; }
|
||||||
|
|
||||||
|
public override void Initialize(string parameterName, string constraintArgument)
|
||||||
|
{
|
||||||
|
ParameterName = parameterName;
|
||||||
|
_innerMatchProcessor.Initialize(parameterName, constraintArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
return Process(httpContext, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
return Process(httpContext, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Process(HttpContext httpContext, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
if (values.TryGetValue(ParameterName, out var value))
|
||||||
|
{
|
||||||
|
return _innerMatchProcessor.ProcessInbound(httpContext, values);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,20 +17,20 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
{
|
{
|
||||||
internal class TreeMatcher : Matcher
|
internal class TreeMatcher : Matcher
|
||||||
{
|
{
|
||||||
private readonly IInlineConstraintResolver _constraintFactory;
|
private readonly MatchProcessorFactory _matchProcessorFactory;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly EndpointSelector _endpointSelector;
|
private readonly EndpointSelector _endpointSelector;
|
||||||
private readonly DataSourceDependantCache<UrlMatchingTree[]> _cache;
|
private readonly DataSourceDependantCache<UrlMatchingTree[]> _cache;
|
||||||
|
|
||||||
public TreeMatcher(
|
public TreeMatcher(
|
||||||
IInlineConstraintResolver constraintFactory,
|
MatchProcessorFactory matchProcessorFactory,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
EndpointDataSource dataSource,
|
EndpointDataSource dataSource,
|
||||||
EndpointSelector endpointSelector)
|
EndpointSelector endpointSelector)
|
||||||
{
|
{
|
||||||
if (constraintFactory == null)
|
if (matchProcessorFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(constraintFactory));
|
throw new ArgumentNullException(nameof(matchProcessorFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger == null)
|
if (logger == null)
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
throw new ArgumentNullException(nameof(dataSource));
|
throw new ArgumentNullException(nameof(dataSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
_constraintFactory = constraintFactory;
|
_matchProcessorFactory = matchProcessorFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_endpointSelector = endpointSelector;
|
_endpointSelector = endpointSelector;
|
||||||
_cache = new DataSourceDependantCache<UrlMatchingTree[]>(dataSource, CreateTrees);
|
_cache = new DataSourceDependantCache<UrlMatchingTree[]>(dataSource, CreateTrees);
|
||||||
|
|
@ -79,6 +79,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
foreach (var item in node.Matches)
|
foreach (var item in node.Matches)
|
||||||
{
|
{
|
||||||
var entry = item.Entry;
|
var entry = item.Entry;
|
||||||
|
var tagData = (InboundEntryTagData)entry.Tag;
|
||||||
var matcher = item.TemplateMatcher;
|
var matcher = item.TemplateMatcher;
|
||||||
|
|
||||||
values.Clear();
|
values.Clear();
|
||||||
|
|
@ -89,12 +90,12 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
|
||||||
Log.MatchedTemplate(_logger, httpContext, entry.RouteTemplate);
|
Log.MatchedTemplate(_logger, httpContext, entry.RouteTemplate);
|
||||||
|
|
||||||
if (!MatchConstraints(httpContext, values, entry.Constraints))
|
if (!MatchConstraints(httpContext, values, tagData.MatchProcessors))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectEndpoint(httpContext, feature, (MatcherEndpoint[])entry.Tag);
|
SelectEndpoint(httpContext, feature, tagData.Endpoints);
|
||||||
|
|
||||||
if (feature.Endpoint != null)
|
if (feature.Endpoint != null)
|
||||||
{
|
{
|
||||||
|
|
@ -123,18 +124,14 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
private bool MatchConstraints(
|
private bool MatchConstraints(
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
RouteValueDictionary values,
|
RouteValueDictionary values,
|
||||||
IDictionary<string, IRouteConstraint> constraints)
|
IList<MatchProcessor> matchProcessors)
|
||||||
{
|
{
|
||||||
if (constraints != null)
|
if (matchProcessors != null)
|
||||||
{
|
{
|
||||||
foreach (var kvp in constraints)
|
foreach (var processor in matchProcessors)
|
||||||
{
|
{
|
||||||
var constraint = kvp.Value;
|
if (!processor.ProcessInbound(httpContext, values))
|
||||||
if (!constraint.Match(httpContext, new DummyRouter(), kvp.Key, values, RouteDirection.IncomingRequest))
|
|
||||||
{
|
{
|
||||||
values.TryGetValue(kvp.Key, out var value);
|
|
||||||
|
|
||||||
Log.ConstraintFailed(_logger, value, kvp.Key, kvp.Value);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +220,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
return trees.ToArray();
|
return trees.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private InboundRouteEntry MapInbound(RouteTemplate template, Endpoint[] endpoints, int order)
|
private InboundRouteEntry MapInbound(RouteTemplate template, MatcherEndpoint[] endpoints, int order)
|
||||||
{
|
{
|
||||||
if (template == null)
|
if (template == null)
|
||||||
{
|
{
|
||||||
|
|
@ -235,27 +232,24 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
Precedence = RoutePrecedence.ComputeInbound(template),
|
Precedence = RoutePrecedence.ComputeInbound(template),
|
||||||
RouteTemplate = template,
|
RouteTemplate = template,
|
||||||
Order = order,
|
Order = order,
|
||||||
Tag = endpoints,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var constraintBuilder = new RouteConstraintBuilder(_constraintFactory, template.TemplateText);
|
// Since all endpoints within a group are expected to have same template and same constraints,
|
||||||
foreach (var parameter in template.Parameters)
|
// get the first endpoint which has the processor references
|
||||||
{
|
var endpoint = endpoints[0];
|
||||||
if (parameter.InlineConstraints != null)
|
|
||||||
{
|
|
||||||
if (parameter.IsOptional)
|
|
||||||
{
|
|
||||||
constraintBuilder.SetOptional(parameter.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var constraint in parameter.InlineConstraints)
|
var matchProcessors = new List<MatchProcessor>();
|
||||||
{
|
foreach (var matchProcessorReference in endpoint.MatchProcessorReferences)
|
||||||
constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
|
{
|
||||||
}
|
var matchProcessor = _matchProcessorFactory.Create(matchProcessorReference);
|
||||||
}
|
matchProcessors.Add(matchProcessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.Constraints = constraintBuilder.Build();
|
entry.Tag = new InboundEntryTagData()
|
||||||
|
{
|
||||||
|
Endpoints = endpoints,
|
||||||
|
MatchProcessors = matchProcessors,
|
||||||
|
};
|
||||||
|
|
||||||
entry.Defaults = new RouteValueDictionary();
|
entry.Defaults = new RouteValueDictionary();
|
||||||
foreach (var parameter in entry.RouteTemplate.Parameters)
|
foreach (var parameter in entry.RouteTemplate.Parameters)
|
||||||
|
|
@ -350,5 +344,11 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
_matchedTemplate(logger, template.TemplateText, httpContext.Request.Path, null);
|
_matchedTemplate(logger, template.TemplateText, httpContext.Request.Path, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class InboundEntryTagData
|
||||||
|
{
|
||||||
|
public MatcherEndpoint[] Endpoints { get; set; }
|
||||||
|
public List<MatchProcessor> MatchProcessors { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,18 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
{
|
{
|
||||||
internal class TreeMatcherFactory : MatcherFactory
|
internal class TreeMatcherFactory : MatcherFactory
|
||||||
{
|
{
|
||||||
private readonly IInlineConstraintResolver _constraintFactory;
|
private readonly MatchProcessorFactory _matchProcessorFactory;
|
||||||
private readonly ILogger<TreeMatcher> _logger;
|
private readonly ILogger<TreeMatcher> _logger;
|
||||||
private readonly EndpointSelector _endpointSelector;
|
private readonly EndpointSelector _endpointSelector;
|
||||||
|
|
||||||
public TreeMatcherFactory(
|
public TreeMatcherFactory(
|
||||||
IInlineConstraintResolver constraintFactory,
|
MatchProcessorFactory matchProcessorFactory,
|
||||||
ILogger<TreeMatcher> logger,
|
ILogger<TreeMatcher> logger,
|
||||||
EndpointSelector endpointSelector)
|
EndpointSelector endpointSelector)
|
||||||
{
|
{
|
||||||
if (constraintFactory == null)
|
if (matchProcessorFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(constraintFactory));
|
throw new ArgumentNullException(nameof(matchProcessorFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger == null)
|
if (logger == null)
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
throw new ArgumentNullException(nameof(endpointSelector));
|
throw new ArgumentNullException(nameof(endpointSelector));
|
||||||
}
|
}
|
||||||
|
|
||||||
_constraintFactory = constraintFactory;
|
_matchProcessorFactory = matchProcessorFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_endpointSelector = endpointSelector;
|
_endpointSelector = endpointSelector;
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
throw new ArgumentNullException(nameof(dataSource));
|
throw new ArgumentNullException(nameof(dataSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TreeMatcher(_constraintFactory, _logger, dataSource, _endpointSelector);
|
return new TreeMatcher(_matchProcessorFactory, _logger, dataSource, _endpointSelector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Routing.EndpointFinders;
|
using Microsoft.AspNetCore.Routing.EndpointFinders;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Matchers;
|
using Microsoft.AspNetCore.Routing.Matchers;
|
||||||
|
|
@ -17,19 +16,16 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
internal class RouteValuesBasedEndpointFinder : IEndpointFinder<RouteValuesBasedEndpointFinderContext>
|
internal class RouteValuesBasedEndpointFinder : IEndpointFinder<RouteValuesBasedEndpointFinderContext>
|
||||||
{
|
{
|
||||||
private readonly CompositeEndpointDataSource _endpointDataSource;
|
private readonly CompositeEndpointDataSource _endpointDataSource;
|
||||||
private readonly IInlineConstraintResolver _inlineConstraintResolver;
|
|
||||||
private readonly ObjectPool<UriBuildingContext> _objectPool;
|
private readonly ObjectPool<UriBuildingContext> _objectPool;
|
||||||
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
|
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
|
||||||
private IDictionary<string, LinkGenerationDecisionTree> _namedMatches;
|
private IDictionary<string, LinkGenerationDecisionTree> _namedMatches;
|
||||||
|
|
||||||
public RouteValuesBasedEndpointFinder(
|
public RouteValuesBasedEndpointFinder(
|
||||||
CompositeEndpointDataSource endpointDataSource,
|
CompositeEndpointDataSource endpointDataSource,
|
||||||
ObjectPool<UriBuildingContext> objectPool,
|
ObjectPool<UriBuildingContext> objectPool)
|
||||||
IInlineConstraintResolver inlineConstraintResolver)
|
|
||||||
{
|
{
|
||||||
_endpointDataSource = endpointDataSource;
|
_endpointDataSource = endpointDataSource;
|
||||||
_objectPool = objectPool;
|
_objectPool = objectPool;
|
||||||
_inlineConstraintResolver = inlineConstraintResolver;
|
|
||||||
|
|
||||||
BuildOutboundMatches();
|
BuildOutboundMatches();
|
||||||
}
|
}
|
||||||
|
|
@ -110,28 +106,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Data = endpoint,
|
Data = endpoint,
|
||||||
RouteName = routeNameMetadata?.Name,
|
RouteName = routeNameMetadata?.Name,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: review. These route constriants should be constructed when the endpoint
|
|
||||||
// is built. This way they can be checked for validity on app startup too
|
|
||||||
var constraintBuilder = new RouteConstraintBuilder(
|
|
||||||
_inlineConstraintResolver,
|
|
||||||
endpoint.ParsedTemplate.TemplateText);
|
|
||||||
foreach (var parameter in endpoint.ParsedTemplate.Parameters)
|
|
||||||
{
|
|
||||||
if (parameter.InlineConstraints != null)
|
|
||||||
{
|
|
||||||
if (parameter.IsOptional)
|
|
||||||
{
|
|
||||||
constraintBuilder.SetOptional(parameter.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var constraint in parameter.InlineConstraints)
|
|
||||||
{
|
|
||||||
constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.Constraints = constraintBuilder.Build();
|
|
||||||
entry.Defaults = endpoint.Defaults;
|
entry.Defaults = endpoint.Defaults;
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
@ -146,21 +120,5 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used only to hook up link generation, and it doesn't need to do anything.
|
|
||||||
private class NullRouter : IRouter
|
|
||||||
{
|
|
||||||
public static readonly NullRouter Instance = new NullRouter();
|
|
||||||
|
|
||||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task RouteAsync(RouteContext context)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,74 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
||||||
Assert.Equal(expectedContent, actualContent);
|
Assert.Equal(expectedContent, actualContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchesEndpoint_WithSuccessfulConstraintMatch()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContent = "WithConstraints";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.GetAsync("/withconstraints/555_001");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
var actualContent = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expectedContent, actualContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotMatchEndpoint_IfConstraintMatchFails()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await _client.GetAsync("/withconstraints/555");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchesEndpoint_WithSuccessful_OptionalConstraintMatch()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContent = "withoptionalconstraints";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.GetAsync("/withoptionalconstraints/555_001");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
var actualContent = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expectedContent, actualContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchesEndpoint_WithSuccessful_OptionalConstraintMatch_NoValueForParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContent = "withoptionalconstraints";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.GetAsync("/withoptionalconstraints");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
var actualContent = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expectedContent, actualContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotMatchEndpoint_IfOptionalConstraintMatchFails()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await _client.GetAsync("/withoptionalconstraints/555");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_testServer.Dispose();
|
_testServer.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ using Microsoft.AspNetCore.Routing.EndpointFinders;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Matchers;
|
using Microsoft.AspNetCore.Routing.Matchers;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -788,6 +790,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
template,
|
template,
|
||||||
defaults,
|
defaults,
|
||||||
new RouteValueDictionary(),
|
new RouteValueDictionary(),
|
||||||
|
new List<MatchProcessorReference>(),
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
null);
|
null);
|
||||||
|
|
@ -796,8 +799,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private ILinkGenerator CreateLinkGenerator()
|
private ILinkGenerator CreateLinkGenerator()
|
||||||
{
|
{
|
||||||
return new DefaultLinkGenerator(
|
return new DefaultLinkGenerator(
|
||||||
|
new DefaultMatchProcessorFactory(
|
||||||
|
Options.Create(new RouteOptions()),
|
||||||
|
NullLogger<DefaultMatchProcessorFactory>.Instance,
|
||||||
|
Mock.Of<IServiceProvider>()),
|
||||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||||
Mock.Of<ILogger<DefaultLinkGenerator>>());
|
NullLogger<DefaultLinkGenerator>.Instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Matchers;
|
|
||||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Matchers;
|
using Microsoft.AspNetCore.Routing.Matchers;
|
||||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||||
|
|
@ -9,9 +12,6 @@ using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.EndpointConstraints
|
namespace Microsoft.AspNetCore.Routing.EndpointConstraints
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
// 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.Globalization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
public class DefaultMatchProcessorFactoryTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Create_ThrowsException_IfNoConstraintOrMatchProcessor_FoundInMap()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = GetMatchProcessorFactory();
|
||||||
|
var matchProcessorReference = new MatchProcessorReference("id", @"notpresent(\d+)");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => factory.Create(matchProcessorReference));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_CreatesMatchProcessor_FromConstraintText_AndRouteConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = GetMatchProcessorFactory();
|
||||||
|
var matchProcessorReference = new MatchProcessorReference("id", "int");
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var processor = factory.Create(matchProcessorReference);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
Assert.NotNull(processor);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
var isMatch = processor.ProcessInbound(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteValueDictionary(new { id = 10 }));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.True(isMatch);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
isMatch = processor.ProcessInbound(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteValueDictionary(new { id = "foo" }));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.False(isMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_CreatesMatchProcessor_FromConstraintText_AndCustomMatchProcessor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new RouteOptions();
|
||||||
|
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor));
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddTransient<EndsWithStringMatchProcessor>();
|
||||||
|
var factory = GetMatchProcessorFactory(options, services);
|
||||||
|
var matchProcessorReference = new MatchProcessorReference("id", "endsWith(_001)");
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var processor = factory.Create(matchProcessorReference);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
Assert.NotNull(processor);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
var isMatch = processor.ProcessInbound(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteValueDictionary(new { id = "555_001" }));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.True(isMatch);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
isMatch = processor.ProcessInbound(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteValueDictionary(new { id = "444" }));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.False(isMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ReturnsMatchProcessor_IfAvailable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = GetMatchProcessorFactory();
|
||||||
|
var matchProcessorReference = new MatchProcessorReference("id", Mock.Of<MatchProcessor>());
|
||||||
|
var expected = matchProcessorReference.MatchProcessor;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var processor = factory.Create(matchProcessorReference);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(expected, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ReturnsMatchProcessor_WithSuppliedRouteConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = GetMatchProcessorFactory();
|
||||||
|
var constraint = TestRouteConstraint.Create();
|
||||||
|
var matchProcessorReference = new MatchProcessorReference("id", constraint);
|
||||||
|
var processor = factory.Create(matchProcessorReference);
|
||||||
|
var expectedHttpContext = new DefaultHttpContext();
|
||||||
|
var expectedValues = new RouteValueDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
processor.ProcessInbound(expectedHttpContext, expectedValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(expectedHttpContext, constraint.HttpContext);
|
||||||
|
Assert.Same(expectedValues, constraint.Values);
|
||||||
|
Assert.Equal("id", constraint.RouteKey);
|
||||||
|
Assert.Equal(RouteDirection.IncomingRequest, constraint.RouteDirection);
|
||||||
|
Assert.Same(NullRouter.Instance, constraint.Route);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultMatchProcessorFactory GetMatchProcessorFactory(
|
||||||
|
RouteOptions options = null,
|
||||||
|
ServiceCollection services = null)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
options = new RouteOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
services = new ServiceCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultMatchProcessorFactory(
|
||||||
|
Options.Create(options),
|
||||||
|
NullLogger<DefaultMatchProcessorFactory>.Instance,
|
||||||
|
services.BuildServiceProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRouteConstraint : IRouteConstraint
|
||||||
|
{
|
||||||
|
private TestRouteConstraint() { }
|
||||||
|
|
||||||
|
public HttpContext HttpContext { get; private set; }
|
||||||
|
public IRouter Route { get; private set; }
|
||||||
|
public string RouteKey { get; private set; }
|
||||||
|
public RouteValueDictionary Values { get; private set; }
|
||||||
|
public RouteDirection RouteDirection { get; private set; }
|
||||||
|
|
||||||
|
public static TestRouteConstraint Create()
|
||||||
|
{
|
||||||
|
return new TestRouteConstraint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Match(
|
||||||
|
HttpContext httpContext,
|
||||||
|
IRouter route,
|
||||||
|
string routeKey,
|
||||||
|
RouteValueDictionary values,
|
||||||
|
RouteDirection routeDirection)
|
||||||
|
{
|
||||||
|
HttpContext = httpContext;
|
||||||
|
Route = route;
|
||||||
|
RouteKey = routeKey;
|
||||||
|
Values = values;
|
||||||
|
RouteDirection = routeDirection;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EndsWithStringMatchProcessor : MatchProcessorBase
|
||||||
|
{
|
||||||
|
public override bool Process(object value)
|
||||||
|
{
|
||||||
|
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
|
return valueString.EndsWith(ConstraintArgument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
|
@ -43,6 +44,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
template,
|
template,
|
||||||
defaults,
|
defaults,
|
||||||
new RouteValueDictionary(),
|
new RouteValueDictionary(),
|
||||||
|
new List<MatchProcessorReference>(),
|
||||||
order ?? 0,
|
order ?? 0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"endpoint: " + template);
|
"endpoint: " + template);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.EndpointConstraints;
|
using Microsoft.AspNetCore.Routing.EndpointConstraints;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
|
@ -16,13 +18,23 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
private MatcherEndpoint CreateEndpoint(string template, int order, object defaultValues = null, EndpointMetadataCollection metadata = null)
|
private MatcherEndpoint CreateEndpoint(string template, int order, object defaultValues = null, EndpointMetadataCollection metadata = null)
|
||||||
{
|
{
|
||||||
var defaults = defaultValues == null ? new RouteValueDictionary() : new RouteValueDictionary(defaultValues);
|
var defaults = defaultValues == null ? new RouteValueDictionary() : new RouteValueDictionary(defaultValues);
|
||||||
return new MatcherEndpoint((next) => null, template, defaults, new RouteValueDictionary(), order, metadata ?? EndpointMetadataCollection.Empty, template);
|
return new MatcherEndpoint(
|
||||||
|
(next) => null,
|
||||||
|
template, defaults,
|
||||||
|
new RouteValueDictionary(),
|
||||||
|
new List<MatchProcessorReference>(),
|
||||||
|
order,
|
||||||
|
metadata ?? EndpointMetadataCollection.Empty,
|
||||||
|
template);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TreeMatcher CreateTreeMatcher(EndpointDataSource endpointDataSource)
|
private TreeMatcher CreateTreeMatcher(EndpointDataSource endpointDataSource)
|
||||||
{
|
{
|
||||||
var compositeDataSource = new CompositeEndpointDataSource(new[] { endpointDataSource });
|
var compositeDataSource = new CompositeEndpointDataSource(new[] { endpointDataSource });
|
||||||
var defaultInlineConstraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()));
|
var defaultInlineConstraintResolver = new DefaultMatchProcessorFactory(
|
||||||
|
Options.Create(new RouteOptions()),
|
||||||
|
NullLogger<DefaultMatchProcessorFactory>.Instance,
|
||||||
|
Mock.Of<IServiceProvider>());
|
||||||
var endpointSelector = new EndpointSelector(
|
var endpointSelector = new EndpointSelector(
|
||||||
compositeDataSource,
|
compositeDataSource,
|
||||||
new EndpointConstraintCache(compositeDataSource, new IEndpointConstraintProvider[] { new DefaultEndpointConstraintProvider() }),
|
new EndpointConstraintCache(compositeDataSource, new IEndpointConstraintProvider[] { new DefaultEndpointConstraintProvider() }),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue