From 41f56dbbeddb8ac0376b86d9088633f19be1ab56 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 17 Jul 2018 10:46:26 -0700 Subject: [PATCH] Minor fix to DefaultLinkGenerator and enabled constraint related tests in DefaultLinkGeneratorTest --- .../DefaultLinkGenerator.cs | 2 +- .../DefaultLinkGeneratorTest.cs | 1055 ++++++++++------- .../RouteTest.cs | 17 +- .../TestObjects/CapturingConstraint.cs | 24 + 4 files changed, 630 insertions(+), 468 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Routing.Tests/TestObjects/CapturingConstraint.cs diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs index d29b05edcd..cef493c3db 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs @@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Routing { var matchProcessorReference = endpoint.MatchProcessorReferences[i]; var parameter = endpoint.ParsedTemplate.GetParameter(matchProcessorReference.ParameterName); - if (parameter.IsOptional && !routeValues.ContainsKey(parameter.Name)) + if (parameter != null && parameter.IsOptional && !routeValues.ContainsKey(parameter.Name)) { continue; } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs index 4416256954..66397121d3 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -3,10 +3,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.EndpointFinders; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; @@ -244,367 +248,502 @@ namespace Microsoft.AspNetCore.Routing Assert.Equal("/Home/Index", link); } - //[Fact] - //public void RouteGenerationRejectsConstraints() - //{ - // // Arrange - // var context = CreateRouteValuesContext(new { p1 = "abcd" }); - - // var endpoint = CreateEndpoints( - // "{p1}/{p2}", - // new { p2 = "catchall" }, - // true, - // new RouteValueDictionary(new { p2 = "\\d{4}" })); - - // // Act - // var virtualPath = route.GetLink(context); - - // // Assert - // Assert.Null(virtualPath); - //} - - //[Fact] - //public void RouteGenerationAcceptsConstraints() - //{ - // // Arrange - // var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); - - // var endpoint = CreateEndpoints( - // "{p1}/{p2}", - // new { p2 = "catchall" }, - // true, - // new RouteValueDictionary(new { p2 = "\\d{4}" })); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.NotNull(pathData); - // Assert.Equal("/hello/1234", link); - // - // - //} - - //[Fact] - //public void RouteWithCatchAllRejectsConstraints() - //{ - // // Arrange - // var context = CreateRouteValuesContext(new { p1 = "abcd" }); - - // var endpoint = CreateEndpoints( - // "{p1}/{*p2}", - // new { p2 = "catchall" }, - // true, - // new RouteValueDictionary(new { p2 = "\\d{4}" })); - - // // Act - // var virtualPath = route.GetLink(context); - - // // Assert - // Assert.Null(virtualPath); - //} - - //[Fact] - //public void RouteWithCatchAllAcceptsConstraints() - //{ - // // Arrange - // var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); - - // var endpoint = CreateEndpoints( - // "{p1}/{*p2}", - // new { p2 = "catchall" }, - // true, - // new RouteValueDictionary(new { p2 = "\\d{4}" })); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.NotNull(pathData); - // Assert.Equal("/hello/1234", link); - // - // - //} - - //[Fact] - //public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString() - //{ - // // Arrange - // var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); - - // var target = new Mock(); - // target - // .Setup( - // e => e.Match( - // It.IsAny(), - // It.IsAny(), - // It.IsAny(), - // It.IsAny(), - // It.IsAny())) - // .Returns(true) - // .Verifiable(); - - // var endpoint = CreateEndpoints( - // "{p1}/{p2}", - // new { p2 = "catchall" }, - // true, - // new RouteValueDictionary(new { p2 = target.Object })); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.NotNull(pathData); - // Assert.Equal("/hello/1234", link); - // - // - - // 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 GetLink_ConstraintsSeeAmbientValues() - //{ - // // Arrange - // var constraint = new CapturingConstraint(); - // var endpoint = CreateEndpoints( - // template: "slug/{controller}/{action}", - // defaultValues: null, - // handleRequest: true, - // constraints: new { c = constraint }); - - // var context = CreateRouteValuesContext( - // values: new { action = "Store" }, - // ambientValues: new { Controller = "Home", action = "Blog", extra = "42" }); - - // var expectedValues = new RouteValueDictionary( - // new { controller = "Home", action = "Store", extra = "42" }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/slug/Home/Store", link); - // - // - - // Assert.Equal(expectedValues, constraint.Values); - //} - - //// Non-parameter default values from the routing generating a link are not in the 'values' - //// collection when constraints are processed. - //[Fact] - //public void GetLink_ConstraintsDontSeeDefaults_WhenTheyArentParameters() - //{ - // // Arrange - // var constraint = new CapturingConstraint(); - // var endpoint = CreateEndpoints( - // template: "slug/{controller}/{action}", - // defaultValues: new { otherthing = "17" }, - // handleRequest: true, - // constraints: new { c = constraint }); - - // var context = CreateRouteValuesContext( - // values: new { action = "Store" }, - // ambientValues: new { Controller = "Home", action = "Blog" }); - - // var expectedValues = new RouteValueDictionary( - // new { controller = "Home", action = "Store" }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/slug/Home/Store", link); - // - // - - // Assert.Equal(expectedValues, constraint.Values); - //} - - //// Default values are visible to the constraint when they are used to fill a parameter. - //[Fact] - //public void GetLink_ConstraintsSeesDefault_WhenThereItsAParamter() - //{ - // // Arrange - // var constraint = new CapturingConstraint(); - // var endpoint = CreateEndpoints( - // template: "slug/{controller}/{action}", - // defaultValues: new { action = "Index" }, - // handleRequest: true, - // constraints: new { c = constraint }); - - // var context = CreateRouteValuesContext( - // values: new { controller = "Shopping" }, - // ambientValues: new { Controller = "Home", action = "Blog" }); - - // var expectedValues = new RouteValueDictionary( - // new { controller = "Shopping", action = "Index" }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/slug/Shopping", link); - // - // - - // 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 GetLink_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient() - //{ - // // Arrange - // var constraint = new CapturingConstraint(); - // var endpoint = CreateEndpoints( - // template: "slug/{controller}/{action}", - // defaultValues: new { otherthing = "17", thirdthing = "13" }, - // handleRequest: true, - // constraints: new { c = constraint }); - - // var context = CreateRouteValuesContext( - // values: new { action = "Store", thirdthing = "13" }, - // ambientValues: new { Controller = "Home", action = "Blog", otherthing = "17" }); - - // var expectedValues = new RouteValueDictionary( - // new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/slug/Home/Store", link); - // - // - - // Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key)); - //} - - //[Fact] - //public void GetLink_InlineConstraints_Success() - //{ - // // Arrange - // var endpoint = CreateEndpoints("{controller}/{action}/{id:int}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home", id = 4 }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/Home/Index/4", link); - // - // - //} - - //[Fact] - //public void GetLink_InlineConstraints_NonMatchingvalue() - //{ - // // Arrange - // var endpoint = CreateEndpoints("{controller}/{action}/{id:int}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home", id = "asf" }); - - // // Act - // var path = route.GetLink(context); - - // // Assert - // Assert.Null(path); - //} - - //[Fact] - //public void GetLink_InlineConstraints_OptionalParameter_ValuePresent() - //{ - // // Arrange - // var endpoint = CreateEndpoints("{controller}/{action}/{id:int?}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home", id = 98 }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/Home/Index/98", link); - // - // - //} - - //[Fact] - //public void GetLink_InlineConstraints_OptionalParameter_ValueNotPresent() - //{ - // // Arrange - // var endpoint = CreateEndpoints("{controller}/{action}/{id:int?}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home" }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/Home/Index", link); - // - // - //} - - //[Fact] - //public void GetLink_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails() - //{ - // // Arrange - // var endpoint = CreateEndpoints("{controller}/{action}/{id:int?}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home", id = "sdfd" }); - - // // Act - // var path = route.GetLink(context); - - // // Assert - // Assert.Null(path); - //} - - //[Fact] - //public void GetLink_InlineConstraints_CompositeInlineConstraint() - //{ - // // Arrange - // var endpoint = CreateEndpoints("{controller}/{action}/{id:int:range(1,20)}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home", id = 14 }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/Home/Index/14", link); - // - // - //} - - //[Fact] - //public void GetLink_InlineConstraints_CompositeConstraint_FromConstructor() - //{ - // // Arrange - // var constraint = new MaxLengthRouteConstraint(20); - // var endpoint = CreateEndpoints( - // template: "{controller}/{action}/{name:alpha}", - // defaultValues: null, - // handleRequest: true, - // constraints: new { name = constraint }); - - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home", name = "products" }); - - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.Equal("/Home/Index/products", link); - // - // - //} + [Fact] + public void RouteGenerationRejectsConstraints() + { + // Arrange + var context = CreateRouteValuesContext(new { p1 = "abcd" }); + var linkGenerator = CreateLinkGenerator(); + var matchProcessorReferences = new List(); + matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}"))); + var endpoint = CreateEndpoint( + "{p1}/{p2}", + new { p2 = "catchall" }, + matchProcessorReferences: matchProcessorReferences); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.False(canGenerateLink); + } + + [Fact] + public void RouteGenerationAcceptsConstraints() + { + // Arrange + var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); + var linkGenerator = CreateLinkGenerator(); + var matchProcessorReferences = new List(); + matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}"))); + var endpoint = CreateEndpoint( + "{p1}/{p2}", + new { p2 = "catchall" }, + matchProcessorReferences); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.True(canGenerateLink); + Assert.Equal("/hello/1234", link); + } + + [Fact] + public void RouteWithCatchAllRejectsConstraints() + { + // Arrange + var context = CreateRouteValuesContext(new { p1 = "abcd" }); + var linkGenerator = CreateLinkGenerator(); + var matchProcessorReferences = new List(); + matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}"))); + var endpoint = CreateEndpoint( + "{p1}/{*p2}", + new { p2 = "catchall" }, + matchProcessorReferences: matchProcessorReferences); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.False(canGenerateLink); + } + + [Fact] + public void RouteWithCatchAllAcceptsConstraints() + { + // Arrange + var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); + var linkGenerator = CreateLinkGenerator(); + var matchProcessorReferences = new List(); + matchProcessorReferences.Add(new MatchProcessorReference("p2", new RegexRouteConstraint("\\d{4}"))); + var endpoint = CreateEndpoint( + "{p1}/{*p2}", + new { p2 = "catchall" }, + matchProcessorReferences); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.True(canGenerateLink); + Assert.Equal("/hello/1234", link); + } + + [Fact] + public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString() + { + // Arrange + var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); + var linkGenerator = CreateLinkGenerator(); + var target = new Mock(); + target + .Setup( + e => e.Match( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(true) + .Verifiable(); + + var endpoint = CreateEndpoint( + "{p1}/{p2}", + defaultValues: new { p2 = "catchall" }, + matchProcessorReferences: new List + { + new MatchProcessorReference("p2", target.Object) + }); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.True(canGenerateLink); + Assert.Equal("/hello/1234", link); + 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 GetLink_ConstraintsSeeAmbientValues() + { + // Arrange + var constraint = new CapturingConstraint(); + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "slug/Home/Store", + defaultValues: new { controller = "Home", action = "Store" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("c", constraint) + }); + + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Store" }, + ambientValues: new { controller = "Home", action = "Blog", extra = "42" }); + + var expectedValues = new RouteValueDictionary( + new { controller = "Home", action = "Store", extra = "42" }); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.True(canGenerateLink); + Assert.Equal("/slug/Home/Store", link); + 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 GetLink_ConstraintsDontSeeDefaults_WhenTheyArentParameters() + { + // Arrange + var constraint = new CapturingConstraint(); + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "slug/Home/Store", + defaultValues: new { controller = "Home", action = "Store", otherthing = "17" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("c", constraint) + }); + + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Store" }, + ambientValues: new { controller = "Home", action = "Blog" }); + + var expectedValues = new RouteValueDictionary( + new { controller = "Home", action = "Store" }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/slug/Home/Store", link); + 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 GetLink_ConstraintsSeesDefault_WhenThereItsAParamter() + { + // Arrange + var constraint = new CapturingConstraint(); + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "slug/{controller}/{action}", + defaultValues: new { action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("c", constraint) + }); + + var context = CreateRouteValuesContext( + suppliedValues: new { controller = "Shopping" }, + ambientValues: new { controller = "Home", action = "Blog" }); + + var expectedValues = new RouteValueDictionary( + new { controller = "Shopping", action = "Index" }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/slug/Shopping", link); + 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 GetLink_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient() + { + // Arrange + var constraint = new CapturingConstraint(); + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "slug/Home/Store", + defaultValues: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("c", constraint) + }); + + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Store", thirdthing = "13" }, + ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" }); + + var expectedValues = new RouteValueDictionary( + new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/slug/Home/Store", link); + Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key)); + } + + [Fact] + public void GetLink_InlineConstraints_Success() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{id}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("id", "int") + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", id = 4 }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/Home/Index/4", link); + } + + [Fact] + public void GetLink_InlineConstraints_NonMatchingvalue() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{id}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("id", "int") + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" }); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.False(canGenerateLink); + } + + [Fact] + public void GetLink_InlineConstraints_OptionalParameter_ValuePresent() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{id}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("id", optional: true, "int") + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", id = 98 }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/Home/Index/98", link); + } + + [Fact] + public void GetLink_InlineConstraints_OptionalParameter_ValueNotPresent() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{id?}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("id", optional: true, "int") + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home" }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/Home/Index", link); + } + + [Fact] + public void GetLink_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{id}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("id", optional: true, "int") + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" }); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.False(canGenerateLink); + } + + [Fact] + public void GetLink_InlineConstraints_CompositeInlineConstraint() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{id}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("id", "int"), + new MatchProcessorReference("id", "range(1,20)") + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", id = 14 }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/Home/Index/14", link); + } + + [Fact] + public void GetLink_InlineConstraints_CompositeInlineConstraint_Fails() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{id}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("id", "int"), + new MatchProcessorReference("id", "range(1,20)") + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", id = 50 }); + + // Act + var canGenerateLink = linkGenerator.TryGetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues, + out var link); + + // Assert + Assert.False(canGenerateLink); + } + + [Fact] + public void GetLink_InlineConstraints_CompositeConstraint_FromConstructor() + { + // Arrange + var constraint = new MaxLengthRouteConstraint(20); + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "Home/Index/{name}", + defaultValues: new { controller = "Home", action = "Index" }, + matchProcessorReferences: new List() + { + new MatchProcessorReference("name", constraint) + }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", name = "products" }); + + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); + + // Assert + Assert.Equal("/Home/Index/products", link); + } [Fact] public void GetLink_OptionalParameter_ParameterPresentInValues() @@ -710,49 +849,48 @@ namespace Microsoft.AspNetCore.Routing Assert.Equal("/Home/Index/products?format=json", link); } - //[Fact] - //public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent() - //{ - // // Arrange - // var endpoint = CreateEndpoints( - // template: "{controller}/{action}/.{name?}", - // defaultValues: null, - // handleRequest: true, - // constraints: null); + [Fact] + public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint( + template: "{controller}/{action}/.{name?}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home", name = "products" }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home", name = "products" }); - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); - // // Assert - // Assert.Equal("/Home/Index/.products", link); - // - // - //} + // Assert + Assert.Equal("/Home/Index/.products", link); + } - //[Fact] - //public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent() - //{ - // // Arrange - // var endpoint = CreateEndpoints( - // template: "{controller}/{action}/.{name?}", - // defaultValues: null, - // handleRequest: true, - // constraints: null); + [Fact] + public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent() + { + // Arrange + var linkGenerator = CreateLinkGenerator(); + var endpoint = CreateEndpoint("{controller}/{action}/.{name?}"); - // var context = CreateRouteValuesContext( - // values: new { action = "Index", controller = "Home" }); + var context = CreateRouteValuesContext( + suppliedValues: new { action = "Index", controller = "Home" }); - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); + // Act + var link = linkGenerator.GetLink( + new DefaultHttpContext(), + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); - // // Assert - // Assert.Equal("/Home/Index/", link); - // - // - //} + // Assert + Assert.Equal("/Home/Index/", link); + } [Fact] public void GetLink_OptionalParameter_InSimpleSegment() @@ -774,63 +912,68 @@ namespace Microsoft.AspNetCore.Routing Assert.Equal("/Home/Index", link); } - //[Fact] - //public void GetLink_TwoOptionalParameters_OneValueFromAmbientValues() - //{ - // // Arrange - // var endpoint = CreateEndpoints("a/{b=15}/{c?}/{d?}"); - // var linkGenerator = CreateLinkGenerator(); - // var context = CreateRouteValuesContext( - // suppliedValues: new { }, - // ambientValues: new { c = "17" }); + [Fact] + public void GetLink_TwoOptionalParameters_OneValueFromAmbientValues() + { + // Arrange + var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}"); + var linkGenerator = CreateLinkGenerator(); + var context = CreateRouteValuesContext( + suppliedValues: new { }, + ambientValues: new { c = "17" }); - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); + // Act + var link = linkGenerator.GetLink( + httpContext: null, + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); - // // Assert - // Assert.Equal("/a/15/17", link); - //} + // Assert + Assert.Equal("/a/15/17", link); + } - //[Fact] - //public void GetLink_OptionalParameterAfterDefault_OneValueFromAmbientValues() - //{ - // // Arrange - // var endpoint = CreateEndpoints("a/{b=15}/{c?}"); - // var linkGenerator = CreateLinkGenerator(); - // var context = CreateRouteValuesContext( - // suppliedValues: new { }, - // ambientValues: new { c = "17" }); + [Fact] + public void GetLink_OptionalParameterAfterDefault_OneValueFromAmbientValues() + { + // Arrange + var endpoint = CreateEndpoint("a/{b=15}/{c?}"); + var linkGenerator = CreateLinkGenerator(); + var context = CreateRouteValuesContext( + suppliedValues: new { }, + ambientValues: new { c = "17" }); - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); + // Act + var link = linkGenerator.GetLink( + httpContext: null, + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); - // // Assert - // Assert.Equal("/a/15/17", link); - //} + // Assert + Assert.Equal("/a/15/17", link); + } - //[Fact] - //public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues() - //{ - // // Arrange - // var endpoint = CreateEndpoints( - // template: "a/{b=15}/{c?}/{d?}", - // defaultValues: null, - // handleRequest: true, - // constraints: null); + [Fact] + public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues() + { + // Arrange + var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}", defaultValues: new { }); + var linkGenerator = CreateLinkGenerator(); + var context = CreateRouteValuesContext( + suppliedValues: new { }, + ambientValues: new { d = "17" }); - // var context = CreateRouteValuesContext( - // values: new { }, - // ambientValues: new { d = "17" }); + // Act + var link = linkGenerator.GetLink( + httpContext: null, + new[] { endpoint }, + context.ExplicitValues, + context.AmbientValues); - // // Act - // var link = linkGenerator.GetLink(new[] { endpoint }, context.ExplicitValues, context.AmbientValues); - - // // Assert - // Assert.NotNull(pathData); - // Assert.Equal("/a", link); - // - // - //} + // Assert + Assert.Equal("/a", link); + } private RouteValuesBasedEndpointFinderContext CreateRouteValuesContext(object suppliedValues, object ambientValues = null) { @@ -840,17 +983,27 @@ namespace Microsoft.AspNetCore.Routing return context; } - private MatcherEndpoint CreateEndpoint(string template, object defaultValues = null) + private MatcherEndpoint CreateEndpoint( + string template, + object defaultValues = null, + object requiredValues = null, + List matchProcessorReferences = null, + int order = 0, + EndpointMetadataCollection metadata = null) { var defaults = defaultValues == null ? new RouteValueDictionary() : new RouteValueDictionary(defaultValues); + var required = requiredValues == null ? new RouteValueDictionary() : new RouteValueDictionary(requiredValues); + metadata = metadata ?? EndpointMetadataCollection.Empty; + matchProcessorReferences = matchProcessorReferences ?? new List(); + return new MatcherEndpoint( next => (httpContext) => Task.CompletedTask, template, defaults, - new RouteValueDictionary(), - new List(), - 0, - EndpointMetadataCollection.Empty, + required, + matchProcessorReferences, + order, + metadata, null); } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs index a09935fcd5..805fed68bc 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.Internal; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -1843,21 +1844,5 @@ namespace Microsoft.AspNetCore.Routing return new DefaultInlineConstraintResolver(routeOptions.Object); } - - private class CapturingConstraint : IRouteConstraint - { - public IDictionary Values { get; private set; } - - public bool Match( - HttpContext httpContext, - IRouter route, - string routeKey, - RouteValueDictionary values, - RouteDirection routeDirection) - { - Values = new RouteValueDictionary(values); - return true; - } - } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/CapturingConstraint.cs b/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/CapturingConstraint.cs new file mode 100644 index 0000000000..94834493df --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/CapturingConstraint.cs @@ -0,0 +1,24 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Routing.TestObjects +{ + internal class CapturingConstraint : IRouteConstraint + { + public IDictionary Values { get; private set; } + + public bool Match( + HttpContext httpContext, + IRouter route, + string routeKey, + RouteValueDictionary values, + RouteDirection routeDirection) + { + Values = new RouteValueDictionary(values); + return true; + } + } +}