Merge pull request #790 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
087e6d05ee
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace RoutingSample.Web
|
namespace RoutingSample.Web
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
|
@ -19,7 +20,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
internal sealed class DefaultLinkGenerator : LinkGenerator
|
internal sealed class DefaultLinkGenerator : LinkGenerator
|
||||||
{
|
{
|
||||||
private static readonly char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
|
||||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
||||||
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
||||||
private readonly ILogger<DefaultLinkGenerator> _logger;
|
private readonly ILogger<DefaultLinkGenerator> _logger;
|
||||||
|
|
@ -172,7 +172,18 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private List<RouteEndpoint> GetEndpoints<TAddress>(TAddress address)
|
private List<RouteEndpoint> GetEndpoints<TAddress>(TAddress address)
|
||||||
{
|
{
|
||||||
var addressingScheme = _serviceProvider.GetRequiredService<IEndpointFinder<TAddress>>();
|
var addressingScheme = _serviceProvider.GetRequiredService<IEndpointFinder<TAddress>>();
|
||||||
return addressingScheme.FindEndpoints(address).OfType<RouteEndpoint>().ToList();
|
var endpoints = addressingScheme.FindEndpoints(address).OfType<RouteEndpoint>().ToList();
|
||||||
|
|
||||||
|
if (endpoints.Count == 0)
|
||||||
|
{
|
||||||
|
Log.EndpointsNotFound(_logger, address);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.EndpointsFound(_logger, address, endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also called from DefaultLinkGenerationTemplate
|
// Also called from DefaultLinkGenerationTemplate
|
||||||
|
|
@ -195,15 +206,17 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
options,
|
options,
|
||||||
out var result))
|
out var result))
|
||||||
{
|
{
|
||||||
|
var uri = UriHelper.BuildRelative(
|
||||||
return UriHelper.BuildRelative(
|
|
||||||
pathBase,
|
pathBase,
|
||||||
result.path,
|
result.path,
|
||||||
result.query,
|
result.query,
|
||||||
fragment);
|
fragment);
|
||||||
|
Log.LinkGenerationSucceeded(_logger, endpoints, uri);
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.LinkGenerationFailed(_logger, endpoints);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,16 +242,19 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
options,
|
options,
|
||||||
out var result))
|
out var result))
|
||||||
{
|
{
|
||||||
return UriHelper.BuildAbsolute(
|
var uri = UriHelper.BuildAbsolute(
|
||||||
scheme,
|
scheme,
|
||||||
host,
|
host,
|
||||||
pathBase,
|
pathBase,
|
||||||
result.path,
|
result.path,
|
||||||
result.query,
|
result.query,
|
||||||
fragment);
|
fragment);
|
||||||
|
Log.LinkGenerationSucceeded(_logger, endpoints, uri);
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.LinkGenerationFailed(_logger, endpoints);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,20 +282,25 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// We're missing one of the required values for this route.
|
// We're missing one of the required values for this route.
|
||||||
result = default;
|
result = default;
|
||||||
|
Log.TemplateFailedRequiredValues(_logger, endpoint, ambientValues, explicitValues);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
|
if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
|
||||||
{
|
{
|
||||||
result = default;
|
result = default;
|
||||||
|
|
||||||
|
// MatchesConstraints does its own logging, so we're not logging here.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!templateBinder.TryBindValues(templateValuesResult.AcceptedValues, options, _globalLinkOptions, out result))
|
if (!templateBinder.TryBindValues(templateValuesResult.AcceptedValues, options, _globalLinkOptions, out result))
|
||||||
{
|
{
|
||||||
|
Log.TemplateFailedExpansion(_logger, endpoint, templateValuesResult.AcceptedValues);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.TemplateSucceeded(_logger, endpoint, result.path, result.query);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,6 +325,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
if (parameterPolicy is IRouteConstraint routeConstraint
|
if (parameterPolicy is IRouteConstraint routeConstraint
|
||||||
&& !routeConstraint.Match(httpContext, NullRouter.Instance, kvp.Key, routeValues, RouteDirection.UrlGeneration))
|
&& !routeConstraint.Match(httpContext, NullRouter.Instance, kvp.Key, routeValues, RouteDirection.UrlGeneration))
|
||||||
{
|
{
|
||||||
|
Log.TemplateFailedConstraint(_logger, endpoint, kvp.Key, routeConstraint, routeValues);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -317,5 +339,163 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
return httpContext?.Features.Get<IRouteValuesFeature>()?.RouteValues;
|
return httpContext?.Features.Get<IRouteValuesFeature>()?.RouteValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class Log
|
||||||
|
{
|
||||||
|
public static class EventIds
|
||||||
|
{
|
||||||
|
public static readonly EventId EndpointsFound = new EventId(100, "EndpointsFound");
|
||||||
|
public static readonly EventId EndpointsNotFound = new EventId(101, "EndpointsNotFound");
|
||||||
|
|
||||||
|
public static readonly EventId TemplateSucceeded = new EventId(102, "TemplateSucceeded");
|
||||||
|
public static readonly EventId TemplateFailedRequiredValues = new EventId(103, "TemplateFailedRequiredValues");
|
||||||
|
public static readonly EventId TemplateFailedConstraint = new EventId(103, "TemplateFailedConstraint");
|
||||||
|
public static readonly EventId TemplateFailedExpansion = new EventId(104, "TemplateFailedExpansion");
|
||||||
|
|
||||||
|
public static readonly EventId LinkGenerationSucceeded = new EventId(105, "LinkGenerationSucceeded");
|
||||||
|
public static readonly EventId LinkGenerationFailed = new EventId(106, "LinkGenerationFailed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, IEnumerable<string>, object, Exception> _endpointsFound = LoggerMessage.Define<IEnumerable<string>, object>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.EndpointsFound,
|
||||||
|
"Found the endpoints {Endpoints} for address {Address}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, object, Exception> _endpointsNotFound = LoggerMessage.Define<object>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.EndpointsNotFound,
|
||||||
|
"No endpoints found for address {Address}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, string, string, Exception> _templateSucceeded = LoggerMessage.Define<string, string, string, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.TemplateSucceeded,
|
||||||
|
"Successfully processed template {Template} for {Endpoint} resulting in {Path} and {Query}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, string, string, string, Exception> _templateFailedRequiredValues = LoggerMessage.Define<string, string, string, string, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.TemplateFailedRequiredValues,
|
||||||
|
"Failed to process the template {Template} for {Endpoint}. " +
|
||||||
|
"A required route value is missing, or has a different value from the required default values." +
|
||||||
|
"Supplied ambient values {AmbientValues} and {Values} with default values {Defaults}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, IRouteConstraint, string, string, Exception> _templateFailedConstraint = LoggerMessage.Define<string, string, IRouteConstraint, string, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.TemplateFailedConstraint,
|
||||||
|
"Failed to process the template {Template} for {Endpoint}. " +
|
||||||
|
"The constraint {Constraint} for parameter {ParameterName} failed with values {Values}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, string, Exception> _templateFailedExpansion = LoggerMessage.Define<string, string, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.TemplateFailedExpansion,
|
||||||
|
"Failed to process the template {Template} for {Endpoint}. " +
|
||||||
|
"The failure occured while expanding the template with values {Values}. " +
|
||||||
|
"This is usually due to a missing or empty value in a complex segment.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, IEnumerable<string>, string, Exception> _linkGenerationSucceeded = LoggerMessage.Define<IEnumerable<string>, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.LinkGenerationSucceeded,
|
||||||
|
"Link generation succeeded for endpoints {Endpoints} with result {URI}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, IEnumerable<string>, Exception> _linkGenerationFailed = LoggerMessage.Define<IEnumerable<string>>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.LinkGenerationFailed,
|
||||||
|
"Link generation failed for endpoints {Endpoints}");
|
||||||
|
|
||||||
|
public static void EndpointsFound(ILogger logger, object address, IEnumerable<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
// Checking level again to avoid allocation on the common path
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_endpointsFound(logger, endpoints.Select(e => e.DisplayName), address, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EndpointsNotFound(ILogger logger, object address)
|
||||||
|
{
|
||||||
|
_endpointsNotFound(logger, address, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TemplateSucceeded(ILogger logger, RouteEndpoint endpoint, PathString path, QueryString query)
|
||||||
|
{
|
||||||
|
_templateSucceeded(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, path.Value, query.Value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TemplateFailedRequiredValues(ILogger logger, RouteEndpoint endpoint, RouteValueDictionary ambientValues, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
// Checking level again to avoid allocation on the common path
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_templateFailedRequiredValues(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, FormatRouteValues(ambientValues), FormatRouteValues(values), FormatRouteValues(endpoint.RoutePattern.Defaults), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TemplateFailedConstraint(ILogger logger, RouteEndpoint endpoint, string parameterName, IRouteConstraint constraint, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
// Checking level again to avoid allocation on the common path
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_templateFailedConstraint(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, constraint, parameterName, FormatRouteValues(values), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TemplateFailedExpansion(ILogger logger, RouteEndpoint endpoint, RouteValueDictionary values)
|
||||||
|
{
|
||||||
|
// Checking level again to avoid allocation on the common path
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_templateFailedExpansion(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, FormatRouteValues(values), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LinkGenerationSucceeded(ILogger logger, IEnumerable<Endpoint> endpoints, string uri)
|
||||||
|
{
|
||||||
|
// Checking level again to avoid allocation on the common path
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_linkGenerationSucceeded(logger, endpoints.Select(e => e.DisplayName), uri, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LinkGenerationFailed(ILogger logger, IEnumerable<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
// Checking level again to avoid allocation on the common path
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_linkGenerationFailed(logger, endpoints.Select(e => e.DisplayName), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPENSIVE: should only be used at Debug and higher levels of logging.
|
||||||
|
private static string FormatRouteValues(IReadOnlyDictionary<string, object> values)
|
||||||
|
{
|
||||||
|
if (values == null || values.Count == 0)
|
||||||
|
{
|
||||||
|
return "{ }";
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append("{ ");
|
||||||
|
|
||||||
|
foreach (var kvp in values.OrderBy(kvp => kvp.Key))
|
||||||
|
{
|
||||||
|
builder.Append("\"");
|
||||||
|
builder.Append(kvp.Key);
|
||||||
|
builder.Append("\"");
|
||||||
|
builder.Append(":");
|
||||||
|
builder.Append(" ");
|
||||||
|
builder.Append("\"");
|
||||||
|
builder.Append(kvp.Value);
|
||||||
|
builder.Append("\"");
|
||||||
|
builder.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim trailing ", "
|
||||||
|
builder.Remove(builder.Length - 2, 2);
|
||||||
|
|
||||||
|
builder.Append(" }");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddSingleton<DfaGraphWriter>();
|
services.TryAddSingleton<DfaGraphWriter>();
|
||||||
|
|
||||||
// Link generation related services
|
// Link generation related services
|
||||||
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
|
||||||
services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
|
services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
|
||||||
|
services.TryAddSingleton<IEndpointFinder<string>, EndpointNameEndpointFinder>();
|
||||||
|
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Endpoint Selection
|
// Endpoint Selection
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
internal class EndpointNameEndpointFinder : IEndpointFinder<string>
|
||||||
|
{
|
||||||
|
private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache;
|
||||||
|
|
||||||
|
public EndpointNameEndpointFinder(CompositeEndpointDataSource dataSource)
|
||||||
|
{
|
||||||
|
_cache = new DataSourceDependentCache<Dictionary<string, Endpoint[]>>(dataSource, Initialize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for tests
|
||||||
|
internal Dictionary<string, Endpoint[]> Entries => _cache.EnsureInitialized();
|
||||||
|
|
||||||
|
public IEnumerable<Endpoint> FindEndpoints(string address)
|
||||||
|
{
|
||||||
|
if (address == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the current value of the cache
|
||||||
|
var entries = Entries;
|
||||||
|
|
||||||
|
entries.TryGetValue(address, out var result);
|
||||||
|
return result ?? Array.Empty<Endpoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, Endpoint[]> Initialize(IReadOnlyList<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
// Collect duplicates as we go, blow up on startup if we find any.
|
||||||
|
var hasDuplicates = false;
|
||||||
|
|
||||||
|
var entries = new Dictionary<string, Endpoint[]>(StringComparer.Ordinal);
|
||||||
|
for (var i = 0; i < endpoints.Count; i++)
|
||||||
|
{
|
||||||
|
var endpoint = endpoints[i];
|
||||||
|
|
||||||
|
var endpointName = GetEndpointName(endpoint);
|
||||||
|
if (endpointName == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entries.TryGetValue(endpointName, out var existing))
|
||||||
|
{
|
||||||
|
// This isn't a duplicate (so far)
|
||||||
|
entries[endpointName] = new[] { endpoint };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok this is a duplicate, because we have two endpoints with the same name. Bail out, because we
|
||||||
|
// are just going to throw, we don't need to finish collecting data.
|
||||||
|
hasDuplicates = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDuplicates)
|
||||||
|
{
|
||||||
|
// No duplicates, success!
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK we need to report some duplicates.
|
||||||
|
var duplicates = endpoints
|
||||||
|
.GroupBy(e => GetEndpointName(e))
|
||||||
|
.Where(g => g.Key != null)
|
||||||
|
.Where(g => g.Count() > 1);
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine(Resources.DuplicateEndpointNameHeader);
|
||||||
|
|
||||||
|
foreach (var group in duplicates)
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine(Resources.FormatDuplicateEndpointNameEntry(group.Key));
|
||||||
|
|
||||||
|
foreach (var endpoint in group)
|
||||||
|
{
|
||||||
|
builder.AppendLine(endpoint.DisplayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(builder.ToString());
|
||||||
|
|
||||||
|
string GetEndpointName(Endpoint endpoint)
|
||||||
|
{
|
||||||
|
if (endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>() != null)
|
||||||
|
{
|
||||||
|
// Skip anything that's suppressed for linking.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies an endpoint name in <see cref="Endpoint.Metadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Endpoint names must be unique within an application, and can be used to unambiguously
|
||||||
|
/// identify a desired endpoint for URI generation using <see cref="LinkGenerator"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public class EndpointNameMetadata : IEndpointNameMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="EndpointNameMetadata"/> with the provided endpoint name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpointName">The endpoint name.</param>
|
||||||
|
public EndpointNameMetadata(string endpointName)
|
||||||
|
{
|
||||||
|
if (endpointName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpointName));
|
||||||
|
}
|
||||||
|
|
||||||
|
EndpointName = endpointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the endpoint name.
|
||||||
|
/// </summary>
|
||||||
|
public string EndpointName { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a contract use to specify an endpoint name in <see cref="Endpoint.Metadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Endpoint names must be unique within an application, and can be used to unambiguously
|
||||||
|
/// identify a desired endpoint for URI generation using <see cref="LinkGenerator"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public interface IEndpointNameMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the endpoint name.
|
||||||
|
/// </summary>
|
||||||
|
string EndpointName { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for using <see cref="LinkGenerator"/> with and endpoint name.
|
||||||
|
/// </summary>
|
||||||
|
public static class LinkGeneratorEndpointNameAddressExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||||
|
public static string GetPathByName(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
HttpContext httpContext,
|
||||||
|
string endpointName,
|
||||||
|
object values,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpointName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.GetPathByAddress<string>(httpContext, endpointName, new RouteValueDictionary(values), fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||||
|
public static string GetPathByName(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string endpointName,
|
||||||
|
object values,
|
||||||
|
PathString pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpointName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.GetPathByAddress<string>(endpointName, new RouteValueDictionary(values), pathBase, fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||||
|
public static string GetUriByName(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
HttpContext httpContext,
|
||||||
|
string endpointName,
|
||||||
|
object values,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpointName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.GetUriByAddress<string>(httpContext, endpointName, new RouteValueDictionary(values), fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
|
||||||
|
/// <param name="host">The URI host/authority, applied to the resulting URI.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>An absolute URI, or <c>null</c>.</returns>
|
||||||
|
public static string GetUriByName(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string endpointName,
|
||||||
|
object values,
|
||||||
|
string scheme,
|
||||||
|
HostString host,
|
||||||
|
PathString pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpointName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.GetUriByAddress<string>(endpointName, new RouteValueDictionary(values), scheme, host, pathBase, fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> based on the provided <paramref name="endpointName"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="endpointName">The endpoint name. Used to resolve endpoints. Optional.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="LinkGenerationTemplate"/> if one or more endpoints matching the address can be found, otherwise <c>null</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static LinkGenerationTemplate GetTemplateByName(this LinkGenerator generator, string endpointName)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpointName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.GetTemplateByAddress<string>(endpointName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,19 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class LinkGeneratorRouteValuesAddressExtensions
|
public static class LinkGeneratorRouteValuesAddressExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
|
||||||
|
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||||
public static string GetPathByRouteValues(
|
public static string GetPathByRouteValues(
|
||||||
this LinkGenerator generator,
|
this LinkGenerator generator,
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
|
|
@ -28,6 +41,19 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return generator.GetPathByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
|
return generator.GetPathByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
|
||||||
|
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||||
public static string GetPathByRouteValues(
|
public static string GetPathByRouteValues(
|
||||||
this LinkGenerator generator,
|
this LinkGenerator generator,
|
||||||
string routeName,
|
string routeName,
|
||||||
|
|
@ -45,6 +71,19 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
|
return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
|
||||||
|
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||||
public static string GetUriByRouteValues(
|
public static string GetUriByRouteValues(
|
||||||
this LinkGenerator generator,
|
this LinkGenerator generator,
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
|
|
@ -62,6 +101,21 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return generator.GetUriByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
|
return generator.GetUriByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
|
||||||
|
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
|
||||||
|
/// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
|
||||||
|
/// <param name="host">The URI host/authority, applied to the resulting URI.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>An absolute URI, or <c>null</c>.</returns>
|
||||||
public static string GetUriByRouteValues(
|
public static string GetUriByRouteValues(
|
||||||
this LinkGenerator generator,
|
this LinkGenerator generator,
|
||||||
string routeName,
|
string routeName,
|
||||||
|
|
@ -81,7 +135,29 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
|
return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> based on the provided <paramref name="routeName"/> and <paramref name="values"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
|
||||||
|
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="LinkGenerationTemplate"/> if one or more endpoints matching the address can be found, otherwise <c>null</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static LinkGenerationTemplate GetTemplateByRouteValues(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string routeName,
|
||||||
|
object values)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext: null, routeName, values);
|
||||||
|
return generator.GetTemplateByAddress<RouteValuesAddress>(address);
|
||||||
|
}
|
||||||
|
|
||||||
private static RouteValuesAddress CreateAddress(HttpContext httpContext, string routeName, object values)
|
private static RouteValuesAddress CreateAddress(HttpContext httpContext, string routeName, object values)
|
||||||
{
|
{
|
||||||
return new RouteValuesAddress()
|
return new RouteValuesAddress()
|
||||||
|
|
|
||||||
|
|
@ -459,7 +459,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ConstraintMustBeStringOrConstraint"), p0, p1, p2);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ConstraintMustBeStringOrConstraint"), p0, p1, p2);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid constraint '{0}'. A constraint must be of type 'string', '{1}', or '{2}'.
|
/// Invalid constraint '{0}'. A constraint must be of type 'string' or '{1}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string RoutePattern_InvalidConstraintReference
|
internal static string RoutePattern_InvalidConstraintReference
|
||||||
{
|
{
|
||||||
|
|
@ -514,6 +514,34 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
internal static string FormatRoutePattern_InvalidStringConstraintReference(object p0, object p1, object p2, object p3)
|
internal static string FormatRoutePattern_InvalidStringConstraintReference(object p0, object p1, object p2, object p3)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("RoutePattern_InvalidStringConstraintReference"), p0, p1, p2, p3);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("RoutePattern_InvalidStringConstraintReference"), p0, p1, p2, p3);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Endpoints with endpoint name '{0}':
|
||||||
|
/// </summary>
|
||||||
|
internal static string DuplicateEndpointNameEntry
|
||||||
|
{
|
||||||
|
get => GetString("DuplicateEndpointNameEntry");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Endpoints with endpoint name '{0}':
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatDuplicateEndpointNameEntry(object p0)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateEndpointNameEntry"), p0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The following endpoints with a duplicate endpoint name were found.
|
||||||
|
/// </summary>
|
||||||
|
internal static string DuplicateEndpointNameHeader
|
||||||
|
{
|
||||||
|
get => GetString("DuplicateEndpointNameHeader");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The following endpoints with a duplicate endpoint name were found.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatDuplicateEndpointNameHeader()
|
||||||
|
=> GetString("DuplicateEndpointNameHeader");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -225,4 +225,10 @@
|
||||||
<data name="RoutePattern_InvalidStringConstraintReference" xml:space="preserve">
|
<data name="RoutePattern_InvalidStringConstraintReference" xml:space="preserve">
|
||||||
<value>Invalid constraint type '{0}' registered as '{1}'. A constraint type must either implement '{2}', or inherit from '{3}'.</value>
|
<value>Invalid constraint type '{0}' registered as '{1}'. A constraint type must either implement '{2}', or inherit from '{3}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DuplicateEndpointNameEntry" xml:space="preserve">
|
||||||
|
<value>Endpoints with endpoint name '{0}':</value>
|
||||||
|
</data>
|
||||||
|
<data name="DuplicateEndpointNameHeader" xml:space="preserve">
|
||||||
|
<value>The following endpoints with a duplicate endpoint name were found.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -6,33 +6,27 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
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.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
using Microsoft.AspNetCore.Routing.Tree;
|
using Microsoft.AspNetCore.Routing.Tree;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
internal class RouteValuesBasedEndpointFinder : IEndpointFinder<RouteValuesAddress>
|
internal class RouteValuesBasedEndpointFinder : IEndpointFinder<RouteValuesAddress>
|
||||||
{
|
{
|
||||||
private readonly CompositeEndpointDataSource _endpointDataSource;
|
private readonly CompositeEndpointDataSource _dataSource;
|
||||||
private readonly ObjectPool<UriBuildingContext> _objectPool;
|
|
||||||
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
|
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
|
||||||
private IDictionary<string, List<OutboundMatchResult>> _namedMatchResults;
|
private IDictionary<string, List<OutboundMatchResult>> _namedMatchResults;
|
||||||
|
|
||||||
public RouteValuesBasedEndpointFinder(
|
public RouteValuesBasedEndpointFinder(CompositeEndpointDataSource dataSource)
|
||||||
CompositeEndpointDataSource endpointDataSource,
|
|
||||||
ObjectPool<UriBuildingContext> objectPool)
|
|
||||||
{
|
{
|
||||||
_endpointDataSource = endpointDataSource;
|
_dataSource = dataSource;
|
||||||
_objectPool = objectPool;
|
|
||||||
|
|
||||||
// Build initial matches
|
// Build initial matches
|
||||||
BuildOutboundMatches();
|
BuildOutboundMatches();
|
||||||
|
|
||||||
// Register for changes in endpoints
|
// Register for changes in endpoints
|
||||||
Extensions.Primitives.ChangeToken.OnChange(
|
Extensions.Primitives.ChangeToken.OnChange(
|
||||||
_endpointDataSource.GetChangeToken,
|
_dataSource.GetChangeToken,
|
||||||
HandleChange);
|
HandleChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +62,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
// re-register the callback as the change token is one time use only and a new change token
|
// re-register the callback as the change token is one time use only and a new change token
|
||||||
// is produced every time
|
// is produced every time
|
||||||
Extensions.Primitives.ChangeToken.OnChange(
|
Extensions.Primitives.ChangeToken.OnChange(
|
||||||
_endpointDataSource.GetChangeToken,
|
_dataSource.GetChangeToken,
|
||||||
HandleChange);
|
HandleChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +98,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var namedOutboundMatchResults = new Dictionary<string, List<OutboundMatchResult>>(
|
var namedOutboundMatchResults = new Dictionary<string, List<OutboundMatchResult>>(
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var endpoints = _endpointDataSource.Endpoints.OfType<RouteEndpoint>();
|
var endpoints = _dataSource.Endpoints.OfType<RouteEndpoint>();
|
||||||
foreach (var endpoint in endpoints)
|
foreach (var endpoint in endpoints)
|
||||||
{
|
{
|
||||||
// Do not consider an endpoint for link generation if the following marker metadata is on it
|
// Do not consider an endpoint for link generation if the following marker metadata is on it
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
public class EndpointNameEndpointFinderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void EndpointFinder_Match_ReturnsMatchingEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/a",
|
||||||
|
metadata: new object[] { new EndpointNameMetadata("name1"), });
|
||||||
|
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/b",
|
||||||
|
metadata: new object[] { new EndpointNameMetadata("name2"), });
|
||||||
|
|
||||||
|
var finder = CreateEndpointFinder(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpoints = finder.FindEndpoints("name2");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
endpoints,
|
||||||
|
e => Assert.Same(endpoint2, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EndpointFinder_NoMatch_ReturnsEmptyCollection()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/a",
|
||||||
|
metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
|
||||||
|
|
||||||
|
var finder = CreateEndpointFinder(endpoint);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpoints = finder.FindEndpoints("name2");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EndpointFinder_NoMatch_CaseSensitive()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/a",
|
||||||
|
metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
|
||||||
|
|
||||||
|
var finder = CreateEndpointFinder(endpoint);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpoints = finder.FindEndpoints("NAME1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EndpointFinder_UpdatesWhenDataSourceChanges()
|
||||||
|
{
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/a",
|
||||||
|
metadata: new object[] { new EndpointNameMetadata("name1"), });
|
||||||
|
var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var finder = CreateEndpointFinder(dynamicDataSource);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
var match = Assert.Single(finder.Entries);
|
||||||
|
Assert.Same(endpoint1, match.Value.Single());
|
||||||
|
|
||||||
|
// Arrange 2
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/b",
|
||||||
|
metadata: new object[] { new EndpointNameMetadata("name2"), });
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
// Trigger change
|
||||||
|
dynamicDataSource.AddEndpoint(endpoint2);
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.Collection(
|
||||||
|
finder.Entries.OrderBy(kvp => kvp.Key),
|
||||||
|
(m) =>
|
||||||
|
{
|
||||||
|
Assert.Same(endpoint1, m.Value.Single());
|
||||||
|
},
|
||||||
|
(m) =>
|
||||||
|
{
|
||||||
|
Assert.Same(endpoint2, m.Value.Single());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EndpointFinder_IgnoresEndpointsWithSuppressLinkGeneration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/a",
|
||||||
|
metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var finder = CreateEndpointFinder(endpoint);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(finder.Entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EndpointFinder_IgnoresEndpointsWithoutEndpointName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"/a",
|
||||||
|
metadata: new object[] { });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var finder = CreateEndpointFinder(endpoint);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(finder.Entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EndpointFinder_ThrowsExceptionForDuplicateEndpoints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new Endpoint[]
|
||||||
|
{
|
||||||
|
EndpointFactory.CreateRouteEndpoint("/a", displayName: "a", metadata: new object[] { new EndpointNameMetadata("name1"), }),
|
||||||
|
EndpointFactory.CreateRouteEndpoint("/b", displayName: "b", metadata: new object[] { new EndpointNameMetadata("name1"), }),
|
||||||
|
EndpointFactory.CreateRouteEndpoint("/c", displayName: "c", metadata: new object[] { new EndpointNameMetadata("name1"), }),
|
||||||
|
|
||||||
|
//// Not a duplicate
|
||||||
|
EndpointFactory.CreateRouteEndpoint("/d", displayName: "d", metadata: new object[] { new EndpointNameMetadata("NAME1"), }),
|
||||||
|
|
||||||
|
EndpointFactory.CreateRouteEndpoint("/e", displayName: "e", metadata: new object[] { new EndpointNameMetadata("name2"), }),
|
||||||
|
EndpointFactory.CreateRouteEndpoint("/f", displayName: "f", metadata: new object[] { new EndpointNameMetadata("name2"), }),
|
||||||
|
};
|
||||||
|
|
||||||
|
var finder = CreateEndpointFinder(endpoints);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => finder.FindEndpoints("any name"));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(@"The following endpoints with a duplicate endpoint name were found.
|
||||||
|
|
||||||
|
Endpoints with endpoint name 'name1':
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
|
||||||
|
Endpoints with endpoint name 'name2':
|
||||||
|
e
|
||||||
|
f
|
||||||
|
", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EndpointNameEndpointFinder CreateEndpointFinder(params Endpoint[] endpoints)
|
||||||
|
{
|
||||||
|
return CreateEndpointFinder(new DefaultEndpointDataSource(endpoints));
|
||||||
|
}
|
||||||
|
|
||||||
|
private EndpointNameEndpointFinder CreateEndpointFinder(params EndpointDataSource[] dataSources)
|
||||||
|
{
|
||||||
|
return new EndpointNameEndpointFinder(new CompositeEndpointDataSource(dataSources));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
// Integration tests for GetXyzByName. These are basic because important behavioral details
|
||||||
|
// are covered elsewhere.
|
||||||
|
//
|
||||||
|
// Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
|
||||||
|
// and DefaultLinkGeneratorProcessTemplateTest
|
||||||
|
//
|
||||||
|
// Does not cover the EndpointNameEndpointFinder in detail. see EndpointNameEndpointFinderTest
|
||||||
|
public class LinkGeneratorEndpointNameExtensionsTest : LinkGeneratorTestBase
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByName_WithoutHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var values = new { p = "In?dex", query = "some?query", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByName(
|
||||||
|
endpointName: "name2",
|
||||||
|
values,
|
||||||
|
new PathString("/Foo/Bar?encodeme?"),
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByName_WithHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext();
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
var values = new { p = "In?dex", query = "some?query", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByName(
|
||||||
|
httpContext,
|
||||||
|
endpointName: "name2",
|
||||||
|
values,
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUriByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var values = new { p = "In?dex", query = "some?query", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetUriByName(
|
||||||
|
endpointName: "name2",
|
||||||
|
values,
|
||||||
|
"http",
|
||||||
|
new HostString("example.com"),
|
||||||
|
new PathString("/Foo/Bar?encodeme?"),
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUriByName_WithHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext();
|
||||||
|
httpContext.Request.Scheme = "http";
|
||||||
|
httpContext.Request.Host = new HostString("example.com");
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
var values = new { p = "In?dex", query = "some?query", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var uri = linkGenerator.GetUriByName(
|
||||||
|
httpContext,
|
||||||
|
endpointName: "name2",
|
||||||
|
values,
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetTemplateByName_CreatesTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var template = linkGenerator.GetTemplateByName(endpointName: "name2");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(template);
|
||||||
|
Assert.Collection(
|
||||||
|
Assert.IsType<DefaultLinkGenerationTemplate>(template).Endpoints.Cast<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText),
|
||||||
|
e => Assert.Same(endpoint2, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -96,7 +97,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetUri_WithHttpContext_WithPathBaseAndFragment()
|
public void GetUriByRouteValues_WithHttpContext_WithPathBaseAndFragment()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint(
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
|
@ -124,5 +125,31 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", uri);
|
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetTemplateByRouteValues_CreatesTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"{controller}/{action}/{id}",
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "In?dex", })) });
|
||||||
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"{controller}/{action}/{id?}",
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "In?dex", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var template = linkGenerator.GetTemplateByRouteValues(
|
||||||
|
routeName: null,
|
||||||
|
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(template);
|
||||||
|
Assert.Collection(
|
||||||
|
Assert.IsType<DefaultLinkGenerationTemplate>(template).Endpoints.Cast<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText),
|
||||||
|
e => Assert.Same(endpoint2, e),
|
||||||
|
e => Assert.Same(endpoint1, e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||||
using Microsoft.AspNetCore.Routing.Tree;
|
using Microsoft.AspNetCore.Routing.Tree;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
@ -83,13 +82,9 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
// Arrange 1
|
// Arrange 1
|
||||||
var endpoint1 = CreateEndpoint("/a");
|
var endpoint1 = CreateEndpoint("/a");
|
||||||
var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
|
var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
|
||||||
var objectPoolProvider = new DefaultObjectPoolProvider();
|
|
||||||
var objectPool = objectPoolProvider.Create(new UriBuilderContextPooledObjectPolicy());
|
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
var finder = new CustomRouteValuesBasedEndpointFinder(
|
var finder = new CustomRouteValuesBasedEndpointFinder(new CompositeEndpointDataSource(new[] { dynamicDataSource }));
|
||||||
new CompositeEndpointDataSource(new[] { dynamicDataSource }),
|
|
||||||
objectPool);
|
|
||||||
|
|
||||||
// Assert 1
|
// Assert 1
|
||||||
Assert.NotNull(finder.AllMatches);
|
Assert.NotNull(finder.AllMatches);
|
||||||
|
|
@ -218,14 +213,9 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return CreateEndpointFinder(new DefaultEndpointDataSource(endpoints));
|
return CreateEndpointFinder(new DefaultEndpointDataSource(endpoints));
|
||||||
}
|
}
|
||||||
|
|
||||||
private CustomRouteValuesBasedEndpointFinder CreateEndpointFinder(params EndpointDataSource[] endpointDataSources)
|
private CustomRouteValuesBasedEndpointFinder CreateEndpointFinder(params EndpointDataSource[] dataSources)
|
||||||
{
|
{
|
||||||
var objectPoolProvider = new DefaultObjectPoolProvider();
|
return new CustomRouteValuesBasedEndpointFinder(new CompositeEndpointDataSource(dataSources));
|
||||||
var objectPool = objectPoolProvider.Create(new UriBuilderContextPooledObjectPolicy());
|
|
||||||
|
|
||||||
return new CustomRouteValuesBasedEndpointFinder(
|
|
||||||
new CompositeEndpointDataSource(endpointDataSources),
|
|
||||||
objectPool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RouteEndpoint CreateEndpoint(
|
private RouteEndpoint CreateEndpoint(
|
||||||
|
|
@ -256,10 +246,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
private class CustomRouteValuesBasedEndpointFinder : RouteValuesBasedEndpointFinder
|
private class CustomRouteValuesBasedEndpointFinder : RouteValuesBasedEndpointFinder
|
||||||
{
|
{
|
||||||
public CustomRouteValuesBasedEndpointFinder(
|
public CustomRouteValuesBasedEndpointFinder(CompositeEndpointDataSource dataSource)
|
||||||
CompositeEndpointDataSource endpointDataSource,
|
: base(dataSource)
|
||||||
ObjectPool<UriBuildingContext> objectPool)
|
|
||||||
: base(endpointDataSource, objectPool)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue