Changed api of LinkGenerator

This commit is contained in:
Kiran Challa 2018-07-30 15:44:37 -07:00 committed by Kiran Challa
parent 9cea167cfa
commit b177ba5309
5 changed files with 601 additions and 620 deletions

View File

@ -25,11 +25,11 @@ namespace RoutingSample.Web
{ {
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor)); options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor));
}); });
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)

View File

@ -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);
} }
} }

View File

@ -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.

View File

@ -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;
}
} }
} }