Various improvments to route values link APIs

- Add docs
- Add support for templates
- Remove undesirable constructor parameter
- misc cleanup
This commit is contained in:
Ryan Nowak 2018-09-06 19:18:14 -07:00
parent c683844817
commit 57cc8aea96
4 changed files with 88 additions and 31 deletions

View File

@ -60,8 +60,7 @@ namespace RoutingSample.Web
return new WebHostBuilder() return new WebHostBuilder()
.UseKestrel() .UseKestrel()
.UseIISIntegration() .UseIISIntegration()
.UseStartup(startupType) .UseStartup(startupType);
.ConfigureLogging(l => l.AddConsole().SetMinimumLevel(LogLevel.Debug));
} }
} }
} }

View File

@ -11,6 +11,19 @@ namespace Microsoft.AspNetCore.Routing
/// </summary> /// </summary>
public static class LinkGeneratorRouteValuesAddressExtensions public static class LinkGeneratorRouteValuesAddressExtensions
{ {
/// <summary>
/// Generates a URI with an absolute path based on the provided values.
/// </summary>
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
/// <param name="options">
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
/// names from <c>RouteOptions</c>.
/// </param>
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
public static string GetPathByRouteValues( public static string GetPathByRouteValues(
this LinkGenerator generator, this LinkGenerator generator,
HttpContext httpContext, HttpContext httpContext,
@ -28,6 +41,19 @@ namespace Microsoft.AspNetCore.Routing
return generator.GetPathByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options); return generator.GetPathByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
} }
/// <summary>
/// Generates a URI with an absolute path based on the provided values.
/// </summary>
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
/// <param name="options">
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
/// names from <c>RouteOptions</c>.
/// </param>
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
public static string GetPathByRouteValues( public static string GetPathByRouteValues(
this LinkGenerator generator, this LinkGenerator generator,
string routeName, string routeName,
@ -45,6 +71,19 @@ namespace Microsoft.AspNetCore.Routing
return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options); return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
} }
/// <summary>
/// Generates an absolute URI based on the provided values.
/// </summary>
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
/// <param name="options">
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
/// names from <c>RouteOptions</c>.
/// </param>
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
public static string GetUriByRouteValues( public static string GetUriByRouteValues(
this LinkGenerator generator, this LinkGenerator generator,
HttpContext httpContext, HttpContext httpContext,
@ -62,6 +101,21 @@ namespace Microsoft.AspNetCore.Routing
return generator.GetUriByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options); return generator.GetUriByAddress<RouteValuesAddress>(httpContext, address, address.ExplicitValues, fragment, options);
} }
/// <summary>
/// Generates an absolute URI based on the provided values.
/// </summary>
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
/// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
/// <param name="host">The URI host/authority, applied to the resulting URI.</param>
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
/// <param name="options">
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
/// names from <c>RouteOptions</c>.
/// </param>
/// <returns>An absolute URI, or <c>null</c>.</returns>
public static string GetUriByRouteValues( public static string GetUriByRouteValues(
this LinkGenerator generator, this LinkGenerator generator,
string routeName, string routeName,
@ -81,7 +135,29 @@ namespace Microsoft.AspNetCore.Routing
return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options); return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
} }
/// <summary>
/// Gets a <see cref="LinkGenerationTemplate"/> based on the provided <paramref name="routeName"/> and <paramref name="values"/>.
/// </summary>
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
/// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
/// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
/// <returns>
/// A <see cref="LinkGenerationTemplate"/> if one or more endpoints matching the address can be found, otherwise <c>null</c>.
/// </returns>
public static LinkGenerationTemplate GetTemplateByRouteValues(
this LinkGenerator generator,
string routeName,
object values)
{
if (generator == null)
{
throw new ArgumentNullException(nameof(generator));
}
var address = CreateAddress(httpContext: null, routeName, values);
return generator.GetTemplateByAddress<RouteValuesAddress>(address);
}
private static RouteValuesAddress CreateAddress(HttpContext httpContext, string routeName, object values) private static RouteValuesAddress CreateAddress(HttpContext httpContext, string routeName, object values)
{ {
return new RouteValuesAddress() return new RouteValuesAddress()

View File

@ -6,33 +6,27 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Template; using Microsoft.AspNetCore.Routing.Template;
using Microsoft.AspNetCore.Routing.Tree; using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.ObjectPool;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
internal class RouteValuesBasedEndpointFinder : IEndpointFinder<RouteValuesAddress> internal class RouteValuesBasedEndpointFinder : IEndpointFinder<RouteValuesAddress>
{ {
private readonly CompositeEndpointDataSource _endpointDataSource; private readonly CompositeEndpointDataSource _dataSource;
private readonly ObjectPool<UriBuildingContext> _objectPool;
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree; private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
private IDictionary<string, List<OutboundMatchResult>> _namedMatchResults; private IDictionary<string, List<OutboundMatchResult>> _namedMatchResults;
public RouteValuesBasedEndpointFinder( public RouteValuesBasedEndpointFinder(CompositeEndpointDataSource dataSource)
CompositeEndpointDataSource endpointDataSource,
ObjectPool<UriBuildingContext> objectPool)
{ {
_endpointDataSource = endpointDataSource; _dataSource = dataSource;
_objectPool = objectPool;
// Build initial matches // Build initial matches
BuildOutboundMatches(); BuildOutboundMatches();
// Register for changes in endpoints // Register for changes in endpoints
Extensions.Primitives.ChangeToken.OnChange( Extensions.Primitives.ChangeToken.OnChange(
_endpointDataSource.GetChangeToken, _dataSource.GetChangeToken,
HandleChange); HandleChange);
} }
@ -68,7 +62,7 @@ namespace Microsoft.AspNetCore.Routing
// re-register the callback as the change token is one time use only and a new change token // re-register the callback as the change token is one time use only and a new change token
// is produced every time // is produced every time
Extensions.Primitives.ChangeToken.OnChange( Extensions.Primitives.ChangeToken.OnChange(
_endpointDataSource.GetChangeToken, _dataSource.GetChangeToken,
HandleChange); HandleChange);
} }
@ -104,7 +98,7 @@ namespace Microsoft.AspNetCore.Routing
var namedOutboundMatchResults = new Dictionary<string, List<OutboundMatchResult>>( var namedOutboundMatchResults = new Dictionary<string, List<OutboundMatchResult>>(
StringComparer.OrdinalIgnoreCase); StringComparer.OrdinalIgnoreCase);
var endpoints = _endpointDataSource.Endpoints.OfType<RouteEndpoint>(); var endpoints = _dataSource.Endpoints.OfType<RouteEndpoint>();
foreach (var endpoint in endpoints) foreach (var endpoint in endpoints)
{ {
// Do not consider an endpoint for link generation if the following marker metadata is on it // Do not consider an endpoint for link generation if the following marker metadata is on it

View File

@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.AspNetCore.Routing.Tree; using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.ObjectPool;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
@ -83,13 +82,9 @@ namespace Microsoft.AspNetCore.Routing
// Arrange 1 // Arrange 1
var endpoint1 = CreateEndpoint("/a"); var endpoint1 = CreateEndpoint("/a");
var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 }); var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
var objectPoolProvider = new DefaultObjectPoolProvider();
var objectPool = objectPoolProvider.Create(new UriBuilderContextPooledObjectPolicy());
// Act 1 // Act 1
var finder = new CustomRouteValuesBasedEndpointFinder( var finder = new CustomRouteValuesBasedEndpointFinder(new CompositeEndpointDataSource(new[] { dynamicDataSource }));
new CompositeEndpointDataSource(new[] { dynamicDataSource }),
objectPool);
// Assert 1 // Assert 1
Assert.NotNull(finder.AllMatches); Assert.NotNull(finder.AllMatches);
@ -218,14 +213,9 @@ namespace Microsoft.AspNetCore.Routing
return CreateEndpointFinder(new DefaultEndpointDataSource(endpoints)); return CreateEndpointFinder(new DefaultEndpointDataSource(endpoints));
} }
private CustomRouteValuesBasedEndpointFinder CreateEndpointFinder(params EndpointDataSource[] endpointDataSources) private CustomRouteValuesBasedEndpointFinder CreateEndpointFinder(params EndpointDataSource[] dataSources)
{ {
var objectPoolProvider = new DefaultObjectPoolProvider(); return new CustomRouteValuesBasedEndpointFinder(new CompositeEndpointDataSource(dataSources));
var objectPool = objectPoolProvider.Create(new UriBuilderContextPooledObjectPolicy());
return new CustomRouteValuesBasedEndpointFinder(
new CompositeEndpointDataSource(endpointDataSources),
objectPool);
} }
private RouteEndpoint CreateEndpoint( private RouteEndpoint CreateEndpoint(
@ -256,10 +246,8 @@ namespace Microsoft.AspNetCore.Routing
private class CustomRouteValuesBasedEndpointFinder : RouteValuesBasedEndpointFinder private class CustomRouteValuesBasedEndpointFinder : RouteValuesBasedEndpointFinder
{ {
public CustomRouteValuesBasedEndpointFinder( public CustomRouteValuesBasedEndpointFinder(CompositeEndpointDataSource dataSource)
CompositeEndpointDataSource endpointDataSource, : base(dataSource)
ObjectPool<UriBuildingContext> objectPool)
: base(endpointDataSource, objectPool)
{ {
} }