Changed api of LinkGenerator
This commit is contained in:
parent
9cea167cfa
commit
b177ba5309
|
|
@ -28,8 +28,8 @@ namespace RoutingSample.Web
|
||||||
|
|
||||||
services.Configure<EndpointOptions>(options =>
|
services.Configure<EndpointOptions>(options =>
|
||||||
{
|
{
|
||||||
options.DataSources.Add(new DefaultEndpointDataSource(new[]
|
options.DataSources.Add(new DefaultEndpointDataSource(new[]
|
||||||
{
|
{
|
||||||
new MatcherEndpoint((next) => (httpContext) =>
|
new MatcherEndpoint((next) => (httpContext) =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
|
@ -83,7 +83,7 @@ namespace RoutingSample.Web
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"withoptionalconstraints"),
|
"withoptionalconstraints"),
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,157 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public abstract class LinkGenerator
|
public abstract class LinkGenerator
|
||||||
{
|
{
|
||||||
public abstract bool TryGetLink(LinkGeneratorContext context, out string link);
|
public string GetLink(object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName: null, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract string GetLink(LinkGeneratorContext context);
|
public string GetLink(object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName: null, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLink(object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName: null, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLink(object values, LinkOptions options, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName: null, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLink(HttpContext httpContext, object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext, routeName: null, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLink(HttpContext httpContext, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext, routeName: null, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLink(HttpContext httpContext, object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext, routeName: null, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLink(HttpContext httpContext, object values, LinkOptions options, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext, routeName: null, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLink(string routeName, object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLink(string routeName, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLink(string routeName, object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext: null, routeName, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLink(string routeName, object values, LinkOptions options, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext: null, routeName, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLink(HttpContext httpContext, string routeName, object values)
|
||||||
|
{
|
||||||
|
return GetLink(httpContext, routeName, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLink(HttpContext httpContext, string routeName, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLink(httpContext, routeName, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool TryGetLink(
|
||||||
|
HttpContext httpContext,
|
||||||
|
string routeName,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link);
|
||||||
|
|
||||||
|
public string GetLinkByAddress<TAddress>(TAddress address, object values)
|
||||||
|
{
|
||||||
|
return GetLinkByAddress(address, httpContext: null, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLinkByAddress<TAddress>(TAddress address, object values, out string link)
|
||||||
|
{
|
||||||
|
return TryGetLinkByAddress(address, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLinkByAddress<TAddress>(TAddress address, object values, LinkOptions options)
|
||||||
|
{
|
||||||
|
return GetLinkByAddress(address, httpContext: null, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLinkByAddress<TAddress>(
|
||||||
|
TAddress address,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
|
{
|
||||||
|
return TryGetLinkByAddress(address, httpContext: null, values, options, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLinkByAddress<TAddress>(TAddress address, HttpContext httpContext, object values)
|
||||||
|
{
|
||||||
|
return GetLinkByAddress(address, httpContext, values, options: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLinkByAddress<TAddress>(
|
||||||
|
TAddress address,
|
||||||
|
HttpContext httpContext,
|
||||||
|
object values,
|
||||||
|
out string link)
|
||||||
|
{
|
||||||
|
return TryGetLinkByAddress(address, httpContext, values, options: null, out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLinkByAddress<TAddress>(
|
||||||
|
TAddress address,
|
||||||
|
HttpContext httpContext,
|
||||||
|
object values,
|
||||||
|
LinkOptions options)
|
||||||
|
{
|
||||||
|
if (TryGetLinkByAddress(address, httpContext, values, options, out var link))
|
||||||
|
{
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool TryGetLinkByAddress<TAddress>(
|
||||||
|
TAddress address,
|
||||||
|
HttpContext httpContext,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,10 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public class LinkGeneratorContext
|
public class LinkOptions
|
||||||
{
|
{
|
||||||
public HttpContext HttpContext { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<Endpoint> Endpoints { get; set; }
|
|
||||||
|
|
||||||
public RouteValueDictionary ExplicitValues { get; set; }
|
|
||||||
|
|
||||||
public RouteValueDictionary AmbientValues { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
|
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
|
||||||
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
|
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
|
||||||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -19,60 +20,106 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
||||||
private readonly MatchProcessorFactory _matchProcessorFactory;
|
private readonly MatchProcessorFactory _matchProcessorFactory;
|
||||||
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
|
||||||
private readonly RouteOptions _options;
|
|
||||||
private readonly ILogger<DefaultLinkGenerator> _logger;
|
private readonly ILogger<DefaultLinkGenerator> _logger;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly RouteOptions _options;
|
||||||
|
|
||||||
public DefaultLinkGenerator(
|
public DefaultLinkGenerator(
|
||||||
MatchProcessorFactory matchProcessorFactory,
|
MatchProcessorFactory matchProcessorFactory,
|
||||||
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
||||||
IOptions<RouteOptions> routeOptions,
|
IOptions<RouteOptions> routeOptions,
|
||||||
ILogger<DefaultLinkGenerator> logger)
|
ILogger<DefaultLinkGenerator> logger,
|
||||||
|
IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_matchProcessorFactory = matchProcessorFactory;
|
_matchProcessorFactory = matchProcessorFactory;
|
||||||
_uriBuildingContextPool = uriBuildingContextPool;
|
_uriBuildingContextPool = uriBuildingContextPool;
|
||||||
_options = routeOptions.Value;
|
_options = routeOptions.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetLink(LinkGeneratorContext context)
|
public override bool TryGetLink(
|
||||||
|
HttpContext httpContext,
|
||||||
|
string routeName,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
{
|
{
|
||||||
if (context == null)
|
return TryGetLinkByRouteValues(
|
||||||
{
|
httpContext,
|
||||||
throw new ArgumentNullException(nameof(context));
|
routeName,
|
||||||
}
|
values,
|
||||||
|
options,
|
||||||
if (TryGetLink(context, out var link))
|
out link);
|
||||||
{
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool TryGetLink(LinkGeneratorContext context, out string link)
|
public override bool TryGetLinkByAddress<TAddress>(
|
||||||
|
TAddress address,
|
||||||
|
HttpContext httpContext,
|
||||||
|
object values,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
{
|
{
|
||||||
if (context == null)
|
return TryGetLinkByAddressInternal(
|
||||||
{
|
address,
|
||||||
throw new ArgumentNullException(nameof(context));
|
httpContext,
|
||||||
}
|
explicitValues: values,
|
||||||
|
ambientValues: GetAmbientValues(httpContext),
|
||||||
|
options,
|
||||||
|
out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
address,
|
||||||
|
httpContext,
|
||||||
|
explicitValues: values,
|
||||||
|
ambientValues: ambientValues,
|
||||||
|
options,
|
||||||
|
out link);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetLinkByAddressInternal<TAddress>(
|
||||||
|
TAddress address,
|
||||||
|
HttpContext httpContext,
|
||||||
|
object explicitValues,
|
||||||
|
RouteValueDictionary ambientValues,
|
||||||
|
LinkOptions options,
|
||||||
|
out string link)
|
||||||
|
{
|
||||||
link = null;
|
link = null;
|
||||||
|
|
||||||
if (context.Endpoints == null)
|
var endpointFinder = _serviceProvider.GetRequiredService<IEndpointFinder<TAddress>>();
|
||||||
|
var endpoints = endpointFinder.FindEndpoints(address);
|
||||||
|
if (endpoints == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var matcherEndpoints = context.Endpoints.OfType<MatcherEndpoint>();
|
var matcherEndpoints = endpoints.OfType<MatcherEndpoint>();
|
||||||
if (!matcherEndpoints.Any())
|
if (!matcherEndpoints.Any())
|
||||||
{
|
{
|
||||||
//todo:log here
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var endpoint in matcherEndpoints)
|
foreach (var endpoint in matcherEndpoints)
|
||||||
{
|
{
|
||||||
link = GetLink(endpoint, context);
|
link = GetLink(endpoint);
|
||||||
if (link != null)
|
if (link != null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -80,34 +127,33 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
private string GetLink(MatcherEndpoint endpoint, LinkGeneratorContext context)
|
string GetLink(MatcherEndpoint endpoint)
|
||||||
{
|
|
||||||
var templateBinder = new TemplateBinder(
|
|
||||||
UrlEncoder.Default,
|
|
||||||
_uriBuildingContextPool,
|
|
||||||
new RouteTemplate(endpoint.RoutePattern),
|
|
||||||
new RouteValueDictionary(endpoint.RoutePattern.Defaults));
|
|
||||||
|
|
||||||
var templateValuesResult = templateBinder.GetValues(
|
|
||||||
ambientValues: context.AmbientValues,
|
|
||||||
explicitValues: context.ExplicitValues,
|
|
||||||
endpoint.RequiredValues.Keys);
|
|
||||||
|
|
||||||
if (templateValuesResult == null)
|
|
||||||
{
|
{
|
||||||
// We're missing one of the required values for this route.
|
var templateBinder = new TemplateBinder(
|
||||||
return null;
|
UrlEncoder.Default,
|
||||||
}
|
_uriBuildingContextPool,
|
||||||
|
new RouteTemplate(endpoint.RoutePattern),
|
||||||
|
new RouteValueDictionary(endpoint.RoutePattern.Defaults));
|
||||||
|
|
||||||
if (!MatchesConstraints(context.HttpContext, endpoint, templateValuesResult.CombinedValues))
|
var templateValuesResult = templateBinder.GetValues(
|
||||||
{
|
ambientValues: ambientValues,
|
||||||
return null;
|
explicitValues: new RouteValueDictionary(explicitValues),
|
||||||
}
|
requiredKeys: endpoint.RequiredValues.Keys);
|
||||||
|
if (templateValuesResult == null)
|
||||||
|
{
|
||||||
|
// We're missing one of the required values for this route.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var url = templateBinder.BindValues(templateValuesResult.AcceptedValues);
|
if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
|
||||||
return Normalize(context, url);
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = templateBinder.BindValues(templateValuesResult.AcceptedValues);
|
||||||
|
return Normalize(url, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MatchesConstraints(
|
private bool MatchesConstraints(
|
||||||
|
|
@ -138,13 +184,11 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Normalize(LinkGeneratorContext context, string url)
|
private string Normalize(string url, LinkOptions options)
|
||||||
{
|
{
|
||||||
var lowercaseUrls = context.LowercaseUrls.HasValue ? context.LowercaseUrls.Value : _options.LowercaseUrls;
|
var lowercaseUrls = options?.LowercaseUrls ?? _options.LowercaseUrls;
|
||||||
var lowercaseQueryStrings = context.LowercaseQueryStrings.HasValue ?
|
var lowercaseQueryStrings = options?.LowercaseQueryStrings ?? _options.LowercaseQueryStrings;
|
||||||
context.LowercaseQueryStrings.Value : _options.LowercaseQueryStrings;
|
var appendTrailingSlash = options?.AppendTrailingSlash ?? _options.AppendTrailingSlash;
|
||||||
var appendTrailingSlash = context.AppendTrailingSlash.HasValue ?
|
|
||||||
context.AppendTrailingSlash.Value : _options.AppendTrailingSlash;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(url) && (lowercaseUrls || appendTrailingSlash))
|
if (!string.IsNullOrEmpty(url) && (lowercaseUrls || appendTrailingSlash))
|
||||||
{
|
{
|
||||||
|
|
@ -179,5 +223,18 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
if (httpContext != null)
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IEndpointFeature>();
|
||||||
|
if (feature != null)
|
||||||
|
{
|
||||||
|
return feature.Values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue