Add support Endpoint Name endpoint finding
This commit is contained in:
parent
57cc8aea96
commit
5f172efa9d
|
|
@ -68,8 +68,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<DfaGraphWriter>();
|
||||
|
||||
// Link generation related services
|
||||
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
||||
services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
|
||||
services.TryAddSingleton<IEndpointFinder<string>, EndpointNameEndpointFinder>();
|
||||
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
||||
|
||||
//
|
||||
// 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,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 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -459,7 +459,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
=> string.Format(CultureInfo.CurrentCulture, GetString("ConstraintMustBeStringOrConstraint"), p0, p1, p2);
|
||||
|
||||
/// <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>
|
||||
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)
|
||||
=> 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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -225,4 +225,10 @@
|
|||
<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>
|
||||
</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>
|
||||
|
|
@ -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.
|
||||
// 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;
|
||||
|
||||
|
|
@ -96,7 +97,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUri_WithHttpContext_WithPathBaseAndFragment()
|
||||
public void GetUriByRouteValues_WithHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint(
|
||||
|
|
@ -124,5 +125,31 @@ namespace Microsoft.AspNetCore.Routing
|
|||
// Assert
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue