Add support Endpoint Name endpoint finding

This commit is contained in:
Ryan Nowak 2018-09-06 19:18:55 -07:00
parent 57cc8aea96
commit 5f172efa9d
10 changed files with 728 additions and 3 deletions

View File

@ -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

View File

@ -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;
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}