diff --git a/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs b/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs index eb055f7696..6242198a46 100644 --- a/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs +++ b/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs @@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Routing.Internal [DebuggerDisplay("{DebuggerDisplayString,nq}")] public class LinkGenerationDecisionTree { + // Fallback value for cases where the ambient values weren't provided. + // + // This is safe because we don't mutate the route values in here. + private static readonly RouteValueDictionary EmptyAmbientValues = new RouteValueDictionary(); + private readonly DecisionTreeNode _root; public LinkGenerationDecisionTree(IReadOnlyList entries) @@ -30,7 +35,7 @@ namespace Microsoft.AspNetCore.Routing.Internal if (_root.Matches.Count > 0 || _root.Criteria.Count > 0) { var results = new List(); - Walk(results, values, ambientValues, _root, isFallbackPath: false); + Walk(results, values, ambientValues ?? EmptyAmbientValues, _root, isFallbackPath: false); results.Sort(OutboundMatchResultComparer.Instance); return results; } diff --git a/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs b/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs index a2bffc01ef..d025679e41 100644 --- a/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/LinkGeneratorRouteValuesAddressExtensions.cs @@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(httpContext)); } - var address = CreateAddress(httpContext: null, routeName, values); + var address = CreateAddress(httpContext, routeName, values); return generator.GetUriByAddress( httpContext, address, diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Internal/LinkGenerationDecisionTreeTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Internal/LinkGenerationDecisionTreeTest.cs index f07296faba..94568c2cd8 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Internal/LinkGenerationDecisionTreeTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Internal/LinkGenerationDecisionTreeTest.cs @@ -14,6 +14,26 @@ namespace Microsoft.AspNetCore.Routing.Internal.Routing { public class LinkGenerationDecisionTreeTest { + [Fact] + public void GetMatches_AllowsNullAmbientValues() + { + // Arrange + var entries = new List(); + + var entry = CreateMatch(new { }); + entries.Add(entry); + + var tree = new LinkGenerationDecisionTree(entries); + + var context = CreateContext(new { }); + + // Act + var matches = tree.GetMatches(context.Values, ambientValues: null); + + // Assert + Assert.Same(entry, Assert.Single(matches).Match); + } + [Fact] public void SelectSingleEntry_NoCriteria() { diff --git a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs index 57874816d6..57f530cdb8 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorRouteValuesAddressExtensionsTest.cs @@ -170,6 +170,38 @@ namespace Microsoft.AspNetCore.Routing Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri); } + [Fact] + public void GetUriByRouteValues_WithHttpContext_CanUseAmbientValues() + { + // 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 httpContext = CreateHttpContext(new { controller = "Home", }); + httpContext.Request.Scheme = "http"; + httpContext.Request.Host = new HostString("example.com"); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var uri = linkGenerator.GetUriByRouteValues( + httpContext, + routeName: null, + values: new RouteValueDictionary(new { action = "Index", query = "some?query" }), + 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); + } + [Fact] public void GetTemplateByRouteValues_CreatesTemplate() {