diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs
new file mode 100644
index 0000000000..ec3736228a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Routing
+{
+ ///
+ /// Defines a contract to generate a URL from a template.
+ ///
+ public abstract class LinkGenerationTemplate
+ {
+ ///
+ /// Generates a URL with an absolute path from the specified route values.
+ ///
+ /// An object that contains route values.
+ /// The generated URL.
+ public string MakeUrl(object values)
+ {
+ return MakeUrl(values, options: null);
+ }
+
+ ///
+ /// Generates a URL with an absolute path from the specified route values and link options.
+ ///
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
+ public abstract string MakeUrl(object values, LinkOptions options);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs
index f4101a609a..35b9456bf6 100644
--- a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs
+++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs
@@ -6,78 +6,191 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing
{
+ ///
+ /// Defines a contract to generate URLs to endpoints.
+ ///
public abstract class LinkGenerator
{
+ ///
+ /// Generates a URL with an absolute path from the specified route values.
+ ///
+ /// An object that contains route values.
+ /// The generated URL.
public string GetLink(object values)
{
return GetLink(httpContext: null, routeName: null, values, options: null);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route values and link options.
+ ///
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
public string GetLink(object values, LinkOptions options)
{
return GetLink(httpContext: null, routeName: null, values, options);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route values.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// An object that contains route values.
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLink(object values, out string link)
{
return TryGetLink(httpContext: null, routeName: null, values, options: null, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route values and link options.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLink(object values, LinkOptions options, out string link)
{
return TryGetLink(httpContext: null, routeName: null, values, options, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route values.
+ ///
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The generated URL.
public string GetLink(HttpContext httpContext, object values)
{
return GetLink(httpContext, routeName: null, values, options: null);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route values.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLink(HttpContext httpContext, object values, out string link)
{
return TryGetLink(httpContext, routeName: null, values, options: null, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route values and link options.
+ ///
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
public string GetLink(HttpContext httpContext, object values, LinkOptions options)
{
return GetLink(httpContext, routeName: null, values, options);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route values and link options.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLink(HttpContext httpContext, object values, LinkOptions options, out string link)
{
return TryGetLink(httpContext, routeName: null, values, options, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name and route values.
+ ///
+ /// The name of the route to generate the URL to.
+ /// An object that contains route values.
+ /// The generated URL.
public string GetLink(string routeName, object values)
{
return GetLink(httpContext: null, routeName, values, options: null);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name and route values.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The name of the route to generate the URL to.
+ /// An object that contains route values.
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLink(string routeName, object values, out string link)
{
return TryGetLink(httpContext: null, routeName, values, options: null, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name and route values.
+ ///
+ /// The name of the route to generate the URL to.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
public string GetLink(string routeName, object values, LinkOptions options)
{
return GetLink(httpContext: null, routeName, values, options);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name, route values and link options.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The name of the route to generate the URL to.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLink(string routeName, object values, LinkOptions options, out string link)
{
return TryGetLink(httpContext: null, routeName, values, options, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name and route values.
+ ///
+ /// The name of the route to generate the URL to.
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The generated URL.
public string GetLink(HttpContext httpContext, string routeName, object values)
{
return GetLink(httpContext, routeName, values, options: null);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name and route values.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The associated with current request.
+ /// The name of the route to generate the URL to.
+ /// An object that contains route values.
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLink(HttpContext httpContext, string routeName, object values, out string link)
{
return TryGetLink(httpContext, routeName, values, options: null, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name, route values and link options.
+ ///
+ /// The name of the route to generate the URL to.
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
public string GetLink(HttpContext httpContext, string routeName, object values, LinkOptions options)
{
if (TryGetLink(httpContext, routeName, values, options, out var link))
@@ -88,6 +201,16 @@ namespace Microsoft.AspNetCore.Routing
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
}
+ ///
+ /// Generates a URL with an absolute path from the specified route name, route values and link options.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The associated with current request.
+ /// The name of the route to generate the URL to.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public abstract bool TryGetLink(
HttpContext httpContext,
string routeName,
@@ -95,51 +218,119 @@ namespace Microsoft.AspNetCore.Routing
LinkOptions options,
out string link);
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information and route values.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// An object that contains route values.
+ /// The generated URL.
public string GetLinkByAddress(TAddress address, object values)
{
- return GetLinkByAddress(address, httpContext: null, values, options: null);
+ return GetLinkByAddress(httpContext: null, address, values, options: null);
}
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information and route values.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// An object that contains route values.
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLinkByAddress(TAddress address, object values, out string link)
{
return TryGetLinkByAddress(address, values, options: null, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information, route values and link options.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
public string GetLinkByAddress(TAddress address, object values, LinkOptions options)
{
- return GetLinkByAddress(address, httpContext: null, values, options);
+ return GetLinkByAddress(httpContext: null, address, values, options);
}
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information, route values and link options.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLinkByAddress(
TAddress address,
object values,
LinkOptions options,
out string link)
{
- return TryGetLinkByAddress(address, httpContext: null, values, options, out link);
+ return TryGetLinkByAddress(httpContext: null, address, values, options, out link);
}
- public string GetLinkByAddress(TAddress address, HttpContext httpContext, object values)
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information, route values and link options.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The generated URL.
+ public string GetLinkByAddress(HttpContext httpContext, TAddress address, object values)
{
- return GetLinkByAddress(address, httpContext, values, options: null);
+ return GetLinkByAddress(httpContext, address, values, options: null);
}
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information and route values.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public bool TryGetLinkByAddress(
- TAddress address,
HttpContext httpContext,
+ TAddress address,
object values,
out string link)
{
- return TryGetLinkByAddress(address, httpContext, values, options: null, out link);
+ return TryGetLinkByAddress(httpContext, address, values, options: null, out link);
}
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information, route values and link options.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
public string GetLinkByAddress(
- TAddress address,
HttpContext httpContext,
+ TAddress address,
object values,
LinkOptions options)
{
- if (TryGetLinkByAddress(address, httpContext, values, options, out var link))
+ if (TryGetLinkByAddress(httpContext, address, values, options, out var link))
{
return link;
}
@@ -147,11 +338,126 @@ namespace Microsoft.AspNetCore.Routing
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
}
+ ///
+ /// Generates a URL with an absolute path from the specified lookup information, route values and link options.
+ /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ /// A return value indicates whether the operation succeeded.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for generating a URL.
+ /// The associated with current request.
+ /// An object that contains route values.
+ /// The .
+ /// The generated URL.
+ /// true if a URL was generated successfully; otherwise, false.
public abstract bool TryGetLinkByAddress(
- TAddress address,
HttpContext httpContext,
+ TAddress address,
object values,
LinkOptions options,
out string link);
+
+ ///
+ /// Gets a to generate a URL from the specified route values.
+ /// This template object holds information of the endpoint(s) that were found and which can later be used to
+ /// generate a URL using the api.
+ ///
+ ///
+ /// An object that contains route values. These values are used to lookup endpoint(s).
+ ///
+ ///
+ /// If an endpoint(s) was found successfully, then this returns a template object representing that,
+ /// null otherwise.
+ ///
+ public LinkGenerationTemplate GetTemplate(object values)
+ {
+ return GetTemplate(httpContext: null, routeName: null, values);
+ }
+
+ ///
+ /// Gets a to generate a URL from the specified route name and route values.
+ /// This template object holds information of the endpoint(s) that were found and which can later be used to
+ /// generate a URL using the api.
+ ///
+ /// The name of the route to generate the URL to.
+ ///
+ /// An object that contains route values. These values are used to lookup for endpoint(s).
+ ///
+ ///
+ /// If an endpoint(s) was found successfully, then this returns a template object representing that,
+ /// null otherwise.
+ ///
+ public LinkGenerationTemplate GetTemplate(string routeName, object values)
+ {
+ return GetTemplate(httpContext: null, routeName, values);
+ }
+
+ ///
+ /// Gets a to generate a URL from the specified route values.
+ /// This template object holds information of the endpoint(s) that were found and which can later be used to
+ /// generate a URL using the api.
+ ///
+ /// The associated with current request.
+ ///
+ /// An object that contains route values. These values are used to lookup for endpoint(s).
+ ///
+ ///
+ /// If an endpoint(s) was found successfully, then this returns a template object representing that,
+ /// null otherwise.
+ ///
+ public LinkGenerationTemplate GetTemplate(HttpContext httpContext, object values)
+ {
+ return GetTemplate(httpContext, routeName: null, values);
+ }
+
+ ///
+ /// Gets a to generate a URL from the specified route name and route values.
+ /// This template object holds information of the endpoint(s) that were found and which can later be used to
+ /// generate a URL using the api.
+ ///
+ /// The associated with current request.
+ /// The name of the route to generate the URL to.
+ ///
+ /// An object that contains route values. These values are used to lookup for endpoint(s).
+ ///
+ ///
+ /// If an endpoint(s) was found successfully, then this returns a template object representing that,
+ /// null otherwise.
+ ///
+ public abstract LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values);
+
+ ///
+ /// Gets a to generate a URL from the specified lookup information.
+ /// This template object holds information of the endpoint(s) that were found and which can later be used to
+ /// generate a URL using the api.
+ /// The lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for creating a template.
+ ///
+ /// If an endpoint(s) was found successfully, then this returns a template object representing that,
+ /// null otherwise.
+ ///
+ public LinkGenerationTemplate GetTemplateByAddress(TAddress address)
+ {
+ return GetTemplateByAddress(httpContext: null, address);
+ }
+
+ ///
+ /// Gets a to generate a URL from the specified lookup information.
+ /// This template object holds information of the endpoint(s) that were found and which can later be used to
+ /// generate a URL using the api.
+ /// The lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'.
+ ///
+ /// The address type to look up endpoints.
+ /// The information used to look up endpoints for creating a template.
+ /// The associated with current request.
+ ///
+ /// If an endpoint(s) was found successfully, then this returns a template object representing that,
+ /// null otherwise.
+ ///
+ public abstract LinkGenerationTemplate GetTemplateByAddress(
+ HttpContext httpContext,
+ TAddress address);
}
}
diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs
new file mode 100644
index 0000000000..2b432db71f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs
@@ -0,0 +1,62 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing.Matching;
+
+namespace Microsoft.AspNetCore.Routing
+{
+ internal class DefaultLinkGenerationTemplate : LinkGenerationTemplate
+ {
+ public DefaultLinkGenerationTemplate(
+ DefaultLinkGenerator linkGenerator,
+ IEnumerable endpoints,
+ HttpContext httpContext,
+ RouteValueDictionary explicitValues,
+ RouteValueDictionary ambientValues)
+ {
+ LinkGenerator = linkGenerator;
+ Endpoints = endpoints;
+ HttpContext = httpContext;
+ EarlierExplicitValues = explicitValues;
+ AmbientValues = ambientValues;
+ }
+
+ internal DefaultLinkGenerator LinkGenerator { get; }
+
+ internal IEnumerable Endpoints { get; }
+
+ internal HttpContext HttpContext { get; }
+
+ internal RouteValueDictionary EarlierExplicitValues { get; }
+
+ internal RouteValueDictionary AmbientValues { get; }
+
+ public override string MakeUrl(object values, LinkOptions options)
+ {
+ var currentValues = new RouteValueDictionary(values);
+ var mergedValuesDictionary = new RouteValueDictionary(EarlierExplicitValues);
+
+ foreach (var kvp in currentValues)
+ {
+ mergedValuesDictionary[kvp.Key] = kvp.Value;
+ }
+
+ foreach (var endpoint in Endpoints)
+ {
+ var link = LinkGenerator.MakeLink(
+ HttpContext,
+ endpoint,
+ AmbientValues,
+ mergedValuesDictionary,
+ options);
+ if (link != null)
+ {
+ return link;
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs
index 13d2869d32..d56e7092ea 100644
--- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs
+++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs
@@ -2,6 +2,7 @@
// 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.Encodings.Web;
using Microsoft.AspNetCore.Http;
@@ -54,21 +55,78 @@ namespace Microsoft.AspNetCore.Routing
}
public override bool TryGetLinkByAddress(
- TAddress address,
HttpContext httpContext,
+ TAddress address,
object values,
LinkOptions options,
out string link)
{
return TryGetLinkByAddressInternal(
- address,
httpContext,
+ address,
explicitValues: values,
ambientValues: GetAmbientValues(httpContext),
options,
out link);
}
+ public override LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values)
+ {
+ var ambientValues = GetAmbientValues(httpContext);
+ var explicitValues = new RouteValueDictionary(values);
+
+ return GetTemplateInternal(
+ httpContext,
+ new RouteValuesAddress
+ {
+ RouteName = routeName,
+ ExplicitValues = explicitValues,
+ AmbientValues = ambientValues
+ },
+ ambientValues,
+ explicitValues,
+ values);
+ }
+
+ public override LinkGenerationTemplate GetTemplateByAddress(
+ HttpContext httpContext,
+ TAddress address)
+ {
+ return GetTemplateInternal(httpContext, address, values: null);
+ }
+
+ internal string MakeLink(
+ HttpContext httpContext,
+ MatcherEndpoint endpoint,
+ RouteValueDictionary ambientValues,
+ RouteValueDictionary explicitValues,
+ LinkOptions options)
+ {
+ var templateBinder = new TemplateBinder(
+ UrlEncoder.Default,
+ _uriBuildingContextPool,
+ new RouteTemplate(endpoint.RoutePattern),
+ new RouteValueDictionary(endpoint.RoutePattern.Defaults));
+
+ var templateValuesResult = templateBinder.GetValues(
+ ambientValues: ambientValues,
+ explicitValues: explicitValues,
+ requiredKeys: endpoint.RequiredValues.Keys);
+ if (templateValuesResult == null)
+ {
+ // We're missing one of the required values for this route.
+ return null;
+ }
+
+ if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
+ {
+ return null;
+ }
+
+ var url = templateBinder.BindValues(templateValuesResult.AcceptedValues);
+ return Normalize(url, options);
+ }
+
private bool TryGetLinkByRouteValues(
HttpContext httpContext,
string routeName,
@@ -86,8 +144,8 @@ namespace Microsoft.AspNetCore.Routing
};
return TryGetLinkByAddressInternal(
- address,
httpContext,
+ address,
explicitValues: values,
ambientValues: ambientValues,
options,
@@ -95,8 +153,8 @@ namespace Microsoft.AspNetCore.Routing
}
private bool TryGetLinkByAddressInternal(
- TAddress address,
HttpContext httpContext,
+ TAddress address,
object explicitValues,
RouteValueDictionary ambientValues,
LinkOptions options,
@@ -104,22 +162,21 @@ namespace Microsoft.AspNetCore.Routing
{
link = null;
- var endpointFinder = _serviceProvider.GetRequiredService>();
- var endpoints = endpointFinder.FindEndpoints(address);
+ var endpoints = FindEndpoints(address);
if (endpoints == null)
{
return false;
}
- var matcherEndpoints = endpoints.OfType();
- if (!matcherEndpoints.Any())
+ foreach (var endpoint in endpoints)
{
- return false;
- }
+ link = MakeLink(
+ httpContext,
+ endpoint,
+ ambientValues,
+ new RouteValueDictionary(explicitValues),
+ options);
- foreach (var endpoint in matcherEndpoints)
- {
- link = GetLink(endpoint);
if (link != null)
{
return true;
@@ -127,33 +184,49 @@ namespace Microsoft.AspNetCore.Routing
}
return false;
+ }
- string GetLink(MatcherEndpoint endpoint)
+ private LinkGenerationTemplate GetTemplateInternal(
+ HttpContext httpContext,
+ TAddress address,
+ object values)
+ {
+ var endpoints = FindEndpoints(address);
+ if (endpoints == null)
{
- var templateBinder = new TemplateBinder(
- UrlEncoder.Default,
- _uriBuildingContextPool,
- new RouteTemplate(endpoint.RoutePattern),
- new RouteValueDictionary(endpoint.RoutePattern.Defaults));
-
- var templateValuesResult = templateBinder.GetValues(
- ambientValues: ambientValues,
- explicitValues: new RouteValueDictionary(explicitValues),
- requiredKeys: endpoint.RequiredValues.Keys);
- if (templateValuesResult == null)
- {
- // We're missing one of the required values for this route.
- return null;
- }
-
- if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
- {
- return null;
- }
-
- var url = templateBinder.BindValues(templateValuesResult.AcceptedValues);
- return Normalize(url, options);
+ return null;
}
+
+ var ambientValues = GetAmbientValues(httpContext);
+ var explicitValues = new RouteValueDictionary(values);
+
+ return new DefaultLinkGenerationTemplate(
+ this,
+ endpoints,
+ httpContext,
+ explicitValues,
+ ambientValues);
+ }
+
+ private LinkGenerationTemplate GetTemplateInternal(
+ HttpContext httpContext,
+ TAddress address,
+ RouteValueDictionary ambientValues,
+ RouteValueDictionary explicitValues,
+ object values)
+ {
+ var endpoints = FindEndpoints(address);
+ if (endpoints == null)
+ {
+ return null;
+ }
+
+ return new DefaultLinkGenerationTemplate(
+ this,
+ endpoints,
+ httpContext,
+ explicitValues,
+ ambientValues);
}
private bool MatchesConstraints(
@@ -234,7 +307,25 @@ namespace Microsoft.AspNetCore.Routing
return feature.Values;
}
}
- return null;
+ return new RouteValueDictionary();
+ }
+
+ private IEnumerable FindEndpoints(TAddress address)
+ {
+ var finder = _serviceProvider.GetRequiredService>();
+ var endpoints = finder.FindEndpoints(address);
+ if (endpoints == null)
+ {
+ return null;
+ }
+
+ var matcherEndpoints = endpoints.OfType();
+ if (!matcherEndpoints.Any())
+ {
+ return null;
+ }
+
+ return matcherEndpoints;
}
}
}
diff --git a/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs b/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs
index ba450f1c7b..618220a6e9 100644
--- a/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs
+++ b/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs
@@ -5,8 +5,17 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Routing
{
+ ///
+ /// Defines a contract to find endpoints based on the supplied lookup information.
+ ///
+ /// The address type to look up endpoints.
public interface IEndpointFinder
{
+ ///
+ /// Finds endpoints based on the supplied lookup information.
+ ///
+ /// The information used to look up endpoints.
+ /// A collection of .
IEnumerable FindEndpoints(TAddress address);
}
}
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
index 1063db6819..c226e3edd6 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
@@ -1217,7 +1217,7 @@ namespace Microsoft.AspNetCore.Routing
}
[Fact]
- public void TryGetLink_WithCustomAddress_CanGenerateLink()
+ public void TryGetLinkByAddress_WithCustomAddress_CanGenerateLink()
{
// Arrange
var services = GetBasicServices();
@@ -1237,8 +1237,8 @@ namespace Microsoft.AspNetCore.Routing
// Act
var canGenerateLink = linkGenerator.TryGetLinkByAddress(
- address: new NameMetadata("CustomerDetails"),
httpContext,
+ address: new NameMetadata("CustomerDetails"),
values: new { id = 10 },
out var link);
@@ -1248,7 +1248,7 @@ namespace Microsoft.AspNetCore.Routing
}
[Fact]
- public void TryGetLink_WithCustomAddress_CanGenerateLink_RespectsLinkOptions_SuppliedAtCallSite()
+ public void TryGetLinkByAddress_WithCustomAddress_CanGenerateLink_RespectsLinkOptions_SuppliedAtCallSite()
{
// Arrange
var services = GetBasicServices();
@@ -1268,8 +1268,8 @@ namespace Microsoft.AspNetCore.Routing
// Act
var canGenerateLink = linkGenerator.TryGetLinkByAddress(
- address: new NameMetadata("CustomerDetails"),
httpContext,
+ address: new NameMetadata("CustomerDetails"),
values: new { id = 10 },
new LinkOptions
{
@@ -1282,6 +1282,177 @@ namespace Microsoft.AspNetCore.Routing
Assert.Equal("/customers/details/10", link);
}
+ [Fact]
+ public void GetTemplate_ByRouteValues_ReturnsTemplate()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateMatcherEndpoint(
+ "Product/Edit/{id}",
+ requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null });
+ var linkGenerator = CreateLinkGenerator(endpoint1);
+ var values = new RouteValueDictionary(new { controller = "Product", action = "Edit" });
+
+ // Act
+ var template = linkGenerator.GetTemplate(values);
+
+ // Assert
+ var defaultTemplate = Assert.IsType(template);
+ Assert.Same(linkGenerator, defaultTemplate.LinkGenerator);
+ Assert.Equal(new[] { endpoint1 }, defaultTemplate.Endpoints);
+ Assert.Equal(values, defaultTemplate.EarlierExplicitValues);
+ Assert.Null(defaultTemplate.HttpContext);
+ Assert.Empty(defaultTemplate.AmbientValues);
+ }
+
+ [Fact]
+ public void GetTemplate_ByRouteName_ReturnsTemplate()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateMatcherEndpoint(
+ "Product/Edit/{id}",
+ requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ metadata: new RouteNameMetadata("EditProduct"));
+ var linkGenerator = CreateLinkGenerator(endpoint1);
+
+ // Act
+ var template = linkGenerator.GetTemplate("EditProduct", values: new { });
+
+ // Assert
+ var defaultTemplate = Assert.IsType(template);
+ Assert.Same(linkGenerator, defaultTemplate.LinkGenerator);
+ Assert.Equal(new[] { endpoint1 }, defaultTemplate.Endpoints);
+ Assert.Empty(defaultTemplate.EarlierExplicitValues);
+ Assert.Null(defaultTemplate.HttpContext);
+ Assert.Empty(defaultTemplate.AmbientValues);
+ }
+
+ [Fact]
+ public void GetTemplate_ByRouteName_ReturnsTemplate_WithMultipleEndpoints()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateMatcherEndpoint(
+ "Product/Edit/{id}",
+ requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ metadata: new RouteNameMetadata("default"));
+ var endpoint2 = EndpointFactory.CreateMatcherEndpoint(
+ "Product/Details/{id}",
+ requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ metadata: new RouteNameMetadata("default"));
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ // Act
+ var template = linkGenerator.GetTemplate("default", values: new { });
+
+ // Assert
+ var defaultTemplate = Assert.IsType(template);
+ Assert.Same(linkGenerator, defaultTemplate.LinkGenerator);
+ Assert.Equal(new[] { endpoint1, endpoint2 }, defaultTemplate.Endpoints);
+ Assert.Empty(defaultTemplate.EarlierExplicitValues);
+ Assert.Null(defaultTemplate.HttpContext);
+ Assert.Empty(defaultTemplate.AmbientValues);
+ }
+
+ [Fact]
+ public void GetTemplateByAddress_ByCustomAddress_ReturnsTemplate()
+ {
+ // Arrange
+ var services = GetBasicServices();
+ services.TryAddEnumerable(
+ ServiceDescriptor.Singleton, EndpointFinderByName>());
+ var endpoint1 = EndpointFactory.CreateMatcherEndpoint(
+ "Product/Edit/{id}",
+ requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null });
+ var endpoint2 = EndpointFactory.CreateMatcherEndpoint(
+ "Customers/Details/{id}",
+ requiredValues: new { controller = "Customers", action = "Details" },
+ defaults: new { controller = "Customers", action = "Details" },
+ metadata: new NameMetadata("CustomerDetails"));
+ var linkGenerator = CreateLinkGenerator(new[] { endpoint1, endpoint2 }, new RouteOptions(), services);
+
+ // Act
+ var template = linkGenerator.GetTemplateByAddress(new NameMetadata("CustomerDetails"));
+
+ // Assert
+ var defaultTemplate = Assert.IsType(template);
+ Assert.Same(linkGenerator, defaultTemplate.LinkGenerator);
+ Assert.Equal(new[] { endpoint2 }, defaultTemplate.Endpoints);
+ Assert.Empty(defaultTemplate.EarlierExplicitValues);
+ Assert.Null(defaultTemplate.HttpContext);
+ Assert.Empty(defaultTemplate.AmbientValues);
+ }
+
+ [Fact]
+ public void MakeUrl_Honors_LinkOptions()
+ {
+ // Arrange
+ var services = GetBasicServices();
+ services.TryAddEnumerable(
+ ServiceDescriptor.Singleton, EndpointFinderByName>());
+ var endpoint1 = EndpointFactory.CreateMatcherEndpoint(
+ "Product/Edit/{id}",
+ requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null });
+ var endpoint2 = EndpointFactory.CreateMatcherEndpoint(
+ "Customers/Details/{id}",
+ requiredValues: new { controller = "Customers", action = "Details" },
+ defaults: new { controller = "Customers", action = "Details" },
+ metadata: new NameMetadata("CustomerDetails"));
+ var linkGenerator = CreateLinkGenerator(new[] { endpoint1, endpoint2 }, new RouteOptions(), services);
+
+ // Act1
+ var template = linkGenerator.GetTemplateByAddress(new NameMetadata("CustomerDetails"));
+
+ // Assert1
+ Assert.NotNull(template);
+
+ // Act2
+ var link = template.MakeUrl(new { id = 10 }, new LinkOptions { LowercaseUrls = true });
+
+ // Assert2
+ Assert.Equal("/customers/details/10", link);
+
+ // Act3
+ link = template.MakeUrl(new { id = 25 });
+
+ // Assert3
+ Assert.Equal("/Customers/Details/25", link);
+ }
+
+ [Fact]
+ public void MakeUrl_GeneratesLink_WithExtraRouteValues()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateMatcherEndpoint(
+ "Product/Edit/{id}",
+ requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null },
+ defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null });
+ var linkGenerator = CreateLinkGenerator(endpoint1);
+
+ // Act1
+ var template = linkGenerator.GetTemplate(
+ values: new { controller = "Product", action = "Edit", foo = "bar" });
+
+ // Assert1
+ Assert.NotNull(template);
+
+ // Act2
+ var link = template.MakeUrl(new { id = 10 });
+
+ // Assert2
+ Assert.Equal("/Product/Edit/10?foo=bar", link);
+
+ // Act3
+ link = template.MakeUrl(new { id = 25, foo = "boo" });
+
+ // Assert3
+ Assert.Equal("/Product/Edit/25?foo=boo", link);
+ }
+
private LinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
{
return CreateLinkGenerator(endpoints, routeOptions: null);
@@ -1365,5 +1536,14 @@ namespace Microsoft.AspNetCore.Routing
}
public string Name { get; }
}
+
+ private class RouteNameMetadata : IRouteNameMetadata
+ {
+ public RouteNameMetadata(string name)
+ {
+ Name = name;
+ }
+ public string Name { get; }
+ }
}
}