// 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.Text.Encodings.Web; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Matchers; using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Routing { internal class DefaultLinkGenerator : LinkGenerator { private readonly MatchProcessorFactory _matchProcessorFactory; private readonly ObjectPool _uriBuildingContextPool; private readonly ILogger _logger; public DefaultLinkGenerator( MatchProcessorFactory matchProcessorFactory, ObjectPool uriBuildingContextPool, ILogger logger) { _matchProcessorFactory = matchProcessorFactory; _uriBuildingContextPool = uriBuildingContextPool; _logger = logger; } public override string GetLink( HttpContext httpContext, IEnumerable endpoints, RouteValueDictionary explicitValues, RouteValueDictionary ambientValues) { if (TryGetLink(httpContext, endpoints, explicitValues, ambientValues, out var link)) { return link; } throw new InvalidOperationException("Could not find a matching endpoint to generate a link."); } public override bool TryGetLink( HttpContext httpContext, IEnumerable endpoints, RouteValueDictionary explicitValues, RouteValueDictionary ambientValues, out string link) { link = null; if (endpoints == null) { return false; } var matcherEndpoints = endpoints.OfType(); if (!matcherEndpoints.Any()) { //todo:log here return false; } foreach (var endpoint in matcherEndpoints) { link = GetLink(httpContext, endpoint, explicitValues, ambientValues); if (link != null) { return true; } } return false; } private string GetLink( HttpContext httpContext, MatcherEndpoint endpoint, RouteValueDictionary explicitValues, RouteValueDictionary ambientValues) { var templateBinder = new TemplateBinder( UrlEncoder.Default, _uriBuildingContextPool, new RouteTemplate(endpoint.RoutePattern), new RouteValueDictionary(endpoint.RoutePattern.Defaults)); var templateValuesResult = templateBinder.GetValues(ambientValues, explicitValues); if (templateValuesResult == null) { // We're missing one of the required values for this route. return null; } if (!Match(httpContext, endpoint, templateValuesResult.CombinedValues)) { return null; } return templateBinder.BindValues(templateValuesResult.AcceptedValues); } private bool Match(HttpContext httpContext, MatcherEndpoint endpoint, RouteValueDictionary routeValues) { if (routeValues == null) { throw new ArgumentNullException(nameof(routeValues)); } foreach (var kvp in endpoint.RoutePattern.Constraints) { var parameter = endpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok var constraintReferences = kvp.Value; for (var i = 0; i < constraintReferences.Count; i++) { var constraintReference = constraintReferences[i]; var matchProcessor = _matchProcessorFactory.Create(parameter, constraintReference); if (!matchProcessor.ProcessOutbound(httpContext, routeValues)) { return false; } } } return true; } } }