Revisions to LinkGenerator (#770)
* Revisions to LinkGenerator * PR feedback
This commit is contained in:
parent
950d2e9b74
commit
e3b704095b
|
|
@ -57,9 +57,10 @@ namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
|||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(_lookUpValues));
|
||||
routeName: null,
|
||||
values: _lookUpValues);
|
||||
|
||||
AssertUrl("/repos/aspnet/routing/issues/comments/20202", actualUrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,17 +56,17 @@ namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
|||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
}));
|
||||
routeName: null,
|
||||
values: new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
});
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,14 +53,14 @@ namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
|||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Products",
|
||||
action = "Details",
|
||||
}));
|
||||
routeName: null,
|
||||
values: new
|
||||
{
|
||||
controller = "Products",
|
||||
action = "Details",
|
||||
});
|
||||
|
||||
AssertUrl("/Products/Details", actualUrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,17 +56,17 @@ namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
|||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
}));
|
||||
routeName: null,
|
||||
values: new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
});
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,13 +103,13 @@ namespace RoutingSample.Web
|
|||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
return response.WriteAsync(
|
||||
"Link: " + linkGenerator.GetLink(httpContext, "WithSingleAsteriskCatchAll", new { }));
|
||||
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { }));
|
||||
},
|
||||
RoutePatternFactory.Parse("/WithSingleAsteriskCatchAll/{*path}"),
|
||||
0,
|
||||
new EndpointMetadataCollection(
|
||||
new RouteValuesAddressMetadata(
|
||||
name: "WithSingleAsteriskCatchAll",
|
||||
routeName: "WithSingleAsteriskCatchAll",
|
||||
requiredValues: new RouteValueDictionary())),
|
||||
"WithSingleAsteriskCatchAll"),
|
||||
new RouteEndpoint((httpContext) =>
|
||||
|
|
@ -120,13 +120,13 @@ namespace RoutingSample.Web
|
|||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
return response.WriteAsync(
|
||||
"Link: " + linkGenerator.GetLink(httpContext, "WithDoubleAsteriskCatchAll", new { }));
|
||||
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { }));
|
||||
},
|
||||
RoutePatternFactory.Parse("/WithDoubleAsteriskCatchAll/{**path}"),
|
||||
0,
|
||||
new EndpointMetadataCollection(
|
||||
new RouteValuesAddressMetadata(
|
||||
name: "WithDoubleAsteriskCatchAll",
|
||||
routeName: "WithDoubleAsteriskCatchAll",
|
||||
requiredValues: new RouteValueDictionary())),
|
||||
"WithDoubleAsteriskCatchAll"),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,29 +1,90 @@
|
|||
// 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 to generate a URL from a template.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <see cref="LinkGenerationTemplate"/> can be created from <see cref="LinkGenerator.GetTemplateByAddress{TAddress}(TAddress)"/>
|
||||
/// by supplying an address value which has matching endpoints. The returned <see cref="LinkGenerationTemplate"/>
|
||||
/// will be bound to the endpoints matching the address that was originally provided.
|
||||
/// </remarks>
|
||||
public abstract class LinkGenerationTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a URL with an absolute path from the specified route values.
|
||||
/// Generates a URI with an absolute path based on the provided 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);
|
||||
}
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</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 abstract string GetPath(
|
||||
HttpContext httpContext,
|
||||
object values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a URL with an absolute path from the specified route values and link options.
|
||||
/// Generates a URI with an absolute path based on the provided values.
|
||||
/// </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);
|
||||
/// <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 abstract string GetPath(
|
||||
object values,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an absolute URI based on the provided values.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</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 abstract string GetUri(
|
||||
HttpContext httpContext,
|
||||
object values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an absolute URI based on the provided values.
|
||||
/// </summary>
|
||||
/// <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 abstract string GetUri(
|
||||
object values,
|
||||
string scheme,
|
||||
HostString host,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,463 +1,121 @@
|
|||
// 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>
|
||||
/// Defines a contract to generate URLs to endpoints.
|
||||
/// Defines a contract to generate absolute and related URIs based on endpoint routing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Generating URIs in endpoint routing occurs in two phases. First, an address is bound to a list of
|
||||
/// endpoints that match the address. Secondly, each endpoint's <c>RoutePattern</c> is evaluated, until
|
||||
/// a route pattern that matches the supplied values is found. The resulting output is combined with
|
||||
/// the other URI parts supplied to the link generator and returned.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The methods provided by the <see cref="LinkGenerator"/> type are general infrastructure, and support
|
||||
/// the standard link generator functionality for any type of address. The most convenient way to use
|
||||
/// <see cref="LinkGenerator"/> is through extension methods that perform operations for a specific
|
||||
/// address type.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class LinkGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a URL with an absolute path from the specified route values.
|
||||
/// Generates a URI with an absolute path based on the provided 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))
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
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,
|
||||
object values,
|
||||
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<TAddress>'.
|
||||
/// </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(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<TAddress>'.
|
||||
/// 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<TAddress>'.
|
||||
/// </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(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<TAddress>'.
|
||||
/// 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(httpContext: null, address, values, options, 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<TAddress>'.
|
||||
/// </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(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<TAddress>'.
|
||||
/// 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>(
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="address">The address value. 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 abstract string GetPathByAddress<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address,
|
||||
object values,
|
||||
out string link)
|
||||
{
|
||||
return TryGetLinkByAddress(httpContext, address, values, options: null, out link);
|
||||
}
|
||||
RouteValueDictionary values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <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<TAddress>'.
|
||||
/// Generates a URI with an absolute path based on the provided values.
|
||||
/// </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>(
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="address">The address value. 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 abstract string GetPathByAddress<TAddress>(
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an absolute URI based on the provided values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="address">The address value. 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 abstract string GetUriByAddress<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address,
|
||||
object values,
|
||||
LinkOptions options)
|
||||
{
|
||||
if (TryGetLinkByAddress(httpContext, address, values, options, out var link))
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
|
||||
}
|
||||
RouteValueDictionary values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <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<TAddress>'.
|
||||
/// A return value indicates whether the operation succeeded.
|
||||
/// Generates an absolute URI based on the provided values.
|
||||
/// </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>(
|
||||
HttpContext httpContext,
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="address">The address value. 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 abstract string GetUriByAddress<TAddress>(
|
||||
TAddress address,
|
||||
object values,
|
||||
LinkOptions options,
|
||||
out string link);
|
||||
RouteValueDictionary values,
|
||||
string scheme,
|
||||
HostString host,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <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.
|
||||
/// Gets a <see cref="LinkGenerationTemplate"/> based on the provided <paramref name="address"/>.
|
||||
/// </summary>
|
||||
/// <param name="values">
|
||||
/// An object that contains route values. These values are used to lookup endpoint(s).
|
||||
/// </param>
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="address">The address value. Used to resolve endpoints.</param>
|
||||
/// <returns>
|
||||
/// If an endpoint(s) was found successfully, then this returns a template object representing that,
|
||||
/// <c>null</c> otherwise.
|
||||
/// A <see cref="LinkGenerationTemplate"/> if one or more endpoints matching the address can be found, otherwise <c>null</c>.
|
||||
/// </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<TAddress>'.
|
||||
/// </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<TAddress>'.
|
||||
/// </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);
|
||||
public abstract LinkGenerationTemplate GetTemplateByAddress<TAddress>(TAddress address);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
sb.Append(" }");
|
||||
var routeValuesAddressMetadata = routeEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||
sb.Append(", Route Name: ");
|
||||
sb.Append(routeValuesAddressMetadata?.Name);
|
||||
sb.Append(routeValuesAddressMetadata?.RouteName);
|
||||
if (routeValuesAddressMetadata?.RequiredValues != null)
|
||||
{
|
||||
sb.Append(", Required Values: new { ");
|
||||
|
|
|
|||
|
|
@ -1,62 +1,98 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
internal class DefaultLinkGenerationTemplate : LinkGenerationTemplate
|
||||
internal sealed class DefaultLinkGenerationTemplate : LinkGenerationTemplate
|
||||
{
|
||||
public DefaultLinkGenerationTemplate(
|
||||
DefaultLinkGenerator linkGenerator,
|
||||
IEnumerable<RouteEndpoint> endpoints,
|
||||
HttpContext httpContext,
|
||||
RouteValueDictionary explicitValues,
|
||||
RouteValueDictionary ambientValues)
|
||||
public DefaultLinkGenerationTemplate(DefaultLinkGenerator linkGenerator, List<RouteEndpoint> endpoints)
|
||||
{
|
||||
LinkGenerator = linkGenerator;
|
||||
Endpoints = endpoints;
|
||||
HttpContext = httpContext;
|
||||
EarlierExplicitValues = explicitValues;
|
||||
AmbientValues = ambientValues;
|
||||
}
|
||||
|
||||
internal DefaultLinkGenerator LinkGenerator { get; }
|
||||
public DefaultLinkGenerator LinkGenerator { get; }
|
||||
|
||||
internal IEnumerable<RouteEndpoint> Endpoints { get; }
|
||||
public List<RouteEndpoint> Endpoints { get; }
|
||||
|
||||
internal HttpContext HttpContext { get; }
|
||||
|
||||
internal RouteValueDictionary EarlierExplicitValues { get; }
|
||||
|
||||
internal RouteValueDictionary AmbientValues { get; }
|
||||
|
||||
public override string MakeUrl(object values, LinkOptions options)
|
||||
public override string GetPath(
|
||||
HttpContext httpContext,
|
||||
object values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
var currentValues = new RouteValueDictionary(values);
|
||||
var mergedValuesDictionary = new RouteValueDictionary(EarlierExplicitValues);
|
||||
|
||||
foreach (var kvp in currentValues)
|
||||
if (httpContext == null)
|
||||
{
|
||||
mergedValuesDictionary[kvp.Key] = kvp.Value;
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
foreach (var endpoint in Endpoints)
|
||||
return LinkGenerator.GetPathByEndpoints(
|
||||
Endpoints,
|
||||
DefaultLinkGenerator.GetAmbientValues(httpContext),
|
||||
new RouteValueDictionary(values),
|
||||
httpContext.Request.PathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
|
||||
public override string GetPath(
|
||||
object values,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
return LinkGenerator.GetPathByEndpoints(
|
||||
Endpoints,
|
||||
ambientValues: null,
|
||||
new RouteValueDictionary(values),
|
||||
pathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
|
||||
public override string GetUri(
|
||||
HttpContext httpContext,
|
||||
object values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
var link = LinkGenerator.MakeLink(
|
||||
HttpContext,
|
||||
endpoint,
|
||||
AmbientValues,
|
||||
mergedValuesDictionary,
|
||||
options);
|
||||
if (link != null)
|
||||
{
|
||||
return link;
|
||||
}
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
return null;
|
||||
|
||||
return LinkGenerator.GetUriByEndpoints(
|
||||
Endpoints,
|
||||
DefaultLinkGenerator.GetAmbientValues(httpContext),
|
||||
new RouteValueDictionary(values),
|
||||
httpContext.Request.Scheme,
|
||||
httpContext.Request.Host,
|
||||
httpContext.Request.PathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
|
||||
public override string GetUri(
|
||||
object values,
|
||||
string scheme,
|
||||
HostString host,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
return LinkGenerator.GetUriByEndpoints(
|
||||
Endpoints,
|
||||
ambientValues: null,
|
||||
new RouteValueDictionary(values),
|
||||
scheme,
|
||||
host,
|
||||
pathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -17,14 +17,17 @@ using Microsoft.Extensions.Options;
|
|||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
internal class DefaultLinkGenerator : LinkGenerator
|
||||
internal sealed class DefaultLinkGenerator : LinkGenerator
|
||||
{
|
||||
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
||||
private static readonly char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
||||
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
||||
private readonly ILogger<DefaultLinkGenerator> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly RouteOptions _options;
|
||||
|
||||
// A LinkOptions object initialized with the values from RouteOptions
|
||||
// Used when the user didn't specify something more global.
|
||||
private readonly LinkOptions _globalLinkOptions;
|
||||
|
||||
public DefaultLinkGenerator(
|
||||
ParameterPolicyFactory parameterPolicyFactory,
|
||||
|
|
@ -35,73 +38,218 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
_parameterPolicyFactory = parameterPolicyFactory;
|
||||
_uriBuildingContextPool = uriBuildingContextPool;
|
||||
_options = routeOptions.Value;
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
_globalLinkOptions = new LinkOptions()
|
||||
{
|
||||
AppendTrailingSlash = routeOptions.Value.AppendTrailingSlash,
|
||||
LowercaseQueryStrings = routeOptions.Value.LowercaseQueryStrings,
|
||||
LowercaseUrls = routeOptions.Value.LowercaseUrls,
|
||||
};
|
||||
}
|
||||
|
||||
public override bool TryGetLink(
|
||||
HttpContext httpContext,
|
||||
string routeName,
|
||||
object values,
|
||||
LinkOptions options,
|
||||
out string link)
|
||||
{
|
||||
return TryGetLinkByRouteValues(
|
||||
httpContext,
|
||||
routeName,
|
||||
values,
|
||||
options,
|
||||
out link);
|
||||
}
|
||||
|
||||
public override bool TryGetLinkByAddress<TAddress>(
|
||||
public override string GetPathByAddress<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address,
|
||||
object values,
|
||||
LinkOptions options,
|
||||
out string link)
|
||||
RouteValueDictionary values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
return TryGetLinkByAddressInternal(
|
||||
httpContext,
|
||||
address,
|
||||
explicitValues: values,
|
||||
ambientValues: GetAmbientValues(httpContext),
|
||||
options,
|
||||
out link);
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
var endpoints = GetEndpoints(address);
|
||||
if (endpoints.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetPathByEndpoints(
|
||||
endpoints,
|
||||
GetAmbientValues(httpContext),
|
||||
values,
|
||||
httpContext.Request.PathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
|
||||
public override LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values)
|
||||
public override string GetPathByAddress<TAddress>(
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
var ambientValues = GetAmbientValues(httpContext);
|
||||
var explicitValues = new RouteValueDictionary(values);
|
||||
var endpoints = GetEndpoints(address);
|
||||
if (endpoints.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetTemplateInternal(
|
||||
httpContext,
|
||||
new RouteValuesAddress
|
||||
{
|
||||
RouteName = routeName,
|
||||
ExplicitValues = explicitValues,
|
||||
AmbientValues = ambientValues
|
||||
},
|
||||
ambientValues,
|
||||
explicitValues,
|
||||
values);
|
||||
return GetPathByEndpoints(
|
||||
endpoints,
|
||||
ambientValues: null,
|
||||
values,
|
||||
pathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
|
||||
public override LinkGenerationTemplate GetTemplateByAddress<TAddress>(
|
||||
public override string GetUriByAddress<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address)
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
return GetTemplateInternal(httpContext, address, values: null);
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
var endpoints = GetEndpoints(address);
|
||||
if (endpoints.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetUriByEndpoints(
|
||||
endpoints,
|
||||
GetAmbientValues(httpContext),
|
||||
values,
|
||||
httpContext.Request.Scheme,
|
||||
httpContext.Request.Host,
|
||||
httpContext.Request.PathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
|
||||
internal string MakeLink(
|
||||
public override string GetUriByAddress<TAddress>(
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
string scheme,
|
||||
HostString host,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = null)
|
||||
{
|
||||
if (!host.HasValue)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
}
|
||||
|
||||
var endpoints = GetEndpoints(address);
|
||||
if (endpoints.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetUriByEndpoints(
|
||||
endpoints,
|
||||
ambientValues: null,
|
||||
values,
|
||||
scheme,
|
||||
host,
|
||||
pathBase,
|
||||
fragment,
|
||||
options);
|
||||
}
|
||||
|
||||
public override LinkGenerationTemplate GetTemplateByAddress<TAddress>(TAddress address)
|
||||
{
|
||||
var endpoints = GetEndpoints(address);
|
||||
if (endpoints.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DefaultLinkGenerationTemplate(this, endpoints);
|
||||
}
|
||||
|
||||
private List<RouteEndpoint> GetEndpoints<TAddress>(TAddress address)
|
||||
{
|
||||
var addressingScheme = _serviceProvider.GetRequiredService<IEndpointFinder<TAddress>>();
|
||||
return addressingScheme.FindEndpoints(address).OfType<RouteEndpoint>().ToList();
|
||||
}
|
||||
|
||||
// Also called from DefaultLinkGenerationTemplate
|
||||
public string GetPathByEndpoints(
|
||||
List<RouteEndpoint> endpoints,
|
||||
RouteValueDictionary ambientValues,
|
||||
RouteValueDictionary values,
|
||||
PathString pathBase,
|
||||
FragmentString fragment,
|
||||
LinkOptions options)
|
||||
{
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
var endpoint = endpoints[i];
|
||||
if (TryProcessTemplate(
|
||||
httpContext: null,
|
||||
endpoint,
|
||||
ambientValues: ambientValues,
|
||||
values,
|
||||
options,
|
||||
out var result))
|
||||
{
|
||||
|
||||
return UriHelper.BuildRelative(
|
||||
pathBase,
|
||||
result.path,
|
||||
result.query,
|
||||
fragment);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Also called from DefaultLinkGenerationTemplate
|
||||
public string GetUriByEndpoints(
|
||||
List<RouteEndpoint> endpoints,
|
||||
RouteValueDictionary ambientValues,
|
||||
RouteValueDictionary values,
|
||||
string scheme,
|
||||
HostString host,
|
||||
PathString pathBase,
|
||||
FragmentString fragment,
|
||||
LinkOptions options)
|
||||
{
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
var endpoint = endpoints[i];
|
||||
if (TryProcessTemplate(
|
||||
httpContext: null,
|
||||
endpoint,
|
||||
ambientValues: ambientValues,
|
||||
values,
|
||||
options,
|
||||
out var result))
|
||||
{
|
||||
return UriHelper.BuildAbsolute(
|
||||
scheme,
|
||||
host,
|
||||
pathBase,
|
||||
result.path,
|
||||
result.query,
|
||||
fragment);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal bool TryProcessTemplate(
|
||||
HttpContext httpContext,
|
||||
RouteEndpoint endpoint,
|
||||
RouteValueDictionary ambientValues,
|
||||
RouteValueDictionary explicitValues,
|
||||
LinkOptions options)
|
||||
LinkOptions options,
|
||||
out (PathString path, QueryString query) result)
|
||||
{
|
||||
var templateBinder = new TemplateBinder(
|
||||
UrlEncoder.Default,
|
||||
|
|
@ -117,118 +265,22 @@ namespace Microsoft.AspNetCore.Routing
|
|||
if (templateValuesResult == null)
|
||||
{
|
||||
// We're missing one of the required values for this route.
|
||||
return null;
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
|
||||
{
|
||||
return null;
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var url = templateBinder.BindValues(templateValuesResult.AcceptedValues);
|
||||
return Normalize(url, options);
|
||||
}
|
||||
|
||||
private bool TryGetLinkByRouteValues(
|
||||
HttpContext httpContext,
|
||||
string routeName,
|
||||
object values,
|
||||
LinkOptions options,
|
||||
out string link)
|
||||
{
|
||||
var ambientValues = GetAmbientValues(httpContext);
|
||||
|
||||
var address = new RouteValuesAddress
|
||||
{
|
||||
RouteName = routeName,
|
||||
ExplicitValues = new RouteValueDictionary(values),
|
||||
AmbientValues = ambientValues
|
||||
};
|
||||
|
||||
return TryGetLinkByAddressInternal(
|
||||
httpContext,
|
||||
address,
|
||||
explicitValues: values,
|
||||
ambientValues: ambientValues,
|
||||
options,
|
||||
out link);
|
||||
}
|
||||
|
||||
private bool TryGetLinkByAddressInternal<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address,
|
||||
object explicitValues,
|
||||
RouteValueDictionary ambientValues,
|
||||
LinkOptions options,
|
||||
out string link)
|
||||
{
|
||||
link = null;
|
||||
|
||||
var endpoints = FindEndpoints(address);
|
||||
if (endpoints == null)
|
||||
if (!templateBinder.TryBindValues(templateValuesResult.AcceptedValues, options, _globalLinkOptions, out result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
link = MakeLink(
|
||||
httpContext,
|
||||
endpoint,
|
||||
ambientValues,
|
||||
new RouteValueDictionary(explicitValues),
|
||||
options);
|
||||
|
||||
if (link != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private LinkGenerationTemplate GetTemplateInternal<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address,
|
||||
object values)
|
||||
{
|
||||
var endpoints = FindEndpoints(address);
|
||||
if (endpoints == null)
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool MatchesConstraints(
|
||||
|
|
@ -260,75 +312,10 @@ namespace Microsoft.AspNetCore.Routing
|
|||
return true;
|
||||
}
|
||||
|
||||
private string Normalize(string url, LinkOptions options)
|
||||
// Also called from DefaultLinkGenerationTemplate
|
||||
public static RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||
{
|
||||
var lowercaseUrls = options?.LowercaseUrls ?? _options.LowercaseUrls;
|
||||
var lowercaseQueryStrings = options?.LowercaseQueryStrings ?? _options.LowercaseQueryStrings;
|
||||
var appendTrailingSlash = options?.AppendTrailingSlash ?? _options.AppendTrailingSlash;
|
||||
|
||||
if (!string.IsNullOrEmpty(url) && (lowercaseUrls || appendTrailingSlash))
|
||||
{
|
||||
var indexOfSeparator = url.IndexOfAny(UrlQueryDelimiters);
|
||||
var urlWithoutQueryString = url;
|
||||
var queryString = string.Empty;
|
||||
|
||||
if (indexOfSeparator != -1)
|
||||
{
|
||||
urlWithoutQueryString = url.Substring(0, indexOfSeparator);
|
||||
queryString = url.Substring(indexOfSeparator);
|
||||
}
|
||||
|
||||
if (lowercaseUrls)
|
||||
{
|
||||
urlWithoutQueryString = urlWithoutQueryString.ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (lowercaseUrls && lowercaseQueryStrings)
|
||||
{
|
||||
queryString = queryString.ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (appendTrailingSlash && !urlWithoutQueryString.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
urlWithoutQueryString += "/";
|
||||
}
|
||||
|
||||
// queryString will contain the delimiter ? or # as the first character, so it's safe to append.
|
||||
url = urlWithoutQueryString + queryString;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
private RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext != null)
|
||||
{
|
||||
var feature = httpContext.Features.Get<IRouteValuesFeature>();
|
||||
if (feature != null)
|
||||
{
|
||||
return feature.RouteValues;
|
||||
}
|
||||
}
|
||||
return new RouteValueDictionary();
|
||||
}
|
||||
|
||||
private IEnumerable<RouteEndpoint> FindEndpoints<TAddress>(TAddress address)
|
||||
{
|
||||
var finder = _serviceProvider.GetRequiredService<IEndpointFinder<TAddress>>();
|
||||
var endpoints = finder.FindEndpoints(address);
|
||||
if (endpoints == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var routeEndpoints = endpoints.OfType<RouteEndpoint>();
|
||||
if (!routeEndpoints.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return routeEndpoints;
|
||||
return httpContext?.Features.Get<IRouteValuesFeature>()?.RouteValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
public interface IRouteValuesAddressMetadata
|
||||
{
|
||||
string Name { get; }
|
||||
string RouteName { get; }
|
||||
IReadOnlyDictionary<string, object> RequiredValues { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,18 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
public class UriBuildingContext
|
||||
{
|
||||
// Holds the 'accepted' parts of the uri.
|
||||
private readonly StringBuilder _uri;
|
||||
// Holds the 'accepted' parts of the path.
|
||||
private readonly StringBuilder _path;
|
||||
private StringBuilder _query;
|
||||
|
||||
// Holds the 'optional' parts of the uri. We need a secondary buffer to handle cases where an optional
|
||||
// Holds the 'optional' parts of the path. We need a secondary buffer to handle cases where an optional
|
||||
// segment is in the middle of the uri. We don't know if we need to write it out - if it's
|
||||
// followed by other optional segments than we will just throw it away.
|
||||
private readonly List<BufferValue> _buffer;
|
||||
|
|
@ -27,20 +29,30 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
public UriBuildingContext(UrlEncoder urlEncoder)
|
||||
{
|
||||
_urlEncoder = urlEncoder;
|
||||
_uri = new StringBuilder();
|
||||
_path = new StringBuilder();
|
||||
_query = new StringBuilder();
|
||||
_buffer = new List<BufferValue>();
|
||||
Writer = new StringWriter(_uri);
|
||||
PathWriter = new StringWriter(_path);
|
||||
QueryWriter = new StringWriter(_query);
|
||||
_lastValueOffset = -1;
|
||||
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public bool LowercaseUrls { get; set; }
|
||||
|
||||
public bool LowercaseQueryStrings { get; set; }
|
||||
|
||||
public bool AppendTrailingSlash { get; set; }
|
||||
|
||||
public SegmentState BufferState { get; private set; }
|
||||
|
||||
public SegmentState UriState { get; private set; }
|
||||
|
||||
public TextWriter Writer { get; }
|
||||
public TextWriter PathWriter { get; }
|
||||
|
||||
public TextWriter QueryWriter { get; }
|
||||
|
||||
public bool Accept(string value)
|
||||
{
|
||||
|
|
@ -68,6 +80,12 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
return false;
|
||||
}
|
||||
|
||||
// NOTE: this needs to be above all 'EncodeValue' and _path.Append calls
|
||||
if (LowercaseUrls)
|
||||
{
|
||||
value = value.ToLowerInvariant();
|
||||
}
|
||||
|
||||
for (var i = 0; i < _buffer.Count; i++)
|
||||
{
|
||||
if (_buffer[i].RequiresEncoding)
|
||||
|
|
@ -76,29 +94,29 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
}
|
||||
else
|
||||
{
|
||||
_uri.Append(_buffer[i].Value);
|
||||
_path.Append(_buffer[i].Value);
|
||||
}
|
||||
}
|
||||
_buffer.Clear();
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0)
|
||||
if (_path.Length != 0)
|
||||
{
|
||||
_uri.Append("/");
|
||||
_path.Append("/");
|
||||
}
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
UriState = SegmentState.Inside;
|
||||
|
||||
_lastValueOffset = _uri.Length;
|
||||
_lastValueOffset = _path.Length;
|
||||
|
||||
// Allow the first segment to have a leading slash.
|
||||
// This prevents the leading slash from PathString segments from being encoded.
|
||||
if (_uri.Length == 0 && value.Length > 0 && value[0] == '/')
|
||||
if (_path.Length == 0 && value.Length > 0 && value[0] == '/')
|
||||
{
|
||||
_uri.Append("/");
|
||||
_path.Append("/");
|
||||
EncodeValue(value, 1, value.Length - 1, encodeSlashes);
|
||||
}
|
||||
else
|
||||
|
|
@ -112,7 +130,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
public void Remove(string literal)
|
||||
{
|
||||
Debug.Assert(_lastValueOffset != -1, "Cannot invoke Remove more than once.");
|
||||
_uri.Length = _lastValueOffset;
|
||||
_path.Length = _lastValueOffset;
|
||||
_lastValueOffset = -1;
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +170,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0 || _buffer.Count != 0)
|
||||
if (_path.Length != 0 || _buffer.Count != 0)
|
||||
{
|
||||
_buffer.Add(new BufferValue("/", requiresEncoding: false));
|
||||
}
|
||||
|
|
@ -172,11 +190,17 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
|
||||
public void Clear()
|
||||
{
|
||||
_uri.Clear();
|
||||
if (_uri.Capacity > 128)
|
||||
_path.Clear();
|
||||
if (_path.Capacity > 128)
|
||||
{
|
||||
// We don't want to retain too much memory if this is getting pooled.
|
||||
_uri.Capacity = 128;
|
||||
_path.Capacity = 128;
|
||||
}
|
||||
|
||||
_query.Clear();
|
||||
if (_query.Capacity > 128)
|
||||
{
|
||||
_query.Capacity = 128;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
|
|
@ -189,18 +213,52 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
_lastValueOffset = -1;
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
|
||||
AppendTrailingSlash = false;
|
||||
LowercaseQueryStrings = false;
|
||||
LowercaseUrls = false;
|
||||
}
|
||||
|
||||
// Used by TemplateBinder.BindValues - the legacy code path of IRouter
|
||||
public override string ToString()
|
||||
{
|
||||
// We can ignore any currently buffered segments - they are are guaranteed to be 'defaults'.
|
||||
if (_uri.Length > 0 && _uri[0] != '/')
|
||||
if (_path.Length > 0 && _path[0] != '/')
|
||||
{
|
||||
// Normalize generated paths so that they always contain a leading slash.
|
||||
_uri.Insert(0, '/');
|
||||
_path.Insert(0, '/');
|
||||
}
|
||||
|
||||
return _uri.ToString();
|
||||
return _path.ToString() + _query.ToString();
|
||||
}
|
||||
|
||||
// Used by TemplateBinder.TryBindValues - the new code path of LinkGenerator
|
||||
public PathString ToPathString()
|
||||
{
|
||||
if (_path.Length > 0 && _path[0] != '/')
|
||||
{
|
||||
// Normalize generated paths so that they always contain a leading slash.
|
||||
_path.Insert(0, '/');
|
||||
}
|
||||
|
||||
if (AppendTrailingSlash && _path.Length > 0 && _path[_path.Length - 1] != '/')
|
||||
{
|
||||
_path.Append('/');
|
||||
}
|
||||
|
||||
return new PathString(_path.ToString());
|
||||
}
|
||||
|
||||
// Used by TemplateBinder.TryBindValues - the new code path of LinkGenerator
|
||||
public QueryString ToQueryString()
|
||||
{
|
||||
if (_query.Length > 0 && _query[0] != '?')
|
||||
{
|
||||
// Normalize generated query so that they always contain a leading ?.
|
||||
_query.Insert(0, '?');
|
||||
}
|
||||
|
||||
return new QueryString(_query.ToString());
|
||||
}
|
||||
|
||||
private void EncodeValue(string value)
|
||||
|
|
@ -219,7 +277,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
// Just encode everything if its ok to encode slashes
|
||||
if (encodeSlashes)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value, start, characterCount);
|
||||
_urlEncoder.Encode(PathWriter, value, start, characterCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -227,8 +285,8 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
int length = start + characterCount;
|
||||
while ((end = value.IndexOf('/', start, characterCount)) >= 0)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value, start, end - start);
|
||||
_uri.Append("/");
|
||||
_urlEncoder.Encode(PathWriter, value, start, end - start);
|
||||
_path.Append("/");
|
||||
|
||||
start = end + 1;
|
||||
characterCount = length - start;
|
||||
|
|
@ -236,14 +294,14 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
|
||||
if (end < 0 && characterCount >= 0)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value, start, length - start);
|
||||
_urlEncoder.Encode(PathWriter, value, start, length - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}' Buffered: '{1}'}}", _uri, string.Join("", _buffer));
|
||||
return string.Format("{{Accepted: '{0}' Buffered: '{1}'}}", _path, string.Join("", _buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
// 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 <see cref="RouteValuesAddress"/>.
|
||||
/// </summary>
|
||||
public static class LinkGeneratorRouteValuesAddressExtensions
|
||||
{
|
||||
public static string GetPathByRouteValues(
|
||||
this LinkGenerator generator,
|
||||
HttpContext httpContext,
|
||||
string routeName,
|
||||
object values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default)
|
||||
{
|
||||
if (generator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generator));
|
||||
}
|
||||
|
||||
var address = CreateAddress(httpContext, routeName, values);
|
||||
return generator.GetPathByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
|
||||
}
|
||||
|
||||
public static string GetPathByRouteValues(
|
||||
this LinkGenerator generator,
|
||||
string routeName,
|
||||
object values,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default)
|
||||
{
|
||||
if (generator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generator));
|
||||
}
|
||||
|
||||
var address = CreateAddress(httpContext: null, routeName, values);
|
||||
return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
|
||||
}
|
||||
|
||||
public static string GetUriByRouteValues(
|
||||
this LinkGenerator generator,
|
||||
HttpContext httpContext,
|
||||
string routeName,
|
||||
object values,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default)
|
||||
{
|
||||
if (generator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generator));
|
||||
}
|
||||
|
||||
var address = CreateAddress(httpContext: null, routeName, values);
|
||||
return generator.GetUriByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
|
||||
}
|
||||
|
||||
public static string GetUriByRouteValues(
|
||||
this LinkGenerator generator,
|
||||
string routeName,
|
||||
object values,
|
||||
string scheme,
|
||||
HostString host,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default)
|
||||
{
|
||||
if (generator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generator));
|
||||
}
|
||||
|
||||
var address = CreateAddress(httpContext: null, routeName, values);
|
||||
return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
|
||||
}
|
||||
|
||||
|
||||
private static RouteValuesAddress CreateAddress(HttpContext httpContext, string routeName, object values)
|
||||
{
|
||||
return new RouteValuesAddress()
|
||||
{
|
||||
AmbientValues = DefaultLinkGenerator.GetAmbientValues(httpContext),
|
||||
ExplicitValues = new RouteValueDictionary(values),
|
||||
RouteName = routeName,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,19 +11,19 @@ namespace Microsoft.AspNetCore.Routing
|
|||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
public sealed class RouteValuesAddressMetadata : IRouteValuesAddressMetadata
|
||||
{
|
||||
public RouteValuesAddressMetadata(string name, IReadOnlyDictionary<string, object> requiredValues)
|
||||
public RouteValuesAddressMetadata(string routeName, IReadOnlyDictionary<string, object> requiredValues)
|
||||
{
|
||||
Name = name;
|
||||
RouteName = routeName;
|
||||
RequiredValues = requiredValues;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string RouteName { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, object> RequiredValues { get; }
|
||||
|
||||
internal string DebuggerToString()
|
||||
{
|
||||
return $"Name: {Name} - Required values: {string.Join(", ", FormatValues(RequiredValues))}";
|
||||
return $"Name: {RouteName} - Required values: {string.Join(", ", FormatValues(RequiredValues))}";
|
||||
|
||||
IEnumerable<string> FormatValues(IEnumerable<KeyValuePair<string, object>> values)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
RequiredLinkValues = new RouteValueDictionary(routeValuesAddressMetadata?.RequiredValues),
|
||||
RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
|
||||
Data = endpoint,
|
||||
RouteName = routeValuesAddressMetadata?.Name,
|
||||
RouteName = routeValuesAddressMetadata?.RouteName,
|
||||
};
|
||||
entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
|
||||
return entry;
|
||||
|
|
|
|||
|
|
@ -160,12 +160,48 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
public string BindValues(RouteValueDictionary acceptedValues)
|
||||
{
|
||||
var context = _pool.Get();
|
||||
var result = BindValues(context, acceptedValues);
|
||||
_pool.Return(context);
|
||||
return result;
|
||||
|
||||
try
|
||||
{
|
||||
return TryBindValuesCore(context, acceptedValues) ? context.ToString() : null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(context);
|
||||
}
|
||||
}
|
||||
|
||||
private string BindValues(UriBuildingContext context, RouteValueDictionary acceptedValues)
|
||||
// Step 2: If the route is a match generate the appropriate URI
|
||||
internal bool TryBindValues(
|
||||
RouteValueDictionary acceptedValues,
|
||||
LinkOptions options,
|
||||
LinkOptions globalOptions,
|
||||
out (PathString path, QueryString query) result)
|
||||
{
|
||||
var context = _pool.Get();
|
||||
|
||||
context.AppendTrailingSlash = options?.AppendTrailingSlash ?? globalOptions.AppendTrailingSlash ?? false;
|
||||
context.LowercaseQueryStrings = options?.LowercaseQueryStrings ?? globalOptions.LowercaseQueryStrings ?? false;
|
||||
context.LowercaseUrls = options?.LowercaseUrls ?? globalOptions.LowercaseUrls ?? false;
|
||||
|
||||
try
|
||||
{
|
||||
if (TryBindValuesCore(context, acceptedValues))
|
||||
{
|
||||
result = (context.ToPathString(), context.ToQueryString());
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(context);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryBindValuesCore(UriBuildingContext context, RouteValueDictionary acceptedValues)
|
||||
{
|
||||
for (var i = 0; i < _pattern.PathSegments.Count; i++)
|
||||
{
|
||||
|
|
@ -182,14 +218,14 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
{
|
||||
if (!context.Accept(literalPart.Content))
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (part is RoutePatternSeparatorPart separatorPart)
|
||||
{
|
||||
if (!context.Accept(separatorPart.Content))
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (part is RoutePatternParameterPart parameterPart)
|
||||
|
|
@ -216,7 +252,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
// we won't necessarily add it to the URI we generate.
|
||||
if (!context.Buffer(converted))
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -237,7 +273,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -262,26 +298,33 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, value, wroteFirst);
|
||||
wroteFirst |= AddQueryKeyValueToContext(context, kvp.Key, value, wroteFirst);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, kvp.Value, wroteFirst);
|
||||
wroteFirst |= AddQueryKeyValueToContext(context, kvp.Key, kvp.Value, wroteFirst);
|
||||
}
|
||||
}
|
||||
return context.ToString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AddParameterToContext(UriBuildingContext context, string key, object value, bool wroteFirst)
|
||||
private bool AddQueryKeyValueToContext(UriBuildingContext context, string key, object value, bool wroteFirst)
|
||||
{
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrEmpty(converted))
|
||||
{
|
||||
context.Writer.Write(wroteFirst ? '&' : '?');
|
||||
_urlEncoder.Encode(context.Writer, key);
|
||||
context.Writer.Write('=');
|
||||
_urlEncoder.Encode(context.Writer, converted);
|
||||
if (context.LowercaseQueryStrings)
|
||||
{
|
||||
key = key.ToLowerInvariant();
|
||||
converted = converted.ToLowerInvariant();
|
||||
}
|
||||
|
||||
context.QueryWriter.Write(wroteFirst ? '&' : '?');
|
||||
_urlEncoder.Encode(context.QueryWriter, key);
|
||||
context.QueryWriter.Write('=');
|
||||
_urlEncoder.Encode(context.QueryWriter, converted);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
// Tests DefaultLinkGenerationTemplate functionality - these are pretty light since most of the functionality
|
||||
// is a direct subset of DefaultLinkGenerator
|
||||
//
|
||||
// Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
|
||||
// and DefaultLinkGeneratorProcessTemplateTest
|
||||
public class DefaultLinkGenerationTemplateTest : LinkGeneratorTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void GetPath_WithoutHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}");
|
||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}");
|
||||
|
||||
var linkGenerator = CreateLinkGenerator();
|
||||
var template = new DefaultLinkGenerationTemplate(linkGenerator, new List<RouteEndpoint>() { endpoint1, endpoint2, });
|
||||
|
||||
// Act
|
||||
var path = template.GetPath(
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
new PathString("/Foo/Bar?encodeme?"),
|
||||
new FragmentString("#Fragment?"),
|
||||
new LinkOptions() { AppendTrailingSlash = true, });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPath_WithHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}");
|
||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}");
|
||||
|
||||
var linkGenerator = CreateLinkGenerator();
|
||||
var template = new DefaultLinkGenerationTemplate(linkGenerator, new List<RouteEndpoint>() { endpoint1, endpoint2, });
|
||||
|
||||
var httpContext = CreateHttpContext();
|
||||
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||
|
||||
// Act
|
||||
var path = template.GetPath(
|
||||
httpContext,
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
new FragmentString("#Fragment?"),
|
||||
new LinkOptions() { AppendTrailingSlash = true, });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUri_WithoutHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}");
|
||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}");
|
||||
|
||||
var linkGenerator = CreateLinkGenerator();
|
||||
var template = new DefaultLinkGenerationTemplate(linkGenerator, new List<RouteEndpoint>() { endpoint1, endpoint2, });
|
||||
|
||||
// Act
|
||||
var path = template.GetUri(
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
"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/Home/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUri_WithHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}");
|
||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}");
|
||||
|
||||
var linkGenerator = CreateLinkGenerator();
|
||||
var template = new DefaultLinkGenerationTemplate(linkGenerator, new List<RouteEndpoint>() { endpoint1, endpoint2, });
|
||||
|
||||
var httpContext = CreateHttpContext();
|
||||
httpContext.Request.Scheme = "http";
|
||||
httpContext.Request.Host = new HostString("example.com");
|
||||
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||
|
||||
// Act
|
||||
var uri = template.GetUri(
|
||||
httpContext,
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
new FragmentString("#Fragment?"),
|
||||
new LinkOptions() { AppendTrailingSlash = true, });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPath_WithHttpContext_IncludesAmbientValues()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}");
|
||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}");
|
||||
|
||||
var linkGenerator = CreateLinkGenerator();
|
||||
var template = new DefaultLinkGenerationTemplate(linkGenerator, new List<RouteEndpoint>() { endpoint1, endpoint2, });
|
||||
|
||||
var httpContext = CreateHttpContext(new { controller = "Home", });
|
||||
httpContext.Request.Scheme = "http";
|
||||
httpContext.Request.Host = new HostString("example.com");
|
||||
|
||||
// Act
|
||||
var uri = template.GetPath(httpContext, values: new RouteValueDictionary(new { action = "Index", }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Home/Index", uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUri_WithHttpContext_IncludesAmbientValues()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}");
|
||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}");
|
||||
|
||||
var linkGenerator = CreateLinkGenerator();
|
||||
var template = new DefaultLinkGenerationTemplate(linkGenerator, new List<RouteEndpoint>() { endpoint1, endpoint2, });
|
||||
|
||||
var httpContext = CreateHttpContext(new { controller = "Home", });
|
||||
httpContext.Request.Scheme = "http";
|
||||
httpContext.Request.Host = new HostString("example.com");
|
||||
|
||||
// Act
|
||||
var uri = template.GetUri(httpContext, values: new { action = "Index", });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("http://example.com/Home/Index", uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,128 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
// Integration tests for GetXyzByRouteValues. 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 RouteValueBasedEndpointFinder in detail. see RouteValueBasedEndpointFinderTest
|
||||
public class LinkGeneratorRouteValuesAddressExtensionsTest : LinkGeneratorTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void GetPathByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// 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 path = linkGenerator.GetPathByRouteValues(
|
||||
routeName: null,
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
new PathString("/Foo/Bar?encodeme?"),
|
||||
new FragmentString("#Fragment?"),
|
||||
new LinkOptions() { AppendTrailingSlash = true, });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPathByRouteValues_WithHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// 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);
|
||||
|
||||
var httpContext = CreateHttpContext();
|
||||
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||
|
||||
// Act
|
||||
var path = linkGenerator.GetPathByRouteValues(
|
||||
httpContext,
|
||||
routeName: null,
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
new FragmentString("#Fragment?"),
|
||||
new LinkOptions() { AppendTrailingSlash = true, });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUriByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// 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 path = linkGenerator.GetUriByRouteValues(
|
||||
routeName: null,
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
"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/Home/In%3Fdex/?query=some%3Fquery#Fragment?", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUri_WithHttpContext_WithPathBaseAndFragment()
|
||||
{
|
||||
// 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);
|
||||
|
||||
var httpContext = CreateHttpContext();
|
||||
httpContext.Request.Scheme = "http";
|
||||
httpContext.Request.Host = new HostString("example.com");
|
||||
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||
|
||||
// Act
|
||||
var uri = linkGenerator.GetUriByRouteValues(
|
||||
httpContext,
|
||||
routeName: null,
|
||||
values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
|
||||
new FragmentString("#Fragment?"),
|
||||
new LinkOptions() { AppendTrailingSlash = true, });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// 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 Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public abstract class LinkGeneratorTestBase
|
||||
{
|
||||
protected HttpContext CreateHttpContext(object ambientValues = null)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
||||
var feature = new EndpointFeature
|
||||
{
|
||||
RouteValues = new RouteValueDictionary(ambientValues)
|
||||
};
|
||||
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
httpContext.Features.Set<IRouteValuesFeature>(feature);
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
private ServiceCollection GetBasicServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||
services.AddOptions();
|
||||
services.AddRouting();
|
||||
services.AddLogging();
|
||||
return services;
|
||||
}
|
||||
|
||||
protected virtual void AddAdditionalServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
|
||||
{
|
||||
return CreateLinkGenerator(routeOptions: null, services: null, endpoints);
|
||||
}
|
||||
|
||||
private protected DefaultLinkGenerator CreateLinkGenerator(RouteOptions routeOptions = null, IServiceCollection services = null, params Endpoint[] endpoints)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
services = GetBasicServices();
|
||||
AddAdditionalServices(services);
|
||||
}
|
||||
|
||||
if (endpoints != null || endpoints.Length > 0)
|
||||
{
|
||||
services.Configure<EndpointOptions>(o =>
|
||||
{
|
||||
o.DataSources.Add(new DefaultEndpointDataSource(endpoints));
|
||||
});
|
||||
}
|
||||
|
||||
routeOptions = routeOptions ?? new RouteOptions();
|
||||
var options = Options.Create(routeOptions);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
return new DefaultLinkGenerator(
|
||||
new DefaultParameterPolicyFactory(options, serviceProvider),
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
options,
|
||||
NullLogger<DefaultLinkGenerator>.Instance,
|
||||
serviceProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,14 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
|
|
|
|||
Loading…
Reference in New Issue