diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs index 82da67cd47..5f99df178d 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Routing /// Defines a contract to generate a URL from a template. /// /// - /// A can be created from + /// A can be created from /// by supplying an address value which has matching endpoints. The returned /// will be bound to the endpoints matching the address that was originally provided. /// @@ -20,6 +20,9 @@ namespace Microsoft.AspNetCore.Routing /// /// The associated with the current request. /// The route values. Used to expand parameters in the route template. Optional. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -29,6 +32,7 @@ namespace Microsoft.AspNetCore.Routing public abstract string GetPath( HttpContext httpContext, object values, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default); @@ -54,6 +58,15 @@ namespace Microsoft.AspNetCore.Routing /// /// The associated with the current request. /// The route values. Used to expand parameters in the route template. Optional. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -63,6 +76,9 @@ namespace Microsoft.AspNetCore.Routing public abstract string GetUri( HttpContext httpContext, object values, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default); diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplateOptions.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplateOptions.cs new file mode 100644 index 0000000000..18eb0f8c7c --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplateOptions.cs @@ -0,0 +1,17 @@ +// 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. + +namespace Microsoft.AspNetCore.Routing +{ + /// + /// Contains options for creating a . + /// + public class LinkGenerationTemplateOptions + { + /// + /// Gets or sets a value indicating whether the template will use route values from the current request + /// when generating a URI. + /// + public bool UseAmbientValues { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs index e8659b3577..9f6bb0640f 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs @@ -25,12 +25,16 @@ namespace Microsoft.AspNetCore.Routing public abstract class LinkGenerator { /// - /// Generates a URI with an absolute path based on the provided values. + /// Generates a URI with an absolute path based on the provided values and . /// /// The address type. /// The associated with the current request. /// The address value. Used to resolve endpoints. /// The route values. Used to expand parameters in the route template. Optional. + /// The values associated with the current request. Optional. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -41,6 +45,8 @@ namespace Microsoft.AspNetCore.Routing HttpContext httpContext, TAddress address, RouteValueDictionary values, + RouteValueDictionary ambientValues = default, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default); @@ -65,12 +71,22 @@ namespace Microsoft.AspNetCore.Routing LinkOptions options = default); /// - /// Generates an absolute URI based on the provided values. + /// Generates an absolute URI based on the provided values and . /// /// The address type. /// The associated with the current request. /// The address value. Used to resolve endpoints. /// The route values. Used to expand parameters in the route template. Optional. + /// The values associated with the current request. Optional. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -81,6 +97,10 @@ namespace Microsoft.AspNetCore.Routing HttpContext httpContext, TAddress address, RouteValueDictionary values, + RouteValueDictionary ambientValues = default, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default); @@ -113,9 +133,10 @@ namespace Microsoft.AspNetCore.Routing /// /// The address type. /// The address value. Used to resolve endpoints. + /// Options for the created . /// /// A if one or more endpoints matching the address can be found, otherwise null. /// - public abstract LinkGenerationTemplate GetTemplateByAddress(TAddress address); + public abstract LinkGenerationTemplate GetTemplateByAddress(TAddress address, LinkGenerationTemplateOptions options = null); } } diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs index b1beb67b1b..d1f83ca6e0 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs @@ -1,29 +1,33 @@ // 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; using System.Collections.Generic; -using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Routing { internal sealed class DefaultLinkGenerationTemplate : LinkGenerationTemplate { - public DefaultLinkGenerationTemplate(DefaultLinkGenerator linkGenerator, List endpoints) + public DefaultLinkGenerationTemplate(DefaultLinkGenerator linkGenerator, List endpoints, LinkGenerationTemplateOptions options) { LinkGenerator = linkGenerator; Endpoints = endpoints; + Options = options; } public DefaultLinkGenerator LinkGenerator { get; } public List Endpoints { get; } + public LinkGenerationTemplateOptions Options { get; } + public override string GetPath( HttpContext httpContext, object values, + PathString? pathBase = default, FragmentString fragment = default, - LinkOptions options = null) + LinkOptions options = default) { if (httpContext == null) { @@ -32,9 +36,9 @@ namespace Microsoft.AspNetCore.Routing return LinkGenerator.GetPathByEndpoints( Endpoints, - DefaultLinkGenerator.GetAmbientValues(httpContext), new RouteValueDictionary(values), - httpContext.Request.PathBase, + GetAmbientValues(httpContext), + pathBase ?? httpContext.Request.PathBase, fragment, options); } @@ -43,22 +47,25 @@ namespace Microsoft.AspNetCore.Routing object values, PathString pathBase = default, FragmentString fragment = default, - LinkOptions options = null) + LinkOptions options = default) { return LinkGenerator.GetPathByEndpoints( Endpoints, - ambientValues: null, new RouteValueDictionary(values), - pathBase, - fragment, - options); + ambientValues: null, + pathBase: pathBase, + fragment: fragment, + options: options); } public override string GetUri( HttpContext httpContext, object values, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, FragmentString fragment = default, - LinkOptions options = null) + LinkOptions options = default) { if (httpContext == null) { @@ -67,11 +74,11 @@ namespace Microsoft.AspNetCore.Routing return LinkGenerator.GetUriByEndpoints( Endpoints, - DefaultLinkGenerator.GetAmbientValues(httpContext), new RouteValueDictionary(values), - httpContext.Request.Scheme, - httpContext.Request.Host, - httpContext.Request.PathBase, + GetAmbientValues(httpContext), + scheme ?? httpContext.Request.Scheme, + host ?? httpContext.Request.Host, + pathBase ?? httpContext.Request.PathBase, fragment, options); } @@ -82,17 +89,32 @@ namespace Microsoft.AspNetCore.Routing HostString host, PathString pathBase = default, FragmentString fragment = default, - LinkOptions options = null) + LinkOptions options = default) { + if (string.IsNullOrEmpty(scheme)) + { + throw new ArgumentException("A scheme must be provided.", nameof(scheme)); + } + + if (!host.HasValue) + { + throw new ArgumentException("A host must be provided.", nameof(host)); + } + return LinkGenerator.GetUriByEndpoints( Endpoints, - ambientValues: null, new RouteValueDictionary(values), - scheme, - host, - pathBase, - fragment, - options); + ambientValues: null, + scheme: scheme, + host: host, + pathBase: pathBase, + fragment: fragment, + options: options); + } + + private RouteValueDictionary GetAmbientValues(HttpContext httpContext) + { + return (Options?.UseAmbientValues ?? false) ? DefaultLinkGenerator.GetAmbientValues(httpContext) : null; } } } diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs index c303c5dcb2..7bd41daeaa 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs @@ -73,6 +73,8 @@ namespace Microsoft.AspNetCore.Routing HttpContext httpContext, TAddress address, RouteValueDictionary values, + RouteValueDictionary ambientValues = default, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = null) { @@ -89,9 +91,9 @@ namespace Microsoft.AspNetCore.Routing return GetPathByEndpoints( endpoints, - GetAmbientValues(httpContext), values, - httpContext.Request.PathBase, + ambientValues, + pathBase ?? httpContext.Request.PathBase, fragment, options); } @@ -111,17 +113,21 @@ namespace Microsoft.AspNetCore.Routing return GetPathByEndpoints( endpoints, - ambientValues: null, values, - pathBase, - fragment, - options); + ambientValues: null, + pathBase: pathBase, + fragment: fragment, + options: options); } public override string GetUriByAddress( HttpContext httpContext, TAddress address, RouteValueDictionary values, + RouteValueDictionary ambientValues = default, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = null) { @@ -138,11 +144,11 @@ namespace Microsoft.AspNetCore.Routing return GetUriByEndpoints( endpoints, - GetAmbientValues(httpContext), values, - httpContext.Request.Scheme, - httpContext.Request.Host, - httpContext.Request.PathBase, + ambientValues, + scheme ?? httpContext.Request.Scheme, + host ?? httpContext.Request.Host, + pathBase ?? httpContext.Request.PathBase, fragment, options); } @@ -156,9 +162,14 @@ namespace Microsoft.AspNetCore.Routing FragmentString fragment = default, LinkOptions options = null) { + if (string.IsNullOrEmpty(scheme)) + { + throw new ArgumentException("A scheme must be provided.", nameof(scheme)); + } + if (!host.HasValue) { - throw new ArgumentNullException(nameof(host)); + throw new ArgumentException("A host must be provided.", nameof(host)); } var endpoints = GetEndpoints(address); @@ -169,16 +180,16 @@ namespace Microsoft.AspNetCore.Routing return GetUriByEndpoints( endpoints, - ambientValues: null, values, - scheme, - host, - pathBase, - fragment, - options); + ambientValues: null, + scheme: scheme, + host: host, + pathBase: pathBase, + fragment: fragment, + options: options); } - public override LinkGenerationTemplate GetTemplateByAddress(TAddress address) + public override LinkGenerationTemplate GetTemplateByAddress(TAddress address, LinkGenerationTemplateOptions options = default) { var endpoints = GetEndpoints(address); if (endpoints.Count == 0) @@ -186,7 +197,7 @@ namespace Microsoft.AspNetCore.Routing return null; } - return new DefaultLinkGenerationTemplate(this, endpoints); + return new DefaultLinkGenerationTemplate(this, endpoints, options); } private List GetEndpoints(TAddress address) @@ -209,8 +220,8 @@ namespace Microsoft.AspNetCore.Routing // Also called from DefaultLinkGenerationTemplate public string GetPathByEndpoints( List endpoints, - RouteValueDictionary ambientValues, RouteValueDictionary values, + RouteValueDictionary ambientValues, PathString pathBase, FragmentString fragment, LinkOptions options) @@ -220,11 +231,11 @@ namespace Microsoft.AspNetCore.Routing var endpoint = endpoints[i]; if (TryProcessTemplate( httpContext: null, - endpoint, + endpoint: endpoint, + values: values, ambientValues: ambientValues, - values, - options, - out var result)) + options: options, + result: out var result)) { var uri = UriHelper.BuildRelative( pathBase, @@ -243,8 +254,8 @@ namespace Microsoft.AspNetCore.Routing // Also called from DefaultLinkGenerationTemplate public string GetUriByEndpoints( List endpoints, - RouteValueDictionary ambientValues, RouteValueDictionary values, + RouteValueDictionary ambientValues, string scheme, HostString host, PathString pathBase, @@ -256,11 +267,11 @@ namespace Microsoft.AspNetCore.Routing var endpoint = endpoints[i]; if (TryProcessTemplate( httpContext: null, - endpoint, + endpoint: endpoint, + values: values, ambientValues: ambientValues, - values, - options, - out var result)) + options: options, + result: out var result)) { var uri = UriHelper.BuildAbsolute( scheme, @@ -323,19 +334,19 @@ namespace Microsoft.AspNetCore.Routing internal bool TryProcessTemplate( HttpContext httpContext, RouteEndpoint endpoint, + RouteValueDictionary values, RouteValueDictionary ambientValues, - RouteValueDictionary explicitValues, LinkOptions options, out (PathString path, QueryString query) result) { var templateBinder = GetTemplateBinder(endpoint); - var templateValuesResult = templateBinder.GetValues(ambientValues, explicitValues); + var templateValuesResult = templateBinder.GetValues(ambientValues, values); if (templateValuesResult == null) { // We're missing one of the required values for this route. result = default; - Log.TemplateFailedRequiredValues(_logger, endpoint, ambientValues, explicitValues); + Log.TemplateFailedRequiredValues(_logger, endpoint, ambientValues, values); return false; } diff --git a/src/Microsoft.AspNetCore.Routing/LinkGeneratorEndpointNameAddressExtensions.cs b/src/Microsoft.AspNetCore.Routing/LinkGeneratorEndpointNameAddressExtensions.cs index ab65be757e..a5904d1071 100644 --- a/src/Microsoft.AspNetCore.Routing/LinkGeneratorEndpointNameAddressExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/LinkGeneratorEndpointNameAddressExtensions.cs @@ -11,6 +11,11 @@ namespace Microsoft.AspNetCore.Routing /// public static class LinkGeneratorEndpointNameAddressExtensions { + private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions() + { + UseAmbientValues = false, + }; + /// /// Generates a URI with an absolute path based on the provided values. /// @@ -18,6 +23,9 @@ namespace Microsoft.AspNetCore.Routing /// The associated with the current request. /// The endpoint name. Used to resolve endpoints. /// The route values. Used to expand parameters in the route template. Optional. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -29,6 +37,7 @@ namespace Microsoft.AspNetCore.Routing HttpContext httpContext, string endpointName, object values, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default) { @@ -37,12 +46,24 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(generator)); } + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + if (endpointName == null) { throw new ArgumentNullException(nameof(endpointName)); } - return generator.GetPathByAddress(httpContext, endpointName, new RouteValueDictionary(values), fragment, options); + return generator.GetPathByAddress( + httpContext, + endpointName, + new RouteValueDictionary(values), + ambientValues: null, + pathBase, + fragment, + options); } /// @@ -86,6 +107,15 @@ namespace Microsoft.AspNetCore.Routing /// The associated with the current request. /// The endpoint name. Used to resolve endpoints. /// The route values. Used to expand parameters in the route template. Optional. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -97,6 +127,9 @@ namespace Microsoft.AspNetCore.Routing HttpContext httpContext, string endpointName, object values, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default) { @@ -105,12 +138,26 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(generator)); } + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + if (endpointName == null) { throw new ArgumentNullException(nameof(endpointName)); } - return generator.GetUriByAddress(httpContext, endpointName, new RouteValueDictionary(values), fragment, options); + return generator.GetUriByAddress( + httpContext, + endpointName, + new RouteValueDictionary(values), + ambientValues: null, + scheme, + host, + pathBase, + fragment, + options); } /// @@ -147,7 +194,17 @@ namespace Microsoft.AspNetCore.Routing { throw new ArgumentNullException(nameof(endpointName)); } - + + if (string.IsNullOrEmpty(scheme)) + { + throw new ArgumentException("A scheme must be provided.", nameof(scheme)); + } + + if (!host.HasValue) + { + throw new ArgumentException("A host must be provided.", nameof(host)); + } + return generator.GetUriByAddress(endpointName, new RouteValueDictionary(values), scheme, host, pathBase, fragment, options); } @@ -171,7 +228,7 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(endpointName)); } - return generator.GetTemplateByAddress(endpointName); + return generator.GetTemplateByAddress(endpointName, _templateOptions); } } } diff --git a/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs b/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs index 3f8014f8e2..a2bffc01ef 100644 --- a/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs @@ -11,6 +11,11 @@ namespace Microsoft.AspNetCore.Routing /// public static class LinkGeneratorRouteValuesAddressExtensions { + private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions() + { + UseAmbientValues = true, + }; + /// /// Generates a URI with an absolute path based on the provided values. /// @@ -18,6 +23,9 @@ namespace Microsoft.AspNetCore.Routing /// The associated with the current request. /// The route name. Used to resolve endpoints. Optional. /// The route values. Used to resolve endpoints and expand parameters in the route template. Optional. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -29,6 +37,7 @@ namespace Microsoft.AspNetCore.Routing HttpContext httpContext, string routeName, object values, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default) { @@ -37,8 +46,20 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(generator)); } + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + var address = CreateAddress(httpContext, routeName, values); - return generator.GetPathByAddress(httpContext, address, address.ExplicitValues, fragment, options); + return generator.GetPathByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + pathBase, + fragment, + options); } /// @@ -78,6 +99,15 @@ namespace Microsoft.AspNetCore.Routing /// The associated with the current request. /// The route name. Used to resolve endpoints. Optional. /// The route values. Used to resolve endpoints and expand parameters in the route template. Optional. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -89,6 +119,9 @@ namespace Microsoft.AspNetCore.Routing HttpContext httpContext, string routeName, object values, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, FragmentString fragment = default, LinkOptions options = default) { @@ -97,8 +130,22 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(generator)); } + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + var address = CreateAddress(httpContext: null, routeName, values); - return generator.GetUriByAddress(httpContext, address, address.ExplicitValues, fragment, options); + return generator.GetUriByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + scheme, + host, + pathBase, + fragment, + options); } /// @@ -155,9 +202,9 @@ namespace Microsoft.AspNetCore.Routing } var address = CreateAddress(httpContext: null, routeName, values); - return generator.GetTemplateByAddress(address); + return generator.GetTemplateByAddress(address, _templateOptions); } - + private static RouteValuesAddress CreateAddress(HttpContext httpContext, string routeName, object values) { return new RouteValuesAddress() diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGenerationTemplateTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGenerationTemplateTest.cs index f76eec2f75..12f797fdea 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGenerationTemplateTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGenerationTemplateTest.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Routing var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}"); var linkGenerator = CreateLinkGenerator(); - var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }); + var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }, options: null); // Act var path = template.GetPath( @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Routing var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}"); var linkGenerator = CreateLinkGenerator(); - var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }); + var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }, options: null); var httpContext = CreateHttpContext(); httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); @@ -52,8 +52,8 @@ namespace Microsoft.AspNetCore.Routing var path = template.GetPath( httpContext, values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }), - new FragmentString("#Fragment?"), - new LinkOptions() { AppendTrailingSlash = true, }); + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); // Assert Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex/?query=some%3Fquery#Fragment?", path); @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Routing var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}"); var linkGenerator = CreateLinkGenerator(); - var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }); + var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }, options: null); // Act var path = template.GetUri( @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Routing var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}"); var linkGenerator = CreateLinkGenerator(); - var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }); + var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }, options: null); var httpContext = CreateHttpContext(); httpContext.Request.Scheme = "http"; @@ -101,22 +101,25 @@ namespace Microsoft.AspNetCore.Routing var uri = template.GetUri( httpContext, values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }), - new FragmentString("#Fragment?"), - new LinkOptions() { AppendTrailingSlash = true, }); + fragment: new FragmentString("#Fragment?"), + options: 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() + public void GetPath_WithHttpContext_IncludesAmbientValues_WhenUseAmbientValuesIsTrue() { // 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() { endpoint1, endpoint2, }); + var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }, options: new LinkGenerationTemplateOptions() + { + UseAmbientValues = true, + }); var httpContext = CreateHttpContext(new { controller = "Home", }); httpContext.Request.Scheme = "http"; @@ -130,14 +133,41 @@ namespace Microsoft.AspNetCore.Routing } [Fact] - public void GetUri_WithHttpContext_IncludesAmbientValues() + public void GetPath_WithHttpContext_ExcludesAmbientValues_WhenUseAmbientValuesIsFalse() { // 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() { endpoint1, endpoint2, }); + var template = new DefaultLinkGenerationTemplate(linkGenerator, new List() { endpoint1, endpoint2, }, options: new LinkGenerationTemplateOptions() + { + UseAmbientValues = false, + }); + + 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.Null(uri); + } + + [Fact] + public void GetUri_WithHttpContext_IncludesAmbientValues_WhenUseAmbientValuesIsTrue() + { + // 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() { endpoint1, endpoint2, }, options: new LinkGenerationTemplateOptions() + { + UseAmbientValues = true, + }); var httpContext = CreateHttpContext(new { controller = "Home", }); httpContext.Request.Scheme = "http"; @@ -149,5 +179,29 @@ namespace Microsoft.AspNetCore.Routing // Assert Assert.Equal("http://example.com/Home/Index", uri); } + + [Fact] + public void GetUri_WithHttpContext_ExcludesAmbientValues_WhenUseAmbientValuesIsFalse() + { + // 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() { endpoint1, endpoint2, }, options: new LinkGenerationTemplateOptions() + { + UseAmbientValues = false, + }); + + 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.Null(uri); + } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs index ccda24efde..4f6792b47f 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs @@ -25,10 +25,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: null, endpoint: endpoint, + values: new RouteValueDictionary(new { p1 = "Home", p3 = "bar", }), ambientValues: null, - explicitValues: new RouteValueDictionary(new { p1 = "Home", p3 = "bar", }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -50,10 +50,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { path = routeValue, }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { path = routeValue, }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -82,10 +82,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { path = routeValue, }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { path = routeValue, }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -105,10 +105,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { path = "a/b b1/c c1" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { path = "a/b b1/c c1" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -128,10 +128,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { name = "name with %special #characters" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { name = "name with %special #characters" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -151,10 +151,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { color = new List { "red", "green", "blue" } }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { color = new List { "red", "green", "blue" } }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -174,10 +174,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { items = new List { 10, 20, 30 } }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { items = new List { 10, 20, 30 } }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -197,10 +197,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { color = new List { } }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { color = new List { } }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -220,10 +220,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { page = 1, color = new List { "red", "green", "blue" }, message = "textfortest" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { page = 1, color = new List { "red", "green", "blue" }, message = "textfortest" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -243,10 +243,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -266,10 +266,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -290,10 +290,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { id = "18" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { id = "18" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -314,10 +314,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { id = "18" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { id = "18" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -341,10 +341,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, - ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "П" }), // Cryillic uppercase Pe + values: new RouteValueDictionary(new { action = "П" }), + ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), // Cryillic uppercase Pe options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -371,10 +371,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -396,10 +396,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -421,10 +421,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -446,13 +446,13 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "InDex" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "InDex" }), options: new LinkOptions { LowercaseUrls = false }, - out var result); + result: out var result); // Assert @@ -475,13 +475,13 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "InDex" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "InDex" }), options: new LinkOptions() { LowercaseUrls = true }, - out var result); + result: out var result); // Assert Assert.True(success); @@ -503,14 +503,14 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), options: new LinkOptions { LowercaseUrls = false, LowercaseQueryStrings = false }, - out var result); + result: out var result); // Assert Assert.True(success); @@ -532,14 +532,14 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), options: new LinkOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, }, - out var result); + result: out var result); // Assert Assert.True(success); @@ -561,10 +561,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index" }), options: new LinkOptions() { AppendTrailingSlash = true, }, - out var result); + result: out var result); // Assert Assert.True(success); @@ -587,10 +587,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { p1 = "abcd" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { p1 = "abcd" }), options: null, - out var result); + result: out var result); // Assert Assert.False(success); @@ -611,10 +611,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -637,10 +637,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { p1 = "abcd" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { p1 = "abcd" }), options: null, - out var result); + result: out var result); // Assert Assert.False(success); @@ -661,10 +661,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -698,10 +698,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -733,10 +733,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Store" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Store" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -766,10 +766,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Store" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Store" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -798,10 +798,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { controller = "Shopping" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { controller = "Shopping" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -832,10 +832,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Store", thirdthing = "13" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Store", thirdthing = "13" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -862,10 +862,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 4 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 4 }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -888,10 +888,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }), options: null, - out var result); + result: out var result); // Assert Assert.False(success); @@ -914,10 +914,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 98 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 98 }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -940,10 +940,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -966,10 +966,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }), options: null, - out var result); + result: out var result); // Assert Assert.False(success); @@ -992,10 +992,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 14 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 14 }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1020,10 +1020,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 50 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 50 }), options: null, - out var result); + result: out var result); // Assert Assert.False(success); @@ -1045,10 +1045,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1068,10 +1068,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1091,10 +1091,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1116,10 +1116,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1141,10 +1141,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1164,10 +1164,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products", format = "json" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products", format = "json" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1188,10 +1188,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1211,10 +1211,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home" }), options: null, - out var result); + result: out var result); Assert.True(success); @@ -1234,10 +1234,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { action = "Index", controller = "Home" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1257,10 +1257,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1280,10 +1280,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1303,10 +1303,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1407,10 +1407,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(explicitValues), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(explicitValues), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1435,10 +1435,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { c = "Products", a = "Edit" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { c = "Products", a = "Edit" }), options: null, - out var result); + result: out var result); // Assert Assert.True(success); @@ -1463,10 +1463,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(new { c = "Products", a = "List" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(new { c = "Products", a = "List" }), options: null, - out var result); + result: out var result); // Assert Assert.False(success); @@ -1571,10 +1571,10 @@ namespace Microsoft.AspNetCore.Routing var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, + values: new RouteValueDictionary(explicitValues), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), - explicitValues: new RouteValueDictionary(explicitValues), options: null, - out var result); + result: out var result); // Assert Assert.False(success); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs index f64297e791..9f0bda2a18 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -370,7 +370,7 @@ namespace Microsoft.AspNetCore.Routing httpContext, 1, values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }), - new FragmentString("#Fragment?")); + fragment: new FragmentString("#Fragment?")); // Assert Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path); @@ -419,7 +419,7 @@ namespace Microsoft.AspNetCore.Routing httpContext, 1, values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }), - new FragmentString("#Fragment?")); + fragment: new FragmentString("#Fragment?")); // Assert Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", uri); @@ -434,12 +434,16 @@ namespace Microsoft.AspNetCore.Routing var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); - var httpContext = CreateHttpContext(new { controller = "Home", }); + var httpContext = CreateHttpContext(); httpContext.Request.Scheme = "http"; httpContext.Request.Host = new HostString("example.com"); // Act - var uri = linkGenerator.GetPathByAddress(httpContext, 1, values: new RouteValueDictionary(new { action = "Index", })); + var uri = linkGenerator.GetPathByAddress( + httpContext, + 1, + values: new RouteValueDictionary(new { action = "Index", }), + ambientValues: new RouteValueDictionary(new { controller = "Home", })); // Assert Assert.Equal("/Home/Index", uri); @@ -454,17 +458,71 @@ namespace Microsoft.AspNetCore.Routing var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); - var httpContext = CreateHttpContext(new { controller = "Home", }); + var httpContext = CreateHttpContext(); httpContext.Request.Scheme = "http"; httpContext.Request.Host = new HostString("example.com"); // Act - var uri = linkGenerator.GetUriByAddress(httpContext, 1, values: new RouteValueDictionary(new { action = "Index", })); + var uri = linkGenerator.GetUriByAddress( + httpContext, + 1, + values: new RouteValueDictionary(new { action = "Index", }), + ambientValues: new RouteValueDictionary(new { controller = "Home", })); // Assert Assert.Equal("http://example.com/Home/Index", uri); } + [Fact] + public void GetPathByAddress_WithHttpContext_CanOverrideUriParts() + { + // Arrange + var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), }); + var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(); + httpContext.Request.PathBase = "/Foo"; + + // Act + var uri = linkGenerator.GetPathByAddress( + httpContext, + 1, + values: new RouteValueDictionary(new { action = "Index", controller= "Home", }), + pathBase: "/"); + + // Assert + Assert.Equal("/Home/Index", uri); + } + + [Fact] + public void GetUriByAddress_WithHttpContext_CanOverrideUriParts() + { + // Arrange + var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), }); + var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(); + httpContext.Request.Scheme = "http"; + httpContext.Request.Host = new HostString("example.com"); + httpContext.Request.PathBase = "/Foo"; + + // Act + var uri = linkGenerator.GetUriByAddress( + httpContext, + 1, + values: new RouteValueDictionary(new { action = "Index", controller = "Home", }), + scheme: "ftp", + host: new HostString("example.com:5000"), + pathBase: "/"); + + // Assert + Assert.Equal("ftp://example.com:5000/Home/Index", uri); + } + [Fact] public void GetTemplateByAddress_WithNoMatch_ReturnsNull() { diff --git a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorEndpointNameExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorEndpointNameExtensionsTest.cs index 0b57b598c7..ee16dc1666 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorEndpointNameExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorEndpointNameExtensionsTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Xunit; namespace Microsoft.AspNetCore.Routing @@ -16,6 +17,37 @@ namespace Microsoft.AspNetCore.Routing // Does not cover the EndpointNameEndpointFinder in detail. see EndpointNameEndpointFinderTest public class LinkGeneratorEndpointNameExtensionsTest : LinkGeneratorTestBase { + [Fact] + public void GetPathByName_WithHttpContext_DoesNotUseAmbientValues() + { + // Arrange + var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), }); + var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var feature = new EndpointFeature() + { + RouteValues = new RouteValueDictionary(new { p = "5", }) + }; + var httpContext = CreateHttpContext(); + httpContext.Features.Set(feature); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + var values = new { query = "some?query", }; + + // Act + var path = linkGenerator.GetPathByName( + httpContext, + endpointName: "name2", + values, + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Null(path); + } + [Fact] public void GetPathByName_WithoutHttpContext_WithPathBaseAndFragment() { @@ -58,8 +90,8 @@ namespace Microsoft.AspNetCore.Routing httpContext, endpointName: "name2", values, - new FragmentString("#Fragment?"), - new LinkOptions() { AppendTrailingSlash = true, }); + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); // Assert Assert.Equal("/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path); @@ -111,8 +143,8 @@ namespace Microsoft.AspNetCore.Routing httpContext, endpointName: "name2", values, - new FragmentString("#Fragment?"), - new LinkOptions() { AppendTrailingSlash = true, }); + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); // Assert Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", uri); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs index 17d5aa305a..57874816d6 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Xunit; namespace Microsoft.AspNetCore.Routing @@ -16,6 +17,41 @@ namespace Microsoft.AspNetCore.Routing // Does not cover the RouteValueBasedEndpointFinder in detail. see RouteValueBasedEndpointFinderTest public class LinkGeneratorRouteValuesAddressExtensionsTest : LinkGeneratorTestBase { + [Fact] + public void GetPathByRouteValues_WithHttpContext_UsesAmbientValues() + { + // Arrange + var endpoint1 = EndpointFactory.CreateRouteEndpoint( + "Home/Index/{id}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpoint2 = EndpointFactory.CreateRouteEndpoint( + "Home/Index/{id?}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var feature = new EndpointFeature() + { + RouteValues = new RouteValueDictionary(new { action = "Index", }) + }; + var httpContext = CreateHttpContext(); + httpContext.Features.Set(feature); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var path = linkGenerator.GetPathByRouteValues( + httpContext, + routeName: null, + values: new RouteValueDictionary(new { controller = "Home", query = "some?query" }), + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path); + } + [Fact] public void GetPathByRouteValues_WithoutHttpContext_WithPathBaseAndFragment() { @@ -66,8 +102,8 @@ namespace Microsoft.AspNetCore.Routing httpContext, routeName: null, values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }), - new FragmentString("#Fragment?"), - new LinkOptions() { AppendTrailingSlash = true, }); + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); // Assert Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path); @@ -127,8 +163,8 @@ namespace Microsoft.AspNetCore.Routing httpContext, routeName: null, values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }), - new FragmentString("#Fragment?"), - new LinkOptions() { AppendTrailingSlash = true, }); + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); // Assert Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri);