Merge branch 'merge/release/2.2-to-master'
# Conflicts: # build/dependencies.props # korebuild-lock.txt
This commit is contained in:
commit
12e662a586
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
namespace Benchmarks
|
namespace Benchmarks
|
||||||
{
|
{
|
||||||
|
|
@ -18,9 +19,7 @@ namespace Benchmarks
|
||||||
{
|
{
|
||||||
services.AddRouting();
|
services.AddRouting();
|
||||||
|
|
||||||
services.Configure<EndpointOptions>(options =>
|
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
||||||
{
|
|
||||||
options.DataSources.Add(new DefaultEndpointDataSource(new[]
|
|
||||||
{
|
{
|
||||||
new MatcherEndpoint(
|
new MatcherEndpoint(
|
||||||
invoker: (next) => (httpContext) =>
|
invoker: (next) => (httpContext) =>
|
||||||
|
|
@ -33,12 +32,12 @@ namespace Benchmarks
|
||||||
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
||||||
},
|
},
|
||||||
routePattern: RoutePatternFactory.Parse("/plaintext"),
|
routePattern: RoutePatternFactory.Parse("/plaintext"),
|
||||||
requiredValues: new RouteValueDictionary(),
|
|
||||||
order: 0,
|
order: 0,
|
||||||
metadata: EndpointMetadataCollection.Empty,
|
metadata: EndpointMetadataCollection.Empty,
|
||||||
displayName: "Plaintext"),
|
displayName: "Plaintext"),
|
||||||
}));
|
});
|
||||||
});
|
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
"PlaintextDispatcher": {
|
"PlaintextDispatcher": {
|
||||||
"Path": "/plaintext"
|
"Path": "/plaintext"
|
||||||
},
|
},
|
||||||
"PlaintextGlobalRouting": {
|
"PlaintextEndpointRouting": {
|
||||||
"Path": "/plaintext"
|
"Path": "/plaintext"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Metadata;
|
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
|
@ -44,7 +43,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
new EndpointMetadataCollection(metadata),
|
new EndpointMetadataCollection(metadata),
|
||||||
template);
|
template);
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace RoutingSample.Web
|
namespace RoutingSample.Web
|
||||||
{
|
{
|
||||||
internal class EndsWithStringMatchProcessor : MatchProcessorBase
|
internal class EndsWithStringMatchProcessor : MatchProcessor
|
||||||
{
|
{
|
||||||
private readonly ILogger<EndsWithStringMatchProcessor> _logger;
|
private readonly ILogger<EndsWithStringMatchProcessor> _logger;
|
||||||
|
|
||||||
|
|
@ -17,15 +19,34 @@ namespace RoutingSample.Web
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Process(object value)
|
public string ParameterName { get; private set; }
|
||||||
|
|
||||||
|
public string ConstraintArgument { get; private set; }
|
||||||
|
|
||||||
|
public override void Initialize(string parameterName, string constraintArgument)
|
||||||
{
|
{
|
||||||
if (value == null)
|
ParameterName = parameterName;
|
||||||
|
ConstraintArgument = constraintArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var endsWith = valueString.EndsWith(ConstraintArgument, StringComparison.OrdinalIgnoreCase);
|
var endsWith = valueString.EndsWith(ConstraintArgument, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (!endsWith)
|
if (!endsWith)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
namespace RoutingSample.Web
|
namespace RoutingSample.Web
|
||||||
{
|
{
|
||||||
|
|
@ -26,10 +27,8 @@ namespace RoutingSample.Web
|
||||||
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor));
|
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor));
|
||||||
});
|
});
|
||||||
|
|
||||||
services.Configure<EndpointOptions>(options =>
|
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
||||||
{
|
{
|
||||||
options.DataSources.Add(new DefaultEndpointDataSource(new[]
|
|
||||||
{
|
|
||||||
new MatcherEndpoint((next) => (httpContext) =>
|
new MatcherEndpoint((next) => (httpContext) =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
|
@ -40,7 +39,6 @@ namespace RoutingSample.Web
|
||||||
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
|
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
|
||||||
},
|
},
|
||||||
RoutePatternFactory.Parse("/"),
|
RoutePatternFactory.Parse("/"),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"Home"),
|
"Home"),
|
||||||
|
|
@ -54,7 +52,6 @@ namespace RoutingSample.Web
|
||||||
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
||||||
},
|
},
|
||||||
RoutePatternFactory.Parse("/plaintext"),
|
RoutePatternFactory.Parse("/plaintext"),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"Plaintext"),
|
"Plaintext"),
|
||||||
|
|
@ -66,7 +63,6 @@ namespace RoutingSample.Web
|
||||||
return response.WriteAsync("WithConstraints");
|
return response.WriteAsync("WithConstraints");
|
||||||
},
|
},
|
||||||
RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"),
|
RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"withconstraints"),
|
"withconstraints"),
|
||||||
|
|
@ -78,12 +74,12 @@ namespace RoutingSample.Web
|
||||||
return response.WriteAsync("withoptionalconstraints");
|
return response.WriteAsync("withoptionalconstraints");
|
||||||
},
|
},
|
||||||
RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"),
|
RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"withoptionalconstraints"),
|
"withoptionalconstraints"),
|
||||||
}));
|
});
|
||||||
});
|
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a contract to generate a URL from a template.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class LinkGenerationTemplate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string MakeUrl(object values)
|
||||||
|
{
|
||||||
|
return MakeUrl(values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values and link options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public abstract string MakeUrl(object values, LinkOptions options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,463 @@
|
||||||
// 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 Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a contract to generate URLs to endpoints.
|
||||||
|
/// </summary>
|
||||||
public abstract class LinkGenerator
|
public abstract class LinkGenerator
|
||||||
{
|
{
|
||||||
public abstract bool TryGetLink(LinkGeneratorContext context, out string link);
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName: null, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract string GetLink(LinkGeneratorContext context);
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values and link options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName: null, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLink(object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName: null, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values and link options.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLink(object values, LinkOptions options, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName: null, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(HttpContext httpContext, object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext, routeName: null, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLink(HttpContext httpContext, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext, routeName: null, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values and link options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(HttpContext httpContext, object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext, routeName: null, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route values and link options.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLink(HttpContext httpContext, object values, LinkOptions options, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext, routeName: null, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name and route values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(string routeName, object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name and route values.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLink(string routeName, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name and route values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(string routeName, object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name, route values and link options.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLink(string routeName, object values, LinkOptions options, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name and route values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(HttpContext httpContext, string routeName, object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext, routeName, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name and route values.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLink(HttpContext httpContext, string routeName, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext, routeName, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name, route values and link options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLink(HttpContext httpContext, string routeName, object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
if (TryGetLink(httpContext, routeName, values, options, out var link))
|
||||||
|
{
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified route name, route values and link options.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public abstract bool TryGetLink(
|
||||||
|
HttpContext httpContext,
|
||||||
|
string routeName,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information and route values.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLinkByAddress<TAddress>(TAddress address, object values)
|
||||||
|
{
|
||||||
|
return GetLinkByAddress(httpContext: null, address, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information and route values.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLinkByAddress<TAddress>(TAddress address, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLinkByAddress(address, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLinkByAddress<TAddress>(TAddress address, object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLinkByAddress(httpContext: null, address, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLinkByAddress<TAddress>(
|
||||||
|
TAddress address,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
|
{
|
||||||
|
return TryGetLinkByAddress(httpContext: null, address, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLinkByAddress<TAddress>(HttpContext httpContext, TAddress address, object values)
|
||||||
|
{
|
||||||
|
return GetLinkByAddress(httpContext, address, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information and route values.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool TryGetLinkByAddress<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address,
|
||||||
|
object values,
|
||||||
|
out string link)
|
||||||
|
{
|
||||||
|
return TryGetLinkByAddress(httpContext, address, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <returns>The generated URL.</returns>
|
||||||
|
public string GetLinkByAddress<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address,
|
||||||
|
object values,
|
||||||
|
LinkOptions options)
|
||||||
|
{
|
||||||
|
if (TryGetLinkByAddress(httpContext, address, values, options, out var link))
|
||||||
|
{
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
|
||||||
|
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// A return value indicates whether the operation succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">An object that contains route values.</param>
|
||||||
|
/// <param name="options">The <see cref="LinkOptions"/>.</param>
|
||||||
|
/// <param name="link">The generated URL.</param>
|
||||||
|
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
|
||||||
|
public abstract bool TryGetLinkByAddress<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route values.
|
||||||
|
/// This template object holds information of the endpoint(s) that were found and which can later be used to
|
||||||
|
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">
|
||||||
|
/// An object that contains route values. These values are used to lookup endpoint(s).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
|
||||||
|
/// <c>null</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public LinkGenerationTemplate GetTemplate(object values)
|
||||||
|
{
|
||||||
|
return GetTemplate(httpContext: null, routeName: null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route name and route values.
|
||||||
|
/// This template object holds information of the endpoint(s) that were found and which can later be used to
|
||||||
|
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">
|
||||||
|
/// An object that contains route values. These values are used to lookup for endpoint(s).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
|
||||||
|
/// <c>null</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public LinkGenerationTemplate GetTemplate(string routeName, object values)
|
||||||
|
{
|
||||||
|
return GetTemplate(httpContext: null, routeName, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route values.
|
||||||
|
/// This template object holds information of the endpoint(s) that were found and which can later be used to
|
||||||
|
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="values">
|
||||||
|
/// An object that contains route values. These values are used to lookup for endpoint(s).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
|
||||||
|
/// <c>null</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public LinkGenerationTemplate GetTemplate(HttpContext httpContext, object values)
|
||||||
|
{
|
||||||
|
return GetTemplate(httpContext, routeName: null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route name and route values.
|
||||||
|
/// This template object holds information of the endpoint(s) that were found and which can later be used to
|
||||||
|
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <param name="routeName">The name of the route to generate the URL to.</param>
|
||||||
|
/// <param name="values">
|
||||||
|
/// An object that contains route values. These values are used to lookup for endpoint(s).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
|
||||||
|
/// <c>null</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public abstract LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified lookup information.
|
||||||
|
/// This template object holds information of the endpoint(s) that were found and which can later be used to
|
||||||
|
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
|
||||||
|
/// The lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for creating a template.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
|
||||||
|
/// <c>null</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public LinkGenerationTemplate GetTemplateByAddress<TAddress>(TAddress address)
|
||||||
|
{
|
||||||
|
return GetTemplateByAddress(httpContext: null, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified lookup information.
|
||||||
|
/// This template object holds information of the endpoint(s) that were found and which can later be used to
|
||||||
|
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
|
||||||
|
/// The lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
|
/// <param name="address">The information used to look up endpoints for creating a template.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
|
||||||
|
/// <c>null</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public abstract LinkGenerationTemplate GetTemplateByAddress<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,10 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public class LinkGeneratorContext
|
public class LinkOptions
|
||||||
{
|
{
|
||||||
public HttpContext HttpContext { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<Endpoint> Endpoints { get; set; }
|
|
||||||
|
|
||||||
public RouteValueDictionary ExplicitValues { get; set; }
|
|
||||||
|
|
||||||
public RouteValueDictionary AmbientValues { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
|
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
|
||||||
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
|
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
|
||||||
|
|
@ -8,11 +8,13 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Metadata;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an <see cref="EndpointDataSource"/> whose values come from a collection of <see cref="EndpointDataSource"/> instances.
|
||||||
|
/// </summary>
|
||||||
[DebuggerDisplay("{DebuggerDisplayString,nq}")]
|
[DebuggerDisplay("{DebuggerDisplayString,nq}")]
|
||||||
public sealed class CompositeEndpointDataSource : EndpointDataSource
|
public sealed class CompositeEndpointDataSource : EndpointDataSource
|
||||||
{
|
{
|
||||||
|
|
@ -34,12 +36,20 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
||||||
|
/// instances.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The <see cref="IChangeToken"/>.</returns>
|
||||||
public override IChangeToken GetChangeToken()
|
public override IChangeToken GetChangeToken()
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
EnsureInitialized();
|
||||||
return _consumerChangeToken;
|
return _consumerChangeToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a read-only collection of <see cref="Endpoint"/> instances.
|
||||||
|
/// </summary>
|
||||||
public override IReadOnlyList<Endpoint> Endpoints
|
public override IReadOnlyList<Endpoint> Endpoints
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -127,15 +137,18 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var template = matcherEndpoint.RoutePattern.RawText;
|
var template = matcherEndpoint.RoutePattern.RawText;
|
||||||
template = string.IsNullOrEmpty(template) ? "\"\"" : template;
|
template = string.IsNullOrEmpty(template) ? "\"\"" : template;
|
||||||
sb.Append(template);
|
sb.Append(template);
|
||||||
sb.Append(", Required Values: new { ");
|
|
||||||
sb.Append(string.Join(", ", FormatValues(matcherEndpoint.RequiredValues)));
|
|
||||||
sb.Append(" }");
|
|
||||||
sb.Append(", Defaults: new { ");
|
sb.Append(", Defaults: new { ");
|
||||||
sb.Append(string.Join(", ", FormatValues(matcherEndpoint.RoutePattern.Defaults)));
|
sb.Append(string.Join(", ", FormatValues(matcherEndpoint.RoutePattern.Defaults)));
|
||||||
sb.Append(" }");
|
sb.Append(" }");
|
||||||
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||||
sb.Append(", Route Name: ");
|
sb.Append(", Route Name: ");
|
||||||
sb.Append(routeNameMetadata?.Name);
|
sb.Append(routeValuesAddressMetadata?.Name);
|
||||||
|
if (routeValuesAddressMetadata?.RequiredValues != null)
|
||||||
|
{
|
||||||
|
sb.Append(", Required Values: new { ");
|
||||||
|
sb.Append(string.Join(", ", FormatValues(routeValuesAddressMetadata.RequiredValues)));
|
||||||
|
sb.Append(" }");
|
||||||
|
}
|
||||||
sb.Append(", Order: ");
|
sb.Append(", Order: ");
|
||||||
sb.Append(matcherEndpoint.Order);
|
sb.Append(matcherEndpoint.Order);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,26 @@ using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a collection of <see cref="Endpoint"/> instances.
|
||||||
|
/// </summary>
|
||||||
public sealed class DefaultEndpointDataSource : EndpointDataSource
|
public sealed class DefaultEndpointDataSource : EndpointDataSource
|
||||||
{
|
{
|
||||||
private readonly List<Endpoint> _endpoints;
|
private readonly List<Endpoint> _endpoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
|
||||||
public DefaultEndpointDataSource(params Endpoint[] endpoints)
|
public DefaultEndpointDataSource(params Endpoint[] endpoints)
|
||||||
: this((IEnumerable<Endpoint>) endpoints)
|
: this((IEnumerable<Endpoint>) endpoints)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
|
||||||
public DefaultEndpointDataSource(IEnumerable<Endpoint> endpoints)
|
public DefaultEndpointDataSource(IEnumerable<Endpoint> endpoints)
|
||||||
{
|
{
|
||||||
if (endpoints == null)
|
if (endpoints == null)
|
||||||
|
|
@ -28,8 +39,16 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
_endpoints.AddRange(endpoints);
|
_endpoints.AddRange(endpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
||||||
|
/// instances.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The <see cref="IChangeToken"/>.</returns>
|
||||||
public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
|
public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a read-only collection of <see cref="Endpoint"/> instances.
|
||||||
|
/// </summary>
|
||||||
public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
|
public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
internal class DefaultLinkGenerationTemplate : LinkGenerationTemplate
|
||||||
|
{
|
||||||
|
public DefaultLinkGenerationTemplate(
|
||||||
|
DefaultLinkGenerator linkGenerator,
|
||||||
|
IEnumerable<MatcherEndpoint> endpoints,
|
||||||
|
HttpContext httpContext,
|
||||||
|
RouteValueDictionary explicitValues,
|
||||||
|
RouteValueDictionary ambientValues)
|
||||||
|
{
|
||||||
|
LinkGenerator = linkGenerator;
|
||||||
|
Endpoints = endpoints;
|
||||||
|
HttpContext = httpContext;
|
||||||
|
EarlierExplicitValues = explicitValues;
|
||||||
|
AmbientValues = ambientValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DefaultLinkGenerator LinkGenerator { get; }
|
||||||
|
|
||||||
|
internal IEnumerable<MatcherEndpoint> Endpoints { get; }
|
||||||
|
|
||||||
|
internal HttpContext HttpContext { get; }
|
||||||
|
|
||||||
|
internal RouteValueDictionary EarlierExplicitValues { get; }
|
||||||
|
|
||||||
|
internal RouteValueDictionary AmbientValues { get; }
|
||||||
|
|
||||||
|
public override string MakeUrl(object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
var currentValues = new RouteValueDictionary(values);
|
||||||
|
var mergedValuesDictionary = new RouteValueDictionary(EarlierExplicitValues);
|
||||||
|
|
||||||
|
foreach (var kvp in currentValues)
|
||||||
|
{
|
||||||
|
mergedValuesDictionary[kvp.Key] = kvp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var endpoint in Endpoints)
|
||||||
|
{
|
||||||
|
var link = LinkGenerator.MakeLink(
|
||||||
|
HttpContext,
|
||||||
|
endpoint,
|
||||||
|
AmbientValues,
|
||||||
|
mergedValuesDictionary,
|
||||||
|
options);
|
||||||
|
if (link != null)
|
||||||
|
{
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,14 @@
|
||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -19,60 +21,163 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
||||||
private readonly MatchProcessorFactory _matchProcessorFactory;
|
private readonly MatchProcessorFactory _matchProcessorFactory;
|
||||||
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
||||||
private readonly RouteOptions _options;
|
|
||||||
private readonly ILogger<DefaultLinkGenerator> _logger;
|
private readonly ILogger<DefaultLinkGenerator> _logger;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly RouteOptions _options;
|
||||||
|
|
||||||
public DefaultLinkGenerator(
|
public DefaultLinkGenerator(
|
||||||
MatchProcessorFactory matchProcessorFactory,
|
MatchProcessorFactory matchProcessorFactory,
|
||||||
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
||||||
IOptions<RouteOptions> routeOptions,
|
IOptions<RouteOptions> routeOptions,
|
||||||
ILogger<DefaultLinkGenerator> logger)
|
ILogger<DefaultLinkGenerator> logger,
|
||||||
|
IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_matchProcessorFactory = matchProcessorFactory;
|
_matchProcessorFactory = matchProcessorFactory;
|
||||||
_uriBuildingContextPool = uriBuildingContextPool;
|
_uriBuildingContextPool = uriBuildingContextPool;
|
||||||
_options = routeOptions.Value;
|
_options = routeOptions.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetLink(LinkGeneratorContext context)
|
public override bool TryGetLink(
|
||||||
|
HttpContext httpContext,
|
||||||
|
string routeName,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
{
|
{
|
||||||
if (context == null)
|
return TryGetLinkByRouteValues(
|
||||||
{
|
httpContext,
|
||||||
throw new ArgumentNullException(nameof(context));
|
routeName,
|
||||||
}
|
values,
|
||||||
|
options,
|
||||||
if (TryGetLink(context, out var link))
|
out link);
|
||||||
{
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool TryGetLink(LinkGeneratorContext context, out string link)
|
public override bool TryGetLinkByAddress<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
{
|
{
|
||||||
if (context == null)
|
return TryGetLinkByAddressInternal(
|
||||||
|
httpContext,
|
||||||
|
address,
|
||||||
|
explicitValues: values,
|
||||||
|
ambientValues: GetAmbientValues(httpContext),
|
||||||
|
options,
|
||||||
|
out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values)
|
||||||
|
{
|
||||||
|
var ambientValues = GetAmbientValues(httpContext);
|
||||||
|
var explicitValues = new RouteValueDictionary(values);
|
||||||
|
|
||||||
|
return GetTemplateInternal(
|
||||||
|
httpContext,
|
||||||
|
new RouteValuesAddress
|
||||||
|
{
|
||||||
|
RouteName = routeName,
|
||||||
|
ExplicitValues = explicitValues,
|
||||||
|
AmbientValues = ambientValues
|
||||||
|
},
|
||||||
|
ambientValues,
|
||||||
|
explicitValues,
|
||||||
|
values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override LinkGenerationTemplate GetTemplateByAddress<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address)
|
||||||
|
{
|
||||||
|
return GetTemplateInternal(httpContext, address, values: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string MakeLink(
|
||||||
|
HttpContext httpContext,
|
||||||
|
MatcherEndpoint endpoint,
|
||||||
|
RouteValueDictionary ambientValues,
|
||||||
|
RouteValueDictionary explicitValues,
|
||||||
|
LinkOptions options)
|
||||||
|
{
|
||||||
|
var templateBinder = new TemplateBinder(
|
||||||
|
UrlEncoder.Default,
|
||||||
|
_uriBuildingContextPool,
|
||||||
|
new RouteTemplate(endpoint.RoutePattern),
|
||||||
|
new RouteValueDictionary(endpoint.RoutePattern.Defaults));
|
||||||
|
|
||||||
|
var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||||
|
var templateValuesResult = templateBinder.GetValues(
|
||||||
|
ambientValues: ambientValues,
|
||||||
|
explicitValues: explicitValues,
|
||||||
|
requiredKeys: routeValuesAddressMetadata?.RequiredValues.Keys);
|
||||||
|
if (templateValuesResult == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(context));
|
// We're missing one of the required values for this route.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = templateBinder.BindValues(templateValuesResult.AcceptedValues);
|
||||||
|
return Normalize(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetLinkByRouteValues(
|
||||||
|
HttpContext httpContext,
|
||||||
|
string routeName,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
|
{
|
||||||
|
var ambientValues = GetAmbientValues(httpContext);
|
||||||
|
|
||||||
|
var address = new RouteValuesAddress
|
||||||
|
{
|
||||||
|
RouteName = routeName,
|
||||||
|
ExplicitValues = new RouteValueDictionary(values),
|
||||||
|
AmbientValues = ambientValues
|
||||||
|
};
|
||||||
|
|
||||||
|
return TryGetLinkByAddressInternal(
|
||||||
|
httpContext,
|
||||||
|
address,
|
||||||
|
explicitValues: values,
|
||||||
|
ambientValues: ambientValues,
|
||||||
|
options,
|
||||||
|
out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetLinkByAddressInternal<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address,
|
||||||
|
object explicitValues,
|
||||||
|
RouteValueDictionary ambientValues,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
|
{
|
||||||
link = null;
|
link = null;
|
||||||
|
|
||||||
if (context.Endpoints == null)
|
var endpoints = FindEndpoints(address);
|
||||||
|
if (endpoints == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var matcherEndpoints = context.Endpoints.OfType<MatcherEndpoint>();
|
foreach (var endpoint in endpoints)
|
||||||
if (!matcherEndpoints.Any())
|
|
||||||
{
|
{
|
||||||
//todo:log here
|
link = MakeLink(
|
||||||
return false;
|
httpContext,
|
||||||
}
|
endpoint,
|
||||||
|
ambientValues,
|
||||||
|
new RouteValueDictionary(explicitValues),
|
||||||
|
options);
|
||||||
|
|
||||||
foreach (var endpoint in matcherEndpoints)
|
|
||||||
{
|
|
||||||
link = GetLink(endpoint, context);
|
|
||||||
if (link != null)
|
if (link != null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -82,32 +187,47 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetLink(MatcherEndpoint endpoint, LinkGeneratorContext context)
|
private LinkGenerationTemplate GetTemplateInternal<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address,
|
||||||
|
object values)
|
||||||
{
|
{
|
||||||
var templateBinder = new TemplateBinder(
|
var endpoints = FindEndpoints(address);
|
||||||
UrlEncoder.Default,
|
if (endpoints == null)
|
||||||
_uriBuildingContextPool,
|
|
||||||
new RouteTemplate(endpoint.RoutePattern),
|
|
||||||
new RouteValueDictionary(endpoint.RoutePattern.Defaults));
|
|
||||||
|
|
||||||
var templateValuesResult = templateBinder.GetValues(
|
|
||||||
ambientValues: context.AmbientValues,
|
|
||||||
explicitValues: context.ExplicitValues,
|
|
||||||
endpoint.RequiredValues.Keys);
|
|
||||||
|
|
||||||
if (templateValuesResult == null)
|
|
||||||
{
|
|
||||||
// We're missing one of the required values for this route.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MatchesConstraints(context.HttpContext, endpoint, templateValuesResult.CombinedValues))
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = templateBinder.BindValues(templateValuesResult.AcceptedValues);
|
var ambientValues = GetAmbientValues(httpContext);
|
||||||
return Normalize(context, url);
|
var explicitValues = new RouteValueDictionary(values);
|
||||||
|
|
||||||
|
return new DefaultLinkGenerationTemplate(
|
||||||
|
this,
|
||||||
|
endpoints,
|
||||||
|
httpContext,
|
||||||
|
explicitValues,
|
||||||
|
ambientValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkGenerationTemplate GetTemplateInternal<TAddress>(
|
||||||
|
HttpContext httpContext,
|
||||||
|
TAddress address,
|
||||||
|
RouteValueDictionary ambientValues,
|
||||||
|
RouteValueDictionary explicitValues,
|
||||||
|
object values)
|
||||||
|
{
|
||||||
|
var endpoints = FindEndpoints(address);
|
||||||
|
if (endpoints == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultLinkGenerationTemplate(
|
||||||
|
this,
|
||||||
|
endpoints,
|
||||||
|
httpContext,
|
||||||
|
explicitValues,
|
||||||
|
ambientValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MatchesConstraints(
|
private bool MatchesConstraints(
|
||||||
|
|
@ -138,13 +258,11 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Normalize(LinkGeneratorContext context, string url)
|
private string Normalize(string url, LinkOptions options)
|
||||||
{
|
{
|
||||||
var lowercaseUrls = context.LowercaseUrls.HasValue ? context.LowercaseUrls.Value : _options.LowercaseUrls;
|
var lowercaseUrls = options?.LowercaseUrls ?? _options.LowercaseUrls;
|
||||||
var lowercaseQueryStrings = context.LowercaseQueryStrings.HasValue ?
|
var lowercaseQueryStrings = options?.LowercaseQueryStrings ?? _options.LowercaseQueryStrings;
|
||||||
context.LowercaseQueryStrings.Value : _options.LowercaseQueryStrings;
|
var appendTrailingSlash = options?.AppendTrailingSlash ?? _options.AppendTrailingSlash;
|
||||||
var appendTrailingSlash = context.AppendTrailingSlash.HasValue ?
|
|
||||||
context.AppendTrailingSlash.Value : _options.AppendTrailingSlash;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(url) && (lowercaseUrls || appendTrailingSlash))
|
if (!string.IsNullOrEmpty(url) && (lowercaseUrls || appendTrailingSlash))
|
||||||
{
|
{
|
||||||
|
|
@ -179,5 +297,36 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
if (httpContext != null)
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IEndpointFeature>();
|
||||||
|
if (feature != null)
|
||||||
|
{
|
||||||
|
return feature.Values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new RouteValueDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<MatcherEndpoint> FindEndpoints<TAddress>(TAddress address)
|
||||||
|
{
|
||||||
|
var finder = _serviceProvider.GetRequiredService<IEndpointFinder<TAddress>>();
|
||||||
|
var endpoints = finder.FindEndpoints(address);
|
||||||
|
if (endpoints == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matcherEndpoints = endpoints.OfType<MatcherEndpoint>();
|
||||||
|
if (!matcherEndpoints.Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matcherEndpoints;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddTransient<DfaMatcherBuilder>();
|
services.TryAddTransient<DfaMatcherBuilder>();
|
||||||
|
|
||||||
// Link generation related services
|
// Link generation related services
|
||||||
services.TryAddSingleton<IEndpointFinder<string>, NameBasedEndpointFinder>();
|
|
||||||
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
||||||
services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
|
services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,21 @@ using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a collection of <see cref="Endpoint"/> instances.
|
||||||
|
/// </summary>
|
||||||
public abstract class EndpointDataSource
|
public abstract class EndpointDataSource
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
||||||
|
/// instances.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The <see cref="IChangeToken"/>.</returns>
|
||||||
public abstract IChangeToken GetChangeToken();
|
public abstract IChangeToken GetChangeToken();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a read-only collection of <see cref="Endpoint"/> instances.
|
||||||
|
/// </summary>
|
||||||
public abstract IReadOnlyList<Endpoint> Endpoints { get; }
|
public abstract IReadOnlyList<Endpoint> Endpoints { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public class EndpointOptions
|
// Internal for 2.2. Public API for configuring endpoints will be added in 3.0
|
||||||
|
internal class EndpointOptions
|
||||||
{
|
{
|
||||||
public IList<EndpointDataSource> DataSources { get; } = new List<EndpointDataSource>();
|
public IList<EndpointDataSource> DataSources { get; } = new List<EndpointDataSource>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents HTTP method metadata used during routing.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||||
|
public sealed class HttpMethodMetadata : IHttpMethodMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="HttpMethodMetadata" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpMethods">
|
||||||
|
/// The HTTP methods used during routing.
|
||||||
|
/// An empty collection means any HTTP method will be accepted.
|
||||||
|
/// </param>
|
||||||
|
public HttpMethodMetadata(IEnumerable<string> httpMethods)
|
||||||
|
: this(httpMethods, acceptCorsPreflight: false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="HttpMethodMetadata" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpMethods">
|
||||||
|
/// The HTTP methods used during routing.
|
||||||
|
/// An empty collection means any HTTP method will be accepted.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="acceptCorsPreflight">A value indicating whether routing accepts CORS preflight requests.</param>
|
||||||
|
public HttpMethodMetadata(IEnumerable<string> httpMethods, bool acceptCorsPreflight)
|
||||||
|
{
|
||||||
|
if (httpMethods == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(httpMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpMethods = httpMethods.ToArray();
|
||||||
|
AcceptCorsPreflight = acceptCorsPreflight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether the associated endpoint should accept CORS preflight requests.
|
||||||
|
/// </summary>
|
||||||
|
public bool AcceptCorsPreflight { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a read-only collection of HTTP methods used during routing.
|
||||||
|
/// An empty collection means any HTTP method will be accepted.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> HttpMethods { get; }
|
||||||
|
|
||||||
|
private string DebuggerToString()
|
||||||
|
{
|
||||||
|
return $"HttpMethods: {string.Join(",", HttpMethods)} - Cors: {AcceptCorsPreflight}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,8 +5,17 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a contract to find endpoints based on the supplied lookup information.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
|
||||||
public interface IEndpointFinder<TAddress>
|
public interface IEndpointFinder<TAddress>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Finds endpoints based on the supplied lookup information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The information used to look up endpoints.</param>
|
||||||
|
/// <returns>A collection of <see cref="Endpoint"/>.</returns>
|
||||||
IEnumerable<Endpoint> FindEndpoints(TAddress address);
|
IEnumerable<Endpoint> FindEndpoints(TAddress address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents HTTP method metadata used during routing.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHttpMethodMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether the associated endpoint should accept CORS preflight requests.
|
||||||
|
/// </summary>
|
||||||
|
bool AcceptCorsPreflight { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a read-only collection of HTTP methods used during routing.
|
||||||
|
/// An empty collection means any HTTP method will be accepted.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<string> HttpMethods { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
|
||||||
{
|
|
||||||
public interface IRouteNameMetadata
|
|
||||||
{
|
|
||||||
string Name { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
// 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.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public interface INameMetadata
|
public interface IRouteValuesAddressMetadata
|
||||||
{
|
{
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
|
IReadOnlyDictionary<string, object> RequiredValues { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents metadata used during link generation.
|
||||||
|
/// The associated endpoint will not be considered for link generation.
|
||||||
|
/// </summary>
|
||||||
public interface ISuppressLinkGenerationMetadata
|
public interface ISuppressLinkGenerationMetadata
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@ using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a set of <see cref="Endpoint"/> candidates that have been matched
|
||||||
|
/// by the routing system. Used by implementations of <see cref="EndpointSelector"/>
|
||||||
|
/// and <see cref="IEndpointSelectorPolicy"/>.
|
||||||
|
/// </summary>
|
||||||
public sealed class CandidateSet
|
public sealed class CandidateSet
|
||||||
{
|
{
|
||||||
// We inline storage for 4 candidates here to avoid allocations in common
|
// We inline storage for 4 candidates here to avoid allocations in common
|
||||||
|
|
@ -18,8 +23,18 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
private CandidateState[] _additionalCandidates;
|
private CandidateState[] _additionalCandidates;
|
||||||
|
|
||||||
// Provided to make testing possible/easy for someone implementing
|
/// <summary>
|
||||||
// an EndpointSelector.
|
/// <para>
|
||||||
|
/// Initializes a new instances of the candidate set structure with the provided list of endpoints
|
||||||
|
/// and associated scores.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The constructor is provided to enable unit tests of implementations of <see cref="EndpointSelector"/>
|
||||||
|
/// and <see cref="IEndpointSelectorPolicy"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoints">The list of endpoints, sorted in descending priority order.</param>
|
||||||
|
/// <param name="scores">The list of endpoint scores. <see cref="CandidateState.Score"/>.</param>
|
||||||
public CandidateSet(MatcherEndpoint[] endpoints, int[] scores)
|
public CandidateSet(MatcherEndpoint[] endpoints, int[] scores)
|
||||||
{
|
{
|
||||||
Count = endpoints.Length;
|
Count = endpoints.Length;
|
||||||
|
|
@ -112,12 +127,25 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the count of candidates in the set.
|
||||||
|
/// </summary>
|
||||||
public int Count { get; }
|
public int Count { get; }
|
||||||
|
|
||||||
// Note that this is a ref-return because of both mutability and performance.
|
/// <summary>
|
||||||
// We don't want to copy these fat structs if it can be avoided.
|
/// Gets the <see cref="CandidateState"/> associated with the candidate <see cref="Endpoint"/>
|
||||||
|
/// at <paramref name="index"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The candidate index.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A reference to the <see cref="CandidateState"/>. The result is returned by reference
|
||||||
|
/// and intended to be mutated.
|
||||||
|
/// </returns>
|
||||||
public ref CandidateState this[int index]
|
public ref CandidateState this[int index]
|
||||||
{
|
{
|
||||||
|
// Note that this is a ref-return because of both mutability and performance.
|
||||||
|
// We don't want to copy these fat structs if it can be avoided.
|
||||||
|
|
||||||
// PERF: Force inlining
|
// PERF: Force inlining
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The mutable state associated with a candidate in a <see cref="CandidateSet"/>.
|
||||||
|
/// </summary>
|
||||||
public struct CandidateState
|
public struct CandidateState
|
||||||
{
|
{
|
||||||
internal CandidateState(MatcherEndpoint endpoint, int score)
|
internal CandidateState(MatcherEndpoint endpoint, int score)
|
||||||
|
|
@ -14,12 +17,39 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
Values = null;
|
Values = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="Routing.Endpoint"/>.
|
||||||
|
/// </summary>
|
||||||
public MatcherEndpoint Endpoint { get; }
|
public MatcherEndpoint Endpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the score of the <see cref="Routing.Endpoint"/> within the current
|
||||||
|
/// <see cref="CandidateSet"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Candidates within a set are ordered in priority order and then assigned a
|
||||||
|
/// sequential score value based on that ordering. Candiates with the same
|
||||||
|
/// score are considered to have equal priority.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The score values are used in the <see cref="EndpointSelector"/> to determine
|
||||||
|
/// whether a set of matching candidates is an ambiguous match.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public int Score { get; }
|
public int Score { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value which indicates where the <see cref="Routing.Endpoint"/> is considered
|
||||||
|
/// a valid candiate for the current request. Set this value to <c>false</c> to exclude an
|
||||||
|
/// <see cref="Routing.Endpoint"/> from consideration.
|
||||||
|
/// </summary>
|
||||||
public bool IsValidCandidate { get; set; }
|
public bool IsValidCandidate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="RouteValueDictionary"/> associated with the
|
||||||
|
/// <see cref="Routing.Endpoint"/> and the current request.
|
||||||
|
/// </summary>
|
||||||
public RouteValueDictionary Values { get; set; }
|
public RouteValueDictionary Values { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,30 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A base class for <see cref="IComparer{Endpoint}"/> implementations that use
|
||||||
|
/// a specific type of metadata from <see cref="Endpoint.Metadata"/> for comparison.
|
||||||
|
/// Useful for implementing <see cref="IEndpointComparerPolicy.Comparer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TMetadata">
|
||||||
|
/// The type of metadata to compare. Typically this is a type of metadata related
|
||||||
|
/// to the application concern being handled.
|
||||||
|
/// </typeparam>
|
||||||
public abstract class EndpointMetadataComparer<TMetadata> : IComparer<Endpoint> where TMetadata : class
|
public abstract class EndpointMetadataComparer<TMetadata> : IComparer<Endpoint> where TMetadata : class
|
||||||
{
|
{
|
||||||
public static readonly EndpointMetadataComparer<TMetadata> Default = new DefaultComparer<TMetadata>();
|
public static readonly EndpointMetadataComparer<TMetadata> Default = new DefaultComparer<TMetadata>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two objects and returns a value indicating whether one is less than, equal to,
|
||||||
|
/// or greater than the other.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The first object to compare.</param>
|
||||||
|
/// <param name="y">The second object to compare.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An implementation of this method must return a value less than zero if
|
||||||
|
/// x is less than y, zero if x is equal to y, or a value greater than zero if x is
|
||||||
|
/// greater than y.
|
||||||
|
/// </returns>
|
||||||
public int Compare(Endpoint x, Endpoint y)
|
public int Compare(Endpoint x, Endpoint y)
|
||||||
{
|
{
|
||||||
if (x == null)
|
if (x == null)
|
||||||
|
|
@ -25,11 +45,32 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return CompareMetadata(GetMetadata(x), GetMetadata(y));
|
return CompareMetadata(GetMetadata(x), GetMetadata(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the metadata of type <typeparamref name="TMetadata"/> from the provided endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The <see cref="Endpoint"/>.</param>
|
||||||
|
/// <returns>The <typeparamref name="TMetadata"/> instance or <c>null</c>.</returns>
|
||||||
protected virtual TMetadata GetMetadata(Endpoint endpoint)
|
protected virtual TMetadata GetMetadata(Endpoint endpoint)
|
||||||
{
|
{
|
||||||
return endpoint.Metadata.GetMetadata<TMetadata>();
|
return endpoint.Metadata.GetMetadata<TMetadata>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two <typeparamref name="TMetadata"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The first object to compare.</param>
|
||||||
|
/// <param name="y">The second object to compare.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An implementation of this method must return a value less than zero if
|
||||||
|
/// x is less than y, zero if x is equal to y, or a value greater than zero if x is
|
||||||
|
/// greater than y.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The base-class implementation of this method will compare metadata based on whether
|
||||||
|
/// or not they are <c>null</c>. The effect of this is that when endpoints are being
|
||||||
|
/// compared, the endpoint that defines an instance of <typeparamref name="TMetadata"/>
|
||||||
|
/// will be considered higher priority.
|
||||||
|
/// </remarks>
|
||||||
protected virtual int CompareMetadata(TMetadata x, TMetadata y)
|
protected virtual int CompareMetadata(TMetadata x, TMetadata y)
|
||||||
{
|
{
|
||||||
// The default policy is that if x endpoint defines TMetadata, and
|
// The default policy is that if x endpoint defines TMetadata, and
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,25 @@ using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A service that is responsible for the final <see cref="Endpoint"/> selection
|
||||||
|
/// decision. To use a custom <see cref="EndpointSelector"/> register an implementation
|
||||||
|
/// of <see cref="EndpointSelector"/> in the dependency injection container as a singleton.
|
||||||
|
/// </summary>
|
||||||
public abstract class EndpointSelector
|
public abstract class EndpointSelector
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously selects an <see cref="Endpoint"/> from the <see cref="CandidateSet"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="feature">The <see cref="IEndpointFeature"/> associated with the current request.</param>
|
||||||
|
/// <param name="candidates">The <see cref="CandidateSet"/>.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that completes asynchronously once endpoint selection is complete.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// An <see cref="EndpointSelector"/> should assign the <see cref="IEndpointFeature.Endpoint"/>,
|
||||||
|
/// <see cref="IEndpointFeature.Invoker"/>, and <see cref="IEndpointFeature.Values"/> properties
|
||||||
|
/// once an endpoint is selected.
|
||||||
|
/// </remarks>
|
||||||
public abstract Task SelectAsync(
|
public abstract Task SelectAsync(
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
IEndpointFeature feature,
|
IEndpointFeature feature,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Metadata;
|
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="MatcherPolicy"/> that implements filtering and selection by
|
||||||
|
/// the HTTP method of a request.
|
||||||
|
/// </summary>
|
||||||
public sealed class HttpMethodMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
|
public sealed class HttpMethodMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
|
||||||
{
|
{
|
||||||
// Used in tests
|
// Used in tests
|
||||||
|
|
@ -26,12 +29,23 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
// Used in tests
|
// Used in tests
|
||||||
internal const string AnyMethod = "*";
|
internal const string AnyMethod = "*";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For framework use only.
|
||||||
|
/// </summary>
|
||||||
public IComparer<Endpoint> Comparer => new HttpMethodMetadataEndpointComparer();
|
public IComparer<Endpoint> Comparer => new HttpMethodMetadataEndpointComparer();
|
||||||
|
|
||||||
// The order value is chosen to be less than 0, so that it comes before naively
|
// The order value is chosen to be less than 0, so that it comes before naively
|
||||||
// written policies.
|
// written policies.
|
||||||
|
/// <summary>
|
||||||
|
/// For framework use only.
|
||||||
|
/// </summary>
|
||||||
public override int Order => -1000;
|
public override int Order => -1000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For framework use only.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoints"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public bool AppliesToNode(IReadOnlyList<Endpoint> endpoints)
|
public bool AppliesToNode(IReadOnlyList<Endpoint> endpoints)
|
||||||
{
|
{
|
||||||
if (endpoints == null)
|
if (endpoints == null)
|
||||||
|
|
@ -50,6 +64,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For framework use only.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoints"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
|
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
|
||||||
{
|
{
|
||||||
// The algorithm here is designed to be preserve the order of the endpoints
|
// The algorithm here is designed to be preserve the order of the endpoints
|
||||||
|
|
@ -181,6 +200,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For framework use only.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exitDestination"></param>
|
||||||
|
/// <param name="edges"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
|
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
|
||||||
{
|
{
|
||||||
var destinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
var destinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
@ -232,7 +257,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
},
|
},
|
||||||
RoutePatternFactory.Parse("/"),
|
RoutePatternFactory.Parse("/"),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
Http405EndpointDisplayName);
|
Http405EndpointDisplayName);
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,30 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="MatcherPolicy"/> interface that can be implemented to sort
|
||||||
|
/// endpoints. Implementations of <see cref="IEndpointComparerPolicy"/> must
|
||||||
|
/// inherit from <see cref="MatcherPolicy"/> and should be registered in
|
||||||
|
/// the dependency injection container as singleton services of type <see cref="MatcherPolicy"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Candidates in a <see cref="CandidateSet"/> are sorted based on their priority. Defining
|
||||||
|
/// a <see cref="IEndpointComparerPolicy"/> adds an additional criterion to the sorting
|
||||||
|
/// operation used to order candidates.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// As an example, the implementation of <see cref="HttpMethodMatcherPolicy"/> implements
|
||||||
|
/// <see cref="IEndpointComparerPolicy"/> to ensure that endpoints matching specific HTTP
|
||||||
|
/// methods are sorted with a higher priority than endpoints without a specific HTTP method
|
||||||
|
/// requirement.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public interface IEndpointComparerPolicy
|
public interface IEndpointComparerPolicy
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an <see cref="IComparer{Endpoint}"/> that will be used to sort the endpoints.
|
||||||
|
/// </summary>
|
||||||
IComparer<Endpoint> Comparer { get; }
|
IComparer<Endpoint> Comparer { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,26 @@ using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="MatcherPolicy"/> interface that can implemented to filter endpoints
|
||||||
|
/// in a <see cref="CandidateSet"/>. Implementations of <see cref="IEndpointSelectorPolicy"/> must
|
||||||
|
/// inherit from <see cref="MatcherPolicy"/> and should be registered in
|
||||||
|
/// the dependency injection container as singleton services of type <see cref="MatcherPolicy"/>.
|
||||||
|
/// </summary>
|
||||||
public interface IEndpointSelectorPolicy
|
public interface IEndpointSelectorPolicy
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the policy to the <see cref="CandidateSet"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">
|
||||||
|
/// The <see cref="HttpContext"/> associated with the current request.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="candidates">The <see cref="CandidateSet"/>.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Implementations of <see cref="IEndpointSelectorPolicy"/> should implement this method
|
||||||
|
/// and filter the set of candidates in the <paramref name="candidates"/> by setting
|
||||||
|
/// <see cref="CandidateState.IsValidCandidate"/> to <c>false</c> where desired.
|
||||||
|
/// </remarks>
|
||||||
void Apply(HttpContext httpContext, CandidateSet candidates);
|
void Apply(HttpContext httpContext, CandidateSet candidates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +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 Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,6 +9,9 @@ using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an <see cref="Endpoint"/> that can be used in URL matching or URL generation.
|
||||||
|
/// </summary>
|
||||||
public sealed class MatcherEndpoint : Endpoint
|
public sealed class MatcherEndpoint : Endpoint
|
||||||
{
|
{
|
||||||
internal static readonly Func<RequestDelegate, RequestDelegate> EmptyInvoker = (next) =>
|
internal static readonly Func<RequestDelegate, RequestDelegate> EmptyInvoker = (next) =>
|
||||||
|
|
@ -16,10 +19,19 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return (context) => Task.CompletedTask;
|
return (context) => Task.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MatcherEndpoint"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="invoker">The delegate to invoke to create a <see cref="RequestDelegate"/>.</param>
|
||||||
|
/// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>
|
||||||
|
/// <param name="order">The order assigned to the endpoint.</param>
|
||||||
|
/// <param name="metadata">
|
||||||
|
/// The <see cref="EndpointMetadataCollection"/> or metadata associated with the endpoint.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="displayName">The informational display name of the endpoint.</param>
|
||||||
public MatcherEndpoint(
|
public MatcherEndpoint(
|
||||||
Func<RequestDelegate, RequestDelegate> invoker,
|
Func<RequestDelegate, RequestDelegate> invoker,
|
||||||
RoutePattern routePattern,
|
RoutePattern routePattern,
|
||||||
RouteValueDictionary requiredValues,
|
|
||||||
int order,
|
int order,
|
||||||
EndpointMetadataCollection metadata,
|
EndpointMetadataCollection metadata,
|
||||||
string displayName)
|
string displayName)
|
||||||
|
|
@ -37,17 +49,26 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
Invoker = invoker;
|
Invoker = invoker;
|
||||||
RoutePattern = routePattern;
|
RoutePattern = routePattern;
|
||||||
RequiredValues = requiredValues;
|
|
||||||
Order = order;
|
Order = order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the invoker. The invoker is a delegate used to create a <see cref="RequestDelegate"/>.
|
||||||
|
/// </summary>
|
||||||
public Func<RequestDelegate, RequestDelegate> Invoker { get; }
|
public Func<RequestDelegate, RequestDelegate> Invoker { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the order value of endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The order value provides absolute control over the priority
|
||||||
|
/// of an endpoint. Endpoints with a lower numeric value of order have higher priority.
|
||||||
|
/// </remarks>
|
||||||
public int Order { get; }
|
public int Order { get; }
|
||||||
|
|
||||||
// Values required by an endpoint for it to be successfully matched on link generation
|
/// <summary>
|
||||||
public IReadOnlyDictionary<string, object> RequiredValues { get; }
|
/// Gets the <see cref="RoutePattern"/> associated with the endpoint.
|
||||||
|
/// </summary>
|
||||||
public RoutePattern RoutePattern { get; }
|
public RoutePattern RoutePattern { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,28 @@
|
||||||
// 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 Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a policy that applies behaviors to the URL matcher. Implementations
|
||||||
|
/// of <see cref="MatcherPolicy"/> and related interfaces must be registered
|
||||||
|
/// in the dependency injection container as singleton services of type
|
||||||
|
/// <see cref="MatcherPolicy"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="MatcherPolicy"/> implementations can implement the following
|
||||||
|
/// interfaces <see cref="IEndpointComparerPolicy"/>, <see cref="IEndpointSelectorPolicy"/>,
|
||||||
|
/// and <see cref="INodeBuilderPolicy"/>.
|
||||||
|
/// </remarks>
|
||||||
public abstract class MatcherPolicy
|
public abstract class MatcherPolicy
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that determines the order the <see cref="MatcherPolicy"/> should
|
||||||
|
/// be applied. Policies are applied in ascending numeric value of the <see cref="Order"/>
|
||||||
|
/// property.
|
||||||
|
/// </summary>
|
||||||
public abstract int Order { get; }
|
public abstract int Order { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Metadata
|
|
||||||
{
|
|
||||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
|
||||||
public sealed class HttpMethodMetadata : IHttpMethodMetadata
|
|
||||||
{
|
|
||||||
public HttpMethodMetadata(IEnumerable<string> httpMethods)
|
|
||||||
: this(httpMethods, acceptCorsPreflight: false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpMethodMetadata(IEnumerable<string> httpMethods, bool acceptCorsPreflight)
|
|
||||||
{
|
|
||||||
if (httpMethods == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(httpMethods));
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpMethods = httpMethods.ToArray();
|
|
||||||
AcceptCorsPreflight = acceptCorsPreflight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AcceptCorsPreflight { get; }
|
|
||||||
|
|
||||||
public IReadOnlyList<string> HttpMethods { get; }
|
|
||||||
|
|
||||||
private string DebuggerToString()
|
|
||||||
{
|
|
||||||
return $"HttpMethods: {string.Join(",", HttpMethods)} - Cors: {AcceptCorsPreflight}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Metadata
|
|
||||||
{
|
|
||||||
public interface IHttpMethodMetadata
|
|
||||||
{
|
|
||||||
bool AcceptCorsPreflight { get; }
|
|
||||||
|
|
||||||
IReadOnlyList<string> HttpMethods { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
|
||||||
{
|
|
||||||
internal class NameBasedEndpointFinder : IEndpointFinder<string>
|
|
||||||
{
|
|
||||||
private readonly CompositeEndpointDataSource _endpointDatasource;
|
|
||||||
private readonly ILogger<NameBasedEndpointFinder> _logger;
|
|
||||||
|
|
||||||
public NameBasedEndpointFinder(
|
|
||||||
CompositeEndpointDataSource endpointDataSource,
|
|
||||||
ILogger<NameBasedEndpointFinder> logger)
|
|
||||||
{
|
|
||||||
_endpointDatasource = endpointDataSource;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Endpoint> FindEndpoints(string endpointName)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(endpointName))
|
|
||||||
{
|
|
||||||
return Array.Empty<Endpoint>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoints = _endpointDatasource.Endpoints.OfType<MatcherEndpoint>();
|
|
||||||
|
|
||||||
foreach (var endpoint in endpoints)
|
|
||||||
{
|
|
||||||
var nameMetadata = endpoint.Metadata.GetMetadata<INameMetadata>();
|
|
||||||
if (nameMetadata != null &&
|
|
||||||
string.Equals(endpointName, nameMetadata.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return new[] { endpoint };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.Empty<Endpoint>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||||
|
public sealed class RouteValuesAddressMetadata : IRouteValuesAddressMetadata
|
||||||
|
{
|
||||||
|
public RouteValuesAddressMetadata(string name, IReadOnlyDictionary<string, object> requiredValues)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
RequiredValues = requiredValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, object> RequiredValues { get; }
|
||||||
|
|
||||||
|
internal string DebuggerToString()
|
||||||
|
{
|
||||||
|
return $"Name: {Name} - Required values: {string.Join(", ", FormatValues(RequiredValues))}";
|
||||||
|
|
||||||
|
IEnumerable<string> FormatValues(IEnumerable<KeyValuePair<string, object>> values)
|
||||||
|
{
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.Select(
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
var value = "null";
|
||||||
|
if (kvp.Value != null)
|
||||||
|
{
|
||||||
|
value = "\"" + kvp.Value.ToString() + "\"";
|
||||||
|
}
|
||||||
|
return kvp.Key + " = " + value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -137,16 +137,16 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
private OutboundRouteEntry CreateOutboundRouteEntry(MatcherEndpoint endpoint)
|
private OutboundRouteEntry CreateOutboundRouteEntry(MatcherEndpoint endpoint)
|
||||||
{
|
{
|
||||||
var routeNameMetadata = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||||
var entry = new OutboundRouteEntry()
|
var entry = new OutboundRouteEntry()
|
||||||
{
|
{
|
||||||
Handler = NullRouter.Instance,
|
Handler = NullRouter.Instance,
|
||||||
Order = endpoint.Order,
|
Order = endpoint.Order,
|
||||||
Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
|
Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
|
||||||
RequiredLinkValues = new RouteValueDictionary(endpoint.RequiredValues),
|
RequiredLinkValues = new RouteValueDictionary(routeValuesAddressMetadata?.RequiredValues),
|
||||||
RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
|
RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
|
||||||
Data = endpoint,
|
Data = endpoint,
|
||||||
RouteName = routeNameMetadata?.Name,
|
RouteName = routeValuesAddressMetadata?.Name,
|
||||||
};
|
};
|
||||||
entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
|
entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
|
||||||
return entry;
|
return entry;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents metadata used during link generation.
|
||||||
|
/// The associated endpoint will not be considered for link generation.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -149,14 +149,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private MatcherEndpoint CreateEndpoint(
|
private MatcherEndpoint CreateEndpoint(
|
||||||
string template,
|
string template,
|
||||||
object defaults = null,
|
object defaults = null,
|
||||||
object requiredValues = null,
|
|
||||||
int order = 0,
|
int order = 0,
|
||||||
string routeName = null)
|
string routeName = null)
|
||||||
{
|
{
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
||||||
new RouteValueDictionary(requiredValues),
|
|
||||||
order,
|
order,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
null);
|
null);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
|
@ -17,18 +19,17 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
string displayName = null,
|
string displayName = null,
|
||||||
params object[] metadata)
|
params object[] metadata)
|
||||||
{
|
{
|
||||||
var metadataCollection = EndpointMetadataCollection.Empty;
|
var d = new List<object>(metadata ?? Array.Empty<object>());
|
||||||
if (metadata != null)
|
if (requiredValues != null)
|
||||||
{
|
{
|
||||||
metadataCollection = new EndpointMetadataCollection(metadata);
|
d.Add(new RouteValuesAddressMetadata(null, new RouteValueDictionary(requiredValues)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints),
|
RoutePatternFactory.Parse(template, defaults, constraints),
|
||||||
new RouteValueDictionary(requiredValues),
|
|
||||||
order,
|
order,
|
||||||
metadataCollection,
|
new EndpointMetadataCollection(d),
|
||||||
displayName);
|
displayName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"test");
|
"test");
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
var endpoint = new MatcherEndpoint(
|
var endpoint = new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse("a/b/c"),
|
RoutePatternFactory.Parse("a/b/c"),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"test");
|
"test");
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,6 @@ test: /test3", ex.Message);
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
$"test: {template}");
|
$"test: {template}");
|
||||||
|
|
|
||||||
|
|
@ -282,12 +282,38 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EndsWithStringMatchProcessor : MatchProcessorBase
|
private class EndsWithStringMatchProcessor : MatchProcessor
|
||||||
{
|
{
|
||||||
public override bool Process(object value)
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
return valueString.EndsWith(ConstraintArgument);
|
var endsWith = valueString.EndsWith(ConstraintArgument, StringComparison.OrdinalIgnoreCase);
|
||||||
|
return endsWith;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -860,7 +860,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints)),
|
RoutePatternFactory.Parse(template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints)),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
new EndpointMetadataCollection(metadata),
|
new EndpointMetadataCollection(metadata),
|
||||||
"test");
|
"test");
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
||||||
new RouteValueDictionary(),
|
|
||||||
order,
|
order,
|
||||||
metadata ?? EndpointMetadataCollection.Empty,
|
metadata ?? EndpointMetadataCollection.Empty,
|
||||||
template);
|
template);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ 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.Metadata;
|
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -343,7 +342,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints),
|
RoutePatternFactory.Parse(template, defaults, constraints),
|
||||||
new RouteValueDictionary(),
|
|
||||||
order,
|
order,
|
||||||
new EndpointMetadataCollection(metadata),
|
new EndpointMetadataCollection(metadata),
|
||||||
displayName);
|
displayName);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Routing.Metadata;
|
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static Microsoft.AspNetCore.Routing.Matching.HttpMethodMatcherPolicy;
|
using static Microsoft.AspNetCore.Routing.Matching.HttpMethodMatcherPolicy;
|
||||||
|
|
@ -288,7 +287,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
new RouteValueDictionary(),
|
|
||||||
0,
|
0,
|
||||||
new EndpointMetadataCollection(metadata),
|
new EndpointMetadataCollection(metadata),
|
||||||
$"test: {template}");
|
$"test: {template}");
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints),
|
RoutePatternFactory.Parse(template, defaults, constraints),
|
||||||
new RouteValueDictionary(),
|
|
||||||
order ?? 0,
|
order ?? 0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"endpoint: " + template);
|
"endpoint: " + template);
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
new RouteValueDictionary(),
|
|
||||||
order,
|
order,
|
||||||
new EndpointMetadataCollection(metadata),
|
new EndpointMetadataCollection(metadata),
|
||||||
"test: " + template);
|
"test: " + template);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -79,29 +80,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Assert.Same(endpoint3, Assert.IsType<MatcherEndpoint>(namedMatches[1].Match.Entry.Data));
|
Assert.Same(endpoint3, Assert.IsType<MatcherEndpoint>(namedMatches[1].Match.Entry.Data));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetOutboundMatches_DoesNotGetNamedMatchesFor_EndpointsHaving_INameMetadata()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var endpoint1 = CreateEndpoint("/a");
|
|
||||||
var endpoint2 = CreateEndpoint("/a", routeName: "named");
|
|
||||||
var endpoint3 = CreateEndpoint(
|
|
||||||
"/b",
|
|
||||||
metadataCollection: new EndpointMetadataCollection(new[] { new NameMetadata("named") }));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var finder = CreateEndpointFinder(endpoint1, endpoint2);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(finder.AllMatches);
|
|
||||||
Assert.Equal(2, finder.AllMatches.Count());
|
|
||||||
Assert.NotNull(finder.NamedMatches);
|
|
||||||
Assert.True(finder.NamedMatches.TryGetValue("named", out var namedMatches));
|
|
||||||
var namedMatch = Assert.Single(namedMatches);
|
|
||||||
var actual = Assert.IsType<MatcherEndpoint>(namedMatch.Match.Entry.Data);
|
|
||||||
Assert.Same(endpoint2, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches()
|
public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches()
|
||||||
{
|
{
|
||||||
|
|
@ -263,40 +241,22 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
if (metadataCollection == null)
|
if (metadataCollection == null)
|
||||||
{
|
{
|
||||||
metadataCollection = EndpointMetadataCollection.Empty;
|
var metadata = new List<object>();
|
||||||
if (!string.IsNullOrEmpty(routeName))
|
if (!string.IsNullOrEmpty(routeName) || requiredValues != null)
|
||||||
{
|
{
|
||||||
metadataCollection = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) });
|
metadata.Add(new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
|
||||||
}
|
}
|
||||||
|
metadataCollection = new EndpointMetadataCollection(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MatcherEndpoint(
|
return new MatcherEndpoint(
|
||||||
MatcherEndpoint.EmptyInvoker,
|
MatcherEndpoint.EmptyInvoker,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
||||||
new RouteValueDictionary(requiredValues),
|
|
||||||
order,
|
order,
|
||||||
metadataCollection,
|
metadataCollection,
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RouteNameMetadata : IRouteNameMetadata
|
|
||||||
{
|
|
||||||
public RouteNameMetadata(string name)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
public string Name { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NameMetadata : INameMetadata
|
|
||||||
{
|
|
||||||
public NameMetadata(string name)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
public string Name { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomRouteValuesBasedEndpointFinder : RouteValuesBasedEndpointFinder
|
private class CustomRouteValuesBasedEndpointFinder : RouteValuesBasedEndpointFinder
|
||||||
{
|
{
|
||||||
public CustomRouteValuesBasedEndpointFinder(
|
public CustomRouteValuesBasedEndpointFinder(
|
||||||
|
|
@ -318,7 +278,5 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// 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.Text;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
public class RouteValuesAddressMetadataTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DebuggerToString_NoNameAndRequiredValues_ReturnsString()
|
||||||
|
{
|
||||||
|
var metadata = new RouteValuesAddressMetadata(null, null);
|
||||||
|
|
||||||
|
Assert.Equal("Name: - Required values: ", metadata.DebuggerToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DebuggerToString_HasNameAndRequiredValues_ReturnsString()
|
||||||
|
{
|
||||||
|
var metadata = new RouteValuesAddressMetadata("Name!", new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["requiredValue1"] = "One",
|
||||||
|
["requiredValue2"] = 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("Name: Name! - Required values: requiredValue1 = \"One\", requiredValue2 = \"2\"", metadata.DebuggerToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue