// 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 Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; namespace Microsoft.AspNetCore.Routing { // Detailed coverage for how DefaultLinkGenerator processes templates public class DefaultLinkGeneratorProcessTemplateTest : LinkGeneratorTestBase { [Fact] public void TryProcessTemplate_EncodesIntermediate_DefaultValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{p1}/{p2=a b}/{p3=foo}"); var linkGenerator = CreateLinkGenerator(endpoint); // Act var success = linkGenerator.TryProcessTemplate( httpContext: null, endpoint: endpoint, values: new RouteValueDictionary(new { p1 = "Home", p3 = "bar", }), ambientValues: null, options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/a%20b/bar", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Theory] [InlineData("a/b/c", "/Home/Index/a%2Fb%2Fc")] [InlineData("a/b b1/c c1", "/Home/Index/a%2Fb%20b1%2Fc%20c1")] public void TryProcessTemplate_EncodesValue_OfSingleAsteriskCatchAllParameter(string routeValue, string expected) { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{*path}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { path = routeValue, }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal(expected, result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Theory] [InlineData("/", "/Home/Index//")] [InlineData("a", "/Home/Index/a")] [InlineData("a/", "/Home/Index/a/")] [InlineData("a/b", "/Home/Index/a/b")] [InlineData("a/b/c", "/Home/Index/a/b/c")] [InlineData("a/b/cc", "/Home/Index/a/b/cc")] [InlineData("a/b/c/", "/Home/Index/a/b/c/")] [InlineData("a/b/c//", "/Home/Index/a/b/c//")] [InlineData("a//b//c", "/Home/Index/a//b//c")] public void TryProcessTemplate_DoesNotEncodeSlashes_OfDoubleAsteriskCatchAllParameter(string routeValue, string expected) { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{**path}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { path = routeValue, }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal(expected, result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_EncodesContentOtherThanSlashes_OfDoubleAsteriskCatchAllParameter() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{**path}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { path = "a/b b1/c c1" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/a/b%20b1/c%20c1", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_EncodesValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { name = "name with %special #characters" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal("?name=name%20with%20%25special%20%23characters", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_ForListOfStrings() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { color = new List { "red", "green", "blue" } }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal("?color=red&color=green&color=blue", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_ForListOfInts() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { items = new List { 10, 20, 30 } }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal("?items=10&items=20&items=30", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_ForList_Empty() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { color = new List { } }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_ForList_StringWorkaround() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act 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), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal("?page=1&color=red&color=green&color=blue&message=textfortest", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_Success_AmbientValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_GeneratesLowercaseUrl_SetOnRouteOptions() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => o.LowercaseUrls = true); }; var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/home/index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } // Regression test for https://github.com/aspnet/Routing/issues/802 [Fact] public void TryProcessTemplate_GeneratesLowercaseUrl_Includes_BufferedValues_SetOnRouteOptions() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}"); Action configure = (s) => { s.Configure(o => o.LowercaseUrls = true); }; var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { id = "18" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/foo/bar/18", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } // Regression test for https://github.com/aspnet/Routing/issues/802 [Fact] public void TryProcessTemplate_ParameterPolicy_Includes_BufferedValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=MyBar}/{id?}", policies: new { bar = new SlugifyParameterTransformer(), }); var linkGenerator = CreateLinkGenerator(endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { id = "18" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Foo/my-bar/18", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } // Regression test for aspnet/Routing#435 // // In this issue we used to lowercase URLs after parameters were encoded, meaning that if a character needed // encoding (such as a cyrillic character, it would not be encoded). [Fact] public void TryProcessTemplate_GeneratesLowercaseUrl_SetOnRouteOptions_CanLowercaseCharactersThatNeedEncoding() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => o.LowercaseUrls = true); }; var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "П" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), // Cryillic uppercase Pe options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/home/%D0%BF", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); // Convert back to decoded. // // This is Cyrillic lowercase Pe (not an n). Assert.Equal("/home/п", PathString.FromUriComponent(result.path.ToUriComponent()).Value); } [Fact] public void TryProcessTemplate_GeneratesLowercaseQueryString_SetOnRouteOptions() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => { o.LowercaseUrls = true; o.LowercaseQueryStrings = true; }); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/home/index", result.path.ToUriComponent()); Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_AppendsTrailingSlash_SetOnRouteOptions() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => o.AppendTrailingSlash = true); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_GeneratesLowercaseQueryStringAndTrailingSlash_SetOnRouteOptions() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => { o.LowercaseUrls = true; o.LowercaseQueryStrings = true; o.AppendTrailingSlash = true; }); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/home/index/", result.path.ToUriComponent()); Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_LowercaseUrlSetToTrue_OnRouteOptions_OverridenByCallsiteValue() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => o.LowercaseUrls = true); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "InDex" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: new LinkOptions { LowercaseUrls = false }, result: out var result); // Assert Assert.True(success); Assert.Equal("/HoMe/InDex", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_LowercaseUrlSetToFalse_OnRouteOptions_OverridenByCallsiteValue() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => o.LowercaseUrls = false); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "InDex" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: new LinkOptions() { LowercaseUrls = true }, result: out var result); // Assert Assert.True(success); Assert.Equal("/home/index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_LowercaseUrlQueryStringsSetToTrue_OnRouteOptions_OverridenByCallsiteValue() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => { o.LowercaseUrls = true; o.LowercaseQueryStrings = true; }); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: new LinkOptions { LowercaseUrls = false, LowercaseQueryStrings = false }, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal("?ShowStatus=True&INFO=DETAILED", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_LowercaseUrlQueryStringsSetToFalse_OnRouteOptions_OverridenByCallsiteValue() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => { o.LowercaseUrls = false; o.LowercaseQueryStrings = false; }); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: new LinkOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, }, result: out var result); // Assert Assert.True(success); Assert.Equal("/home/index", result.path.ToUriComponent()); Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_AppendTrailingSlashSetToFalse_OnRouteOptions_OverridenByCallsiteValue() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); Action configure = (s) => { s.Configure(o => o.AppendTrailingSlash = false); }; var linkGenerator = CreateLinkGenerator( configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: new LinkOptions() { AppendTrailingSlash = true, }, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void RouteGenerationRejectsConstraints() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "{p1}/{p2}", defaults: new { p2 = "catchall" }, policies: new { p2 = "\\d{4}" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { p1 = "abcd" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.False(success); } [Fact] public void RouteGenerationAcceptsConstraints() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "{p1}/{p2}", defaults: new { p2 = "catchall" }, policies: new { p2 = new RegexRouteConstraint("\\d{4}"), }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/hello/1234", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void RouteWithCatchAllRejectsConstraints() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "{p1}/{*p2}", defaults: new { p2 = "catchall" }, policies: new { p2 = new RegexRouteConstraint("\\d{4}") }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { p1 = "abcd" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.False(success); } [Fact] public void RouteWithCatchAllAcceptsConstraints() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "{p1}/{*p2}", defaults: new { p2 = "catchall" }, policies: new { p2 = new RegexRouteConstraint("\\d{4}") }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/hello/1234", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString() { // Arrange var target = new Mock(); target .Setup( e => e.Match( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(true) .Verifiable(); var endpoint = EndpointFactory.CreateRouteEndpoint( "{p1}/{p2}", defaults: new { p2 = "catchall" }, policies: new { p2 = target.Object }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/hello/1234", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); target.VerifyAll(); } // Any ambient values from the current request should be visible to constraint, even // if they have nothing to do with the route generating a link [Fact] public void TryProcessTemplate_ConstraintsSeeAmbientValues() { // Arrange var constraint = new CapturingConstraint(); var endpoint = EndpointFactory.CreateRouteEndpoint( template: "slug/Home/Store", defaults: new { controller = "Home", action = "Store" }, policies: new { c = constraint }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext( ambientValues: new { controller = "Home", action = "Blog", extra = "42" }); var expectedValues = new RouteValueDictionary( new { controller = "Home", action = "Store", extra = "42" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Store" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/slug/Home/Store", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key)); } // Non-parameter default values from the routing generating a link are not in the 'values' // collection when constraints are processed. [Fact] public void TryProcessTemplate_ConstraintsDontSeeDefaults_WhenTheyArentParameters() { // Arrange var constraint = new CapturingConstraint(); var endpoint = EndpointFactory.CreateRouteEndpoint( template: "slug/Home/Store", defaults: new { controller = "Home", action = "Store", otherthing = "17" }, policies: new { c = constraint }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" }); var expectedValues = new RouteValueDictionary( new { controller = "Home", action = "Store" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Store" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/slug/Home/Store", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key)); } // Default values are visible to the constraint when they are used to fill a parameter. [Fact] public void TryProcessTemplate_ConstraintsSeesDefault_WhenThereItsAParamter() { // Arrange var constraint = new CapturingConstraint(); var endpoint = EndpointFactory.CreateRouteEndpoint( template: "slug/{controller}/{action}", defaults: new { action = "Index" }, policies: new { c = constraint, }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" }); var expectedValues = new RouteValueDictionary( new { controller = "Shopping", action = "Index" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { controller = "Shopping" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/slug/Shopping", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); Assert.Equal(expectedValues, constraint.Values); } // Default values from the routing generating a link are in the 'values' collection when // constraints are processed - IFF they are specified as values or ambient values. [Fact] public void TryProcessTemplate_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient() { // Arrange var constraint = new CapturingConstraint(); var endpoint = EndpointFactory.CreateRouteEndpoint( template: "slug/Home/Store", defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }, policies: new { c = constraint, }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext( ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" }); var expectedValues = new RouteValueDictionary( new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Store", thirdthing = "13" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/slug/Home/Store", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key)); } [Theory] [InlineData(true)] [InlineData(false)] public void TryProcessTemplate_InlineConstraints_Success(bool hasHttpContext) { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{id:int}", defaults: new { controller = "Home", action = "Index" }, policies: new { }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null; // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 4 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/4", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_InlineConstraints_NonMatchingvalue() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{id}", defaults: new { controller = "Home", action = "Index" }, policies: new { id = "int" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.False(success); } [Theory] [InlineData(true)] [InlineData(false)] public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValuePresent(bool hasHttpContext) { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{id:int?}", defaults: new { controller = "Home", action = "Index" }, policies: new { }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null; // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 98 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/98", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValueNotPresent() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{id?}", defaults: new { controller = "Home", action = "Index" }, policies: new { id = "int" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{id?}", defaults: new { controller = "Home", action = "Index" }, policies: new { id = "int" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.False(success); } [Theory] [InlineData(true)] [InlineData(false)] public void TryProcessTemplate_InlineConstraints_MultipleInlineConstraints(bool hasHttpContext) { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{id:int:range(1,20)}", defaults: new { controller = "Home", action = "Index" }, policies: new { }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null; // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 14 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/14", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Theory] [InlineData(true)] [InlineData(false)] public void TryProcessTemplate_InlineConstraints_CompositeInlineConstraint_Fails(bool hasHttpContext) { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{id:int:range(1,20)}", defaults: new { controller = "Home", action = "Index" }, policies: new { }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null; // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 50 }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.False(success); } [Fact] public void TryProcessTemplate_InlineConstraints_CompositeConstraint_FromConstructor() { // Arrange var constraint = new MaxLengthRouteConstraint(20); var endpoint = EndpointFactory.CreateRouteEndpoint( template: "Home/Index/{name}", defaults: new { controller = "Home", action = "Index" }, policies: new { name = constraint }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/products", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameter_ParameterPresentInValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/products", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameter_ParameterNotPresentInValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameter_ParameterPresentInValuesAndDefaults() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "{controller}/{action}/{name}", defaults: new { name = "default-products" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/products", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "{controller}/{action}/{name}", defaults: new { name = "products" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_ParameterNotPresentInTemplate_PresentInValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products", format = "json" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/products", result.path.ToUriComponent()); Assert.Equal("?format=json", result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( template: "{controller}/{action}/.{name?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index/.products", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/.{name?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); Assert.True(success); Assert.Equal("/Home/Index/", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameter_InSimpleSegment() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { action = "Index", controller = "Home" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Home/Index", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_TwoOptionalParameters_OneValueFromAmbientValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}/{d?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { c = "17" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/a/15/17", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_OptionalParameterAfterDefault_OneValueFromAmbientValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { c = "17" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/a/15/17", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues() { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}/{d?}"); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { d = "17" }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/a", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } public static TheoryData DoesNotDiscardAmbientValuesData { get { // - ambient values // - explicit values // - required values // - defaults return new TheoryData { // link to same action on same controller { new { controller = "Products", action = "Edit", id = 10 }, new { controller = "Products", action = "Edit" }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null } }, // link to same action on same controller - ignoring case { new { controller = "ProDUcts", action = "EDit", id = 10 }, new { controller = "ProDUcts", action = "EDit" }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null } }, // link to same action and same controller on same area { new { area = "Admin", controller = "Products", action = "Edit", id = 10 }, new { area = "Admin", controller = "Products", action = "Edit" }, new { area = "Admin", controller = "Products", action = "Edit", page = (string)null }, new { area = "Admin", controller = "Products", action = "Edit", page = (string)null } }, // link to same action and same controller on same area { new { area = "Admin", controller = "Products", action = "Edit", id = 10 }, new { controller = "Products", action = "Edit" }, new { area = "Admin", controller = "Products", action = "Edit", page = (string)null }, new { area = "Admin", controller = "Products", action = "Edit", page = (string)null } }, // link to same action and same controller { new { controller = "Products", action = "Edit", id = 10 }, new { controller = "Products", action = "Edit" }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null } }, { new { controller = "Products", action = "Edit", id = 10 }, new { controller = "Products", action = "Edit" }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null } }, { new { controller = "Products", action = "Edit", id = 10 }, new { controller = "Products", action = "Edit" }, new { area = "", controller = "Products", action = "Edit", page = "" }, new { area = "", controller = "Products", action = "Edit", page = "" } }, // link to same page { new { page = "Products/Edit", id = 10 }, new { page = "Products/Edit" }, new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" }, new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" } }, }; } } [Theory] [MemberData(nameof(DoesNotDiscardAmbientValuesData))] public void TryProcessTemplate_DoesNotDiscardAmbientValues_IfAllRequiredKeysMatch( object ambientValues, object explicitValues, object requiredValues, object defaults) { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "Products/Edit/{id}", requiredValues: requiredValues, defaults: defaults); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(explicitValues), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Products/Edit/10", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_DoesNotDiscardAmbientValues_IfAllRequiredValuesMatch_ForGenericKeys() { // Verifying that discarding works in general usage case i.e when keys are not like controller, action etc. // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "Products/Edit/{id}", requiredValues: new { c = "Products", a = "Edit" }, defaults: new { c = "Products", a = "Edit" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { c = "Products", a = "Edit" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.True(success); Assert.Equal("/Products/Edit/10", result.path.ToUriComponent()); Assert.Equal(string.Empty, result.query.ToUriComponent()); } [Fact] public void TryProcessTemplate_DiscardsAmbientValues_ForGenericKeys() { // Verifying that discarding works in general usage case i.e when keys are not like controller, action etc. // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "Products/Edit/{id}", requiredValues: new { c = "Products", a = "Edit" }, defaults: new { c = "Products", a = "Edit" }); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 }); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(new { c = "Products", a = "List" }), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.False(success); } public static TheoryData DiscardAmbientValuesData { get { // - ambient values // - explicit values // - required values // - defaults return new TheoryData { // link to different action on same controller { new { controller = "Products", action = "Edit", id = 10 }, new { controller = "Products", action = "List" }, new { area = (string)null, controller = "Products", action = "List", page = (string)null }, new { area = (string)null, controller = "Products", action = "List", page = (string)null } }, // link to different action on same controller and same area { new { area = "Customer", controller = "Products", action = "Edit", id = 10 }, new { area = "Customer", controller = "Products", action = "List" }, new { area = "Customer", controller = "Products", action = "List", page = (string)null }, new { area = "Customer", controller = "Products", action = "List", page = (string)null } }, // link from one area to a different one { new { area = "Admin", controller = "Products", action = "Edit", id = 10 }, new { area = "Consumer", controller = "Products", action = "Edit" }, new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null }, new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null } }, // link from non-area to a area one { new { controller = "Products", action = "Edit", id = 10 }, new { area = "Consumer", controller = "Products", action = "Edit" }, new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null }, new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null } }, // link from area to a non-area based action { new { area = "Admin", controller = "Products", action = "Edit", id = 10 }, new { area = "", controller = "Products", action = "Edit" }, new { area = "", controller = "Products", action = "Edit", page = (string)null }, new { area = "", controller = "Products", action = "Edit", page = (string)null } }, // link from controller-action to a page { new { controller = "Products", action = "Edit", id = 10 }, new { page = "Products/Edit" }, new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit"}, new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit"} }, // link from a page to controller-action { new { page = "Products/Edit", id = 10 }, new { controller = "Products", action = "Edit" }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }, new { area = (string)null, controller = "Products", action = "Edit", page = (string)null } }, // link from one page to a different page { new { page = "Products/Details", id = 10 }, new { page = "Products/Edit" }, new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" }, new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" } }, }; } } [Theory] [MemberData(nameof(DiscardAmbientValuesData))] public void TryProcessTemplate_DiscardsAmbientValues_IfAnyAmbientValue_IsDifferentThan_EndpointRequiredValues( object ambientValues, object explicitValues, object requiredValues, object defaults) { // Linking to a different action on the same controller // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint( "Products/Edit/{id}", requiredValues: requiredValues, defaults: defaults); var linkGenerator = CreateLinkGenerator(endpoint); var httpContext = CreateHttpContext(ambientValues); // Act var success = linkGenerator.TryProcessTemplate( httpContext: httpContext, endpoint: endpoint, values: new RouteValueDictionary(explicitValues), ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), options: null, result: out var result); // Assert Assert.False(success); } } }