Created LinkGenerationTemplate and friends

This commit is contained in:
Kiran Challa 2018-08-02 07:43:11 -07:00 committed by Kiran Challa
parent b177ba5309
commit c8946a40e4
6 changed files with 729 additions and 52 deletions

View File

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Defines a contract to generate a URL from a template.
/// </summary>
public abstract class LinkGenerationTemplate
{
/// <summary>
/// Generates a URL with an absolute path from the specified route values.
/// </summary>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public string MakeUrl(object values)
{
return MakeUrl(values, options: null);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values and link options.
/// </summary>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <returns>The generated URL.</returns>
public abstract string MakeUrl(object values, LinkOptions options);
}
}

View File

@ -6,78 +6,191 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Defines a contract to generate URLs to endpoints.
/// </summary>
public abstract class LinkGenerator
{
/// <summary>
/// Generates a URL with an absolute path from the specified route values.
/// </summary>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public string GetLink(object values)
{
return GetLink(httpContext: null, routeName: null, values, options: null);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values and link options.
/// </summary>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <returns>The generated URL.</returns>
public string GetLink(object values, LinkOptions options)
{
return GetLink(httpContext: null, routeName: null, values, options);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="values">An object that contains route values.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLink(object values, out string link)
{
return TryGetLink(httpContext: null, routeName: null, values, options: null, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values and link options.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLink(object values, LinkOptions options, out string link)
{
return TryGetLink(httpContext: null, routeName: null, values, options, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public string GetLink(HttpContext httpContext, object values)
{
return GetLink(httpContext, routeName: null, values, options: null);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLink(HttpContext httpContext, object values, out string link)
{
return TryGetLink(httpContext, routeName: null, values, options: null, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values and link options.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <returns>The generated URL.</returns>
public string GetLink(HttpContext httpContext, object values, LinkOptions options)
{
return GetLink(httpContext, routeName: null, values, options);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route values and link options.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLink(HttpContext httpContext, object values, LinkOptions options, out string link)
{
return TryGetLink(httpContext, routeName: null, values, options, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name and route values.
/// </summary>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public string GetLink(string routeName, object values)
{
return GetLink(httpContext: null, routeName, values, options: null);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name and route values.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLink(string routeName, object values, out string link)
{
return TryGetLink(httpContext: null, routeName, values, options: null, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name and route values.
/// </summary>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <returns>The generated URL.</returns>
public string GetLink(string routeName, object values, LinkOptions options)
{
return GetLink(httpContext: null, routeName, values, options);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name, route values and link options.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLink(string routeName, object values, LinkOptions options, out string link)
{
return TryGetLink(httpContext: null, routeName, values, options, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name and route values.
/// </summary>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public string GetLink(HttpContext httpContext, string routeName, object values)
{
return GetLink(httpContext, routeName, values, options: null);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name and route values.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLink(HttpContext httpContext, string routeName, object values, out string link)
{
return TryGetLink(httpContext, routeName, values, options: null, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name, route values and link options.
/// </summary>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <returns>The generated URL.</returns>
public string GetLink(HttpContext httpContext, string routeName, object values, LinkOptions options)
{
if (TryGetLink(httpContext, routeName, values, options, out var link))
@ -88,6 +201,16 @@ namespace Microsoft.AspNetCore.Routing
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
}
/// <summary>
/// Generates a URL with an absolute path from the specified route name, route values and link options.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public abstract bool TryGetLink(
HttpContext httpContext,
string routeName,
@ -95,51 +218,119 @@ namespace Microsoft.AspNetCore.Routing
LinkOptions options,
out string link);
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information and route values.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public string GetLinkByAddress<TAddress>(TAddress address, object values)
{
return GetLinkByAddress(address, httpContext: null, values, options: null);
return GetLinkByAddress(httpContext: null, address, values, options: null);
}
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information and route values.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLinkByAddress<TAddress>(TAddress address, object values, out string link)
{
return TryGetLinkByAddress(address, values, options: null, out link);
}
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <returns>The generated URL.</returns>
public string GetLinkByAddress<TAddress>(TAddress address, object values, LinkOptions options)
{
return GetLinkByAddress(address, httpContext: null, values, options);
return GetLinkByAddress(httpContext: null, address, values, options);
}
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLinkByAddress<TAddress>(
TAddress address,
object values,
LinkOptions options,
out string link)
{
return TryGetLinkByAddress(address, httpContext: null, values, options, out link);
return TryGetLinkByAddress(httpContext: null, address, values, options, out link);
}
public string GetLinkByAddress<TAddress>(TAddress address, HttpContext httpContext, object values)
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public string GetLinkByAddress<TAddress>(HttpContext httpContext, TAddress address, object values)
{
return GetLinkByAddress(address, httpContext, values, options: null);
return GetLinkByAddress(httpContext, address, values, options: null);
}
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information and route values.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public bool TryGetLinkByAddress<TAddress>(
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);
}
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <returns>The generated URL.</returns>
public string GetLinkByAddress<TAddress>(
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.");
}
/// <summary>
/// Generates a URL with an absolute path from the specified lookup information, route values and link options.
/// This lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for generating a URL.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="options">The <see cref="LinkOptions"/>.</param>
/// <param name="link">The generated URL.</param>
/// <returns><c>true</c> if a URL was generated successfully; otherwise, <c>false</c>.</returns>
public abstract bool TryGetLinkByAddress<TAddress>(
TAddress address,
HttpContext httpContext,
TAddress address,
object values,
LinkOptions options,
out string link);
/// <summary>
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route values.
/// This template object holds information of the endpoint(s) that were found and which can later be used to
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
/// </summary>
/// <param name="values">
/// An object that contains route values. These values are used to lookup endpoint(s).
/// </param>
/// <returns>
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
/// <c>null</c> otherwise.
/// </returns>
public LinkGenerationTemplate GetTemplate(object values)
{
return GetTemplate(httpContext: null, routeName: null, values);
}
/// <summary>
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route name and route values.
/// This template object holds information of the endpoint(s) that were found and which can later be used to
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
/// </summary>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">
/// An object that contains route values. These values are used to lookup for endpoint(s).
/// </param>
/// <returns>
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
/// <c>null</c> otherwise.
/// </returns>
public LinkGenerationTemplate GetTemplate(string routeName, object values)
{
return GetTemplate(httpContext: null, routeName, values);
}
/// <summary>
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route values.
/// This template object holds information of the endpoint(s) that were found and which can later be used to
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="values">
/// An object that contains route values. These values are used to lookup for endpoint(s).
/// </param>
/// <returns>
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
/// <c>null</c> otherwise.
/// </returns>
public LinkGenerationTemplate GetTemplate(HttpContext httpContext, object values)
{
return GetTemplate(httpContext, routeName: null, values);
}
/// <summary>
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified route name and route values.
/// This template object holds information of the endpoint(s) that were found and which can later be used to
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <param name="routeName">The name of the route to generate the URL to.</param>
/// <param name="values">
/// An object that contains route values. These values are used to lookup for endpoint(s).
/// </param>
/// <returns>
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
/// <c>null</c> otherwise.
/// </returns>
public abstract LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values);
/// <summary>
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified lookup information.
/// This template object holds information of the endpoint(s) that were found and which can later be used to
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
/// The lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for creating a template.</param>
/// <returns>
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
/// <c>null</c> otherwise.
/// </returns>
public LinkGenerationTemplate GetTemplateByAddress<TAddress>(TAddress address)
{
return GetTemplateByAddress(httpContext: null, address);
}
/// <summary>
/// Gets a <see cref="LinkGenerationTemplate"/> to generate a URL from the specified lookup information.
/// This template object holds information of the endpoint(s) that were found and which can later be used to
/// generate a URL using the <see cref="LinkGenerationTemplate.MakeUrl(object, LinkOptions)"/> api.
/// The lookup information is used to find endpoints using a registered 'IEndpointFinder&lt;TAddress&gt;'.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
/// <param name="address">The information used to look up endpoints for creating a template.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with current request.</param>
/// <returns>
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
/// <c>null</c> otherwise.
/// </returns>
public abstract LinkGenerationTemplate GetTemplateByAddress<TAddress>(
HttpContext httpContext,
TAddress address);
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
namespace Microsoft.AspNetCore.Routing
{
internal class DefaultLinkGenerationTemplate : LinkGenerationTemplate
{
public DefaultLinkGenerationTemplate(
DefaultLinkGenerator linkGenerator,
IEnumerable<MatcherEndpoint> endpoints,
HttpContext httpContext,
RouteValueDictionary explicitValues,
RouteValueDictionary ambientValues)
{
LinkGenerator = linkGenerator;
Endpoints = endpoints;
HttpContext = httpContext;
EarlierExplicitValues = explicitValues;
AmbientValues = ambientValues;
}
internal DefaultLinkGenerator LinkGenerator { get; }
internal IEnumerable<MatcherEndpoint> Endpoints { get; }
internal HttpContext HttpContext { get; }
internal RouteValueDictionary EarlierExplicitValues { get; }
internal RouteValueDictionary AmbientValues { get; }
public override string MakeUrl(object values, LinkOptions options)
{
var currentValues = new RouteValueDictionary(values);
var mergedValuesDictionary = new RouteValueDictionary(EarlierExplicitValues);
foreach (var kvp in currentValues)
{
mergedValuesDictionary[kvp.Key] = kvp.Value;
}
foreach (var endpoint in Endpoints)
{
var link = LinkGenerator.MakeLink(
HttpContext,
endpoint,
AmbientValues,
mergedValuesDictionary,
options);
if (link != null)
{
return link;
}
}
return null;
}
}
}

View File

@ -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>(
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<TAddress>(
HttpContext httpContext,
TAddress address)
{
return GetTemplateInternal(httpContext, address, values: null);
}
internal string MakeLink(
HttpContext httpContext,
MatcherEndpoint endpoint,
RouteValueDictionary ambientValues,
RouteValueDictionary explicitValues,
LinkOptions options)
{
var templateBinder = new TemplateBinder(
UrlEncoder.Default,
_uriBuildingContextPool,
new RouteTemplate(endpoint.RoutePattern),
new RouteValueDictionary(endpoint.RoutePattern.Defaults));
var 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>(
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<IEndpointFinder<TAddress>>();
var endpoints = endpointFinder.FindEndpoints(address);
var endpoints = FindEndpoints(address);
if (endpoints == null)
{
return false;
}
var matcherEndpoints = endpoints.OfType<MatcherEndpoint>();
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<TAddress>(
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<TAddress>(
HttpContext httpContext,
TAddress address,
RouteValueDictionary ambientValues,
RouteValueDictionary explicitValues,
object values)
{
var endpoints = FindEndpoints(address);
if (endpoints == null)
{
return null;
}
return new DefaultLinkGenerationTemplate(
this,
endpoints,
httpContext,
explicitValues,
ambientValues);
}
private bool MatchesConstraints(
@ -234,7 +307,25 @@ namespace Microsoft.AspNetCore.Routing
return feature.Values;
}
}
return null;
return new RouteValueDictionary();
}
private IEnumerable<MatcherEndpoint> FindEndpoints<TAddress>(TAddress address)
{
var finder = _serviceProvider.GetRequiredService<IEndpointFinder<TAddress>>();
var endpoints = finder.FindEndpoints(address);
if (endpoints == null)
{
return null;
}
var matcherEndpoints = endpoints.OfType<MatcherEndpoint>();
if (!matcherEndpoints.Any())
{
return null;
}
return matcherEndpoints;
}
}
}

View File

@ -5,8 +5,17 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Defines a contract to find endpoints based on the supplied lookup information.
/// </summary>
/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
public interface IEndpointFinder<TAddress>
{
/// <summary>
/// Finds endpoints based on the supplied lookup information.
/// </summary>
/// <param name="address">The information used to look up endpoints.</param>
/// <returns>A collection of <see cref="Endpoint"/>.</returns>
IEnumerable<Endpoint> FindEndpoints(TAddress address);
}
}

View File

@ -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<INameMetadata>(
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<INameMetadata>(
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<DefaultLinkGenerationTemplate>(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<DefaultLinkGenerationTemplate>(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<DefaultLinkGenerationTemplate>(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<IEndpointFinder<INameMetadata>, 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<INameMetadata>(new NameMetadata("CustomerDetails"));
// Assert
var defaultTemplate = Assert.IsType<DefaultLinkGenerationTemplate>(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<IEndpointFinder<INameMetadata>, 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<INameMetadata>(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; }
}
}
}