diff --git a/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs b/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs index bfd6069eca..f26c36c132 100644 --- a/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs +++ b/benchmarkapps/Benchmarks/StartupUsingDispatcher.cs @@ -35,7 +35,8 @@ namespace Benchmarks values: new { }, order: 0, metadata: EndpointMetadataCollection.Empty, - displayName: "Plaintext"), + displayName: "Plaintext", + address: null), })); }); } diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs index 29e94564a7..a9853d13c3 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/MatcherBenchmarkBase.cs @@ -31,7 +31,8 @@ namespace Microsoft.AspNetCore.Routing.Matchers new { }, 0, EndpointMetadataCollection.Empty, - template); + template, + address: null); } internal static int[] SampleRequests(int endpointCount, int count) diff --git a/samples/DispatcherSample.Web/Startup.cs b/samples/DispatcherSample.Web/Startup.cs index 4b8a461ff1..5b8103640c 100644 --- a/samples/DispatcherSample.Web/Startup.cs +++ b/samples/DispatcherSample.Web/Startup.cs @@ -31,7 +31,7 @@ namespace DispatcherSample.Web response.ContentLength = payloadLength; return response.Body.WriteAsync(_homePayload, 0, payloadLength); }, - "/", new { }, 0, EndpointMetadataCollection.Empty, "Home"), + "/", new { }, 0, EndpointMetadataCollection.Empty, "Home", address: null), new MatcherEndpoint((next) => (httpContext) => { var response = httpContext.Response; @@ -41,7 +41,7 @@ namespace DispatcherSample.Web response.ContentLength = payloadLength; return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength); }, - "/plaintext", new { }, 0, EndpointMetadataCollection.Empty, "Plaintext"), + "/plaintext", new { }, 0, EndpointMetadataCollection.Empty, "Plaintext", address: null), })); }); } diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Address.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/Address.cs new file mode 100644 index 0000000000..6004d8b9f8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/Address.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Routing +{ + public class Address + { + public Address() + { + } + + public Address(string name) + { + Name = name; + } + + public string Name { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs index 21a2b06bad..da138e4014 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs @@ -8,15 +8,21 @@ namespace Microsoft.AspNetCore.Routing [DebuggerDisplay("{DisplayName,nq}")] public abstract class Endpoint { - protected Endpoint(EndpointMetadataCollection metadata, string displayName) + protected Endpoint( + EndpointMetadataCollection metadata, + string displayName, + Address address) { - // Both allowed to be null + // All are allowed to be null Metadata = metadata ?? EndpointMetadataCollection.Empty; DisplayName = displayName; + Address = address; } public string DisplayName { get; } public EndpointMetadataCollection Metadata { get; } + + public Address Address { get; } } } diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/IEndpointFinder.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/IEndpointFinder.cs new file mode 100644 index 0000000000..e799cacbd9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/IEndpointFinder.cs @@ -0,0 +1,12 @@ +// 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; + +namespace Microsoft.AspNetCore.Routing +{ + public interface IEndpointFinder + { + IEnumerable FindEndpoints(Address address); + } +} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/ILinkGenerator.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/ILinkGenerator.cs new file mode 100644 index 0000000000..8734108fb4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/ILinkGenerator.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Routing +{ + public interface ILinkGenerator + { + bool TryGetLink(LinkGeneratorContext context, out string link); + + string GetLink(LinkGeneratorContext context); + } +} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGeneratorContext.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGeneratorContext.cs new file mode 100644 index 0000000000..90f6eab6b8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGeneratorContext.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Routing +{ + public class LinkGeneratorContext + { + public Address Address { get; set; } + + public RouteValueDictionary AmbientValues { get; set; } + + public RouteValueDictionary SuppliedValues { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/DefaultEndpointFinder.cs b/src/Microsoft.AspNetCore.Routing/DefaultEndpointFinder.cs new file mode 100644 index 0000000000..cf06f56195 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/DefaultEndpointFinder.cs @@ -0,0 +1,53 @@ +// 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.Extensions.Logging; + +namespace Microsoft.AspNetCore.Routing +{ + internal class DefaultEndpointFinder : IEndpointFinder + { + private readonly CompositeEndpointDataSource _endpointDatasource; + private readonly ILogger _logger; + + public DefaultEndpointFinder( + CompositeEndpointDataSource endpointDataSource, + ILogger logger) + { + _endpointDatasource = endpointDataSource; + _logger = logger; + } + + public IEnumerable FindEndpoints(Address lookupAddress) + { + var allEndpoints = _endpointDatasource.Endpoints; + + if (lookupAddress == null || string.IsNullOrEmpty(lookupAddress.Name)) + { + return allEndpoints; + } + + var endpointsWithAddress = allEndpoints.Where(ep => ep.Address != null); + if (!endpointsWithAddress.Any()) + { + return allEndpoints; + } + + foreach (var endpoint in endpointsWithAddress) + { + if (string.Equals(lookupAddress.Name, endpoint.Address.Name, StringComparison.OrdinalIgnoreCase)) + { + return new[] { endpoint }; + } + } + + _logger.LogDebug( + $"Could not find an endpoint having an address with name '{lookupAddress.Name}'."); + + return Enumerable.Empty(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs new file mode 100644 index 0000000000..efa773e6f5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Routing.Internal; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Template; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; + +namespace Microsoft.AspNetCore.Routing +{ + public class DefaultLinkGenerator : ILinkGenerator + { + private readonly IEndpointFinder _endpointFinder; + private readonly ObjectPool _uriBuildingContextPool; + private readonly ILogger _logger; + + public DefaultLinkGenerator( + IEndpointFinder endpointFinder, + ObjectPool uriBuildingContextPool, + ILogger logger) + { + _endpointFinder = endpointFinder; + _uriBuildingContextPool = uriBuildingContextPool; + _logger = logger; + } + + public string GetLink(LinkGeneratorContext context) + { + if (TryGetLink(context, out var link)) + { + return link; + } + + throw new InvalidOperationException("Could not find a matching endpoint to generate a link."); + } + + public bool TryGetLink(LinkGeneratorContext context, out string link) + { + var address = context.Address; + var endpoints = _endpointFinder.FindEndpoints(address); + link = null; + + if (endpoints == null) + { + return false; + } + + var matcherEndpoints = endpoints.OfType(); + if (!matcherEndpoints.Any()) + { + //todo:log here + return false; + } + + foreach (var endpoint in matcherEndpoints) + { + link = GetLink(endpoint.ParsedTemlate, endpoint.Values, context); + if (link != null) + { + return true; + } + } + + return false; + } + + private string GetLink( + RouteTemplate template, + IReadOnlyDictionary defaultValues, + LinkGeneratorContext context) + { + var defaults = new RouteValueDictionary(defaultValues); + var templateBinder = new TemplateBinder( + UrlEncoder.Default, + _uriBuildingContextPool, + template, + defaults); + + var values = templateBinder.GetValues( + new RouteValueDictionary(context.AmbientValues), + new RouteValueDictionary(context.SuppliedValues)); + if (values == null) + { + // We're missing one of the required values for this route. + return null; + } + + //TODO: route constraint matching here + + return templateBinder.BindValues(values.AcceptedValues); + } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/DependencyInjection/DispatcherServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Routing/DependencyInjection/DispatcherServiceCollectionExtensions.cs index 2bb82e0933..64f8d4ad81 100644 --- a/src/Microsoft.AspNetCore.Routing/DependencyInjection/DispatcherServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/DependencyInjection/DispatcherServiceCollectionExtensions.cs @@ -34,6 +34,9 @@ namespace Microsoft.Extensions.DependencyInjection // services.TryAddSingleton(); + // Link generation related services + services.TryAddSingleton(); + services.TryAddSingleton(); // // Endpoint Selection // diff --git a/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs b/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs index d371afc143..c416bd148e 100644 --- a/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs +++ b/src/Microsoft.AspNetCore.Routing/Matchers/MatcherEndpoint.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Template; namespace Microsoft.AspNetCore.Routing.Matchers { @@ -21,8 +22,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers object values, int order, EndpointMetadataCollection metadata, - string displayName) - : base(metadata, displayName) + string displayName, + Address address) + : base(metadata, displayName, address) { if (invoker == null) { @@ -36,14 +38,42 @@ namespace Microsoft.AspNetCore.Routing.Matchers Invoker = invoker; Template = template; - Values = new RouteValueDictionary(values); + ParsedTemlate = TemplateParser.Parse(template); + var mergedDefaults = GetDefaults(ParsedTemlate, new RouteValueDictionary(values)); + Values = mergedDefaults; Order = order; } public int Order { get; } public Func Invoker { get; } public string Template { get; } - public IReadOnlyDictionary Values { get; } + + // Todo: needs review + public RouteTemplate ParsedTemlate { get; } + + private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate, RouteValueDictionary defaults) + { + var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults); + + foreach (var parameter in parsedTemplate.Parameters) + { + if (parameter.DefaultValue != null) + { + if (result.ContainsKey(parameter.Name)) + { + throw new InvalidOperationException( + Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly( + parameter.Name)); + } + else + { + result.Add(parameter.Name, parameter.DefaultValue); + } + } + } + + return result; + } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultEndpointFinderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultEndpointFinderTest.cs new file mode 100644 index 0000000000..a9421aad05 --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultEndpointFinderTest.cs @@ -0,0 +1,161 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Routing.TestObjects; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Microsoft.AspNetCore.Routing +{ + public class DefaultEndpointFinderTest + { + [Fact] + public void FindEndpoints_IgnoresCase_ForRouteNameLookup() + { + // Arrange + var endpoint1 = CreateEndpoint(new Address("home")); + var endpoint2 = CreateEndpoint(new Address("admin")); + var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2); + + // Act + var result = endpointFinder.FindEndpoints(new Address("Admin")); + + // Assert + var endpoint = Assert.Single(result); + Assert.Same(endpoint2, endpoint); + } + + [Fact] + public void FindEndpoints_MultipleEndpointsWithSameName_ReturnsFirstEndpoint_WithMatchingName() + { + // Arrange + var name = "common-tag-for-all-my-section's-routes"; + var endpoint1 = CreateEndpoint(new Address(name)); + var endpoint2 = CreateEndpoint(new Address("admin")); + var endpoint3 = CreateEndpoint(new Address(name)); + var endpoint4 = CreateEndpoint(new Address("products")); + var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2, endpoint3, endpoint4); + + // Act + var result = endpointFinder.FindEndpoints(new Address(name)); + + // Assert + var endpoint = Assert.Single(result); + Assert.Same(endpoint, endpoint1); + } + + [Fact] + public void FindEndpoints_ReturnsAllEndpoints_WhenNoEndpointsHaveAddress() + { + // Arrange + var endpoint1 = CreateEndpoint(address: null); + var endpoint2 = CreateEndpoint(address: null); + var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2); + + // Act + var result = endpointFinder.FindEndpoints(new Address("Admin")); + + // Assert + Assert.Collection( + result, + (ep) => Assert.Same(endpoint1, ep), + (ep) => Assert.Same(endpoint2, ep)); + } + + [Fact] + public void FindEndpoints_ReturnsAllEndpoints_WhenLookupAddress_IsNull() + { + // Arrange + var endpoint1 = CreateEndpoint(new Address("home")); + var endpoint2 = CreateEndpoint(new Address("admin")); + var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2); + + // Act + var result = endpointFinder.FindEndpoints(lookupAddress: null); + + // Assert + Assert.Collection( + result, + (ep) => Assert.Same(endpoint1, ep), + (ep) => Assert.Same(endpoint2, ep)); + } + + [Fact] + public void FindEndpoints_ReturnsAllEndpoints_WhenNoEndpointsHaveAddress_AndLookupAddress_IsNull() + { + // Arrange + var endpoint1 = CreateEndpoint(address: null); + var endpoint2 = CreateEndpoint(address: null); + var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2); + + // Act + var result = endpointFinder.FindEndpoints(lookupAddress: null); + + // Assert + Assert.Collection( + result, + (ep) => Assert.Same(endpoint1, ep), + (ep) => Assert.Same(endpoint2, ep)); + } + + [Fact] + public void FindEndpoints_ReturnsAllEndpoints_WhenNoInformationGiven_OnLookupAddress() + { + // Arrange + var endpoint1 = CreateEndpoint(new Address("home")); + var endpoint2 = CreateEndpoint(new Address("admin")); + var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2); + + // Act + var result = endpointFinder.FindEndpoints(new Address(name: null)); + + // Assert + Assert.Collection( + result, + (ep) => Assert.Same(endpoint1, ep), + (ep) => Assert.Same(endpoint2, ep)); + } + + [Fact] + public void FindEndpoints_ReturnsEmpty_WhenNoEndpointFound_WithLookupAddress_Name() + { + // Arrange + var endpoint1 = CreateEndpoint(new Address("home")); + var endpoint2 = CreateEndpoint(new Address("admin")); + var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2); + + // Act + var result = endpointFinder.FindEndpoints(new Address("DoesNotExist")); + + // Assert + Assert.Empty(result); + } + + private Endpoint CreateEndpoint(Address address) + { + return new TestEndpoint( + EndpointMetadataCollection.Empty, + displayName: null, + address: address); + } + + private DefaultEndpointFinder CreateDefaultEndpointFinder(params Endpoint[] endpoints) + { + return new DefaultEndpointFinder( + new CompositeEndpointDataSource(new[] { new DefaultEndpointDataSource(endpoints) }), + NullLogger.Instance); + } + + private class HomeController + { + public void Index() { } + public void Contact() { } + } + + private class AdminController + { + public void Index() { } + public void Contact() { } + } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs new file mode 100644 index 0000000000..892c69519b --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -0,0 +1,799 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Routing.Internal; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.ObjectPool; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Routing +{ + public class DefaultLinkGeneratorTest + { + [Fact] + public void GetLink_Success() + { + // Arrange + var endpoint = CreateEndpoint("{controller}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext(new { controller = "Home" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home", link); + } + + [Fact] + public void GetLink_Fail_ThrowsException() + { + // Arrange + var expectedMessage = "Could not find a matching endpoint to generate a link."; + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext(new { controller = "Home" }); + + // Act & Assert + var exception = Assert.Throws(() => linkGenerator.GetLink(context)); + Assert.Equal(expectedMessage, exception.Message); + } + + [Fact] + public void TryGetLink_Fail() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext(new { controller = "Home" }); + + // Act + var canGenerateLink = linkGenerator.TryGetLink(context, out var link); + + // Assert + Assert.False(canGenerateLink); + Assert.Null(link); + } + + [Fact] + public void GetLink_MultipleEndpoints_Success() + { + // Arrange + var endpoint1 = CreateEndpoint("{controller}/{action}/{id?}"); + var endpoint2 = CreateEndpoint("{controller}/{action}"); + var endpoint3 = CreateEndpoint("{controller}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint1, endpoint2, endpoint3)); + var context = CreateLinkGeneratorContext(new { controller = "Home", action = "Index", id = "10" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index/10", link); + } + + [Fact] + public void GetLink_MultipleEndpoints_Success2() + { + // Arrange + var endpoint1 = CreateEndpoint("{controller}/{action}/{id}"); + var endpoint2 = CreateEndpoint("{controller}/{action}"); + var endpoint3 = CreateEndpoint("{controller}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint1, endpoint2, endpoint3)); + var context = CreateLinkGeneratorContext(new { controller = "Home", action = "Index" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index", link); + } + + [Fact] + public void GetLink_EncodesValues() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { name = "name with %special #characters" }, + ambientValues: new { controller = "Home", action = "Index" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index?name=name%20with%20%25special%20%23characters", link); + } + + [Fact] + public void GetLink_ForListOfStrings() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + new { color = new List { "red", "green", "blue" } }, + new { controller = "Home", action = "Index" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index?color=red&color=green&color=blue", link); + } + + [Fact] + public void GetLink_ForListOfInts() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + new { items = new List { 10, 20, 30 } }, + new { controller = "Home", action = "Index" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index?items=10&items=20&items=30", link); + } + + [Fact] + public void GetLink_ForList_Empty() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + new { color = new List { } }, + new { controller = "Home", action = "Index" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index", link); + } + + [Fact] + public void GetLink_ForList_StringWorkaround() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + new { page = 1, color = new List { "red", "green", "blue" }, message = "textfortest" }, + new { controller = "Home", action = "Index" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index?page=1&color=red&color=green&color=blue&message=textfortest", link); + } + + [Fact] + public void GetLink_Success_AmbientValues() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { action = "Index" }, + ambientValues: new { controller = "Home" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index", link); + } + + //[Fact] + //public void RouteGenerationRejectsConstraints() + //{ + // // Arrange + // var context = CreateLinkGeneratorContext(new { p1 = "abcd" }); + + // var endpoint = CreateEndpoint( + // "{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 = CreateLinkGeneratorContext(new { p1 = "hello", p2 = "1234" }); + + // var endpoint = CreateEndpoint( + // "{p1}/{p2}", + // new { p2 = "catchall" }, + // true, + // new RouteValueDictionary(new { p2 = "\\d{4}" })); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.NotNull(pathData); + // Assert.Equal("/hello/1234", link); + // + // + //} + + //[Fact] + //public void RouteWithCatchAllRejectsConstraints() + //{ + // // Arrange + // var context = CreateLinkGeneratorContext(new { p1 = "abcd" }); + + // var endpoint = CreateEndpoint( + // "{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 = CreateLinkGeneratorContext(new { p1 = "hello", p2 = "1234" }); + + // var endpoint = CreateEndpoint( + // "{p1}/{*p2}", + // new { p2 = "catchall" }, + // true, + // new RouteValueDictionary(new { p2 = "\\d{4}" })); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.NotNull(pathData); + // Assert.Equal("/hello/1234", link); + // + // + //} + + //[Fact] + //public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString() + //{ + // // Arrange + // var context = CreateLinkGeneratorContext(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 = CreateEndpoint( + // "{p1}/{p2}", + // new { p2 = "catchall" }, + // true, + // new RouteValueDictionary(new { p2 = target.Object })); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // 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 = CreateEndpoint( + // template: "slug/{controller}/{action}", + // defaultValues: null, + // handleRequest: true, + // constraints: new { c = constraint }); + + // var context = CreateLinkGeneratorContext( + // 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(context); + + // // 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 = CreateEndpoint( + // template: "slug/{controller}/{action}", + // defaultValues: new { otherthing = "17" }, + // handleRequest: true, + // constraints: new { c = constraint }); + + // var context = CreateLinkGeneratorContext( + // values: new { action = "Store" }, + // ambientValues: new { Controller = "Home", action = "Blog" }); + + // var expectedValues = new RouteValueDictionary( + // new { controller = "Home", action = "Store" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // 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 = CreateEndpoint( + // template: "slug/{controller}/{action}", + // defaultValues: new { action = "Index" }, + // handleRequest: true, + // constraints: new { c = constraint }); + + // var context = CreateLinkGeneratorContext( + // values: new { controller = "Shopping" }, + // ambientValues: new { Controller = "Home", action = "Blog" }); + + // var expectedValues = new RouteValueDictionary( + // new { controller = "Shopping", action = "Index" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // 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 = CreateEndpoint( + // template: "slug/{controller}/{action}", + // defaultValues: new { otherthing = "17", thirdthing = "13" }, + // handleRequest: true, + // constraints: new { c = constraint }); + + // var context = CreateLinkGeneratorContext( + // 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(context); + + // // 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 = CreateEndpoint("{controller}/{action}/{id:int}"); + // var context = CreateLinkGeneratorContext( + // values: new { action = "Index", controller = "Home", id = 4 }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/Home/Index/4", link); + // + // + //} + + //[Fact] + //public void GetLink_InlineConstraints_NonMatchingvalue() + //{ + // // Arrange + // var endpoint = CreateEndpoint("{controller}/{action}/{id:int}"); + // var context = CreateLinkGeneratorContext( + // 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 = CreateEndpoint("{controller}/{action}/{id:int?}"); + // var context = CreateLinkGeneratorContext( + // values: new { action = "Index", controller = "Home", id = 98 }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/Home/Index/98", link); + // + // + //} + + //[Fact] + //public void GetLink_InlineConstraints_OptionalParameter_ValueNotPresent() + //{ + // // Arrange + // var endpoint = CreateEndpoint("{controller}/{action}/{id:int?}"); + // var context = CreateLinkGeneratorContext( + // values: new { action = "Index", controller = "Home" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/Home/Index", link); + // + // + //} + + //[Fact] + //public void GetLink_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails() + //{ + // // Arrange + // var endpoint = CreateEndpoint("{controller}/{action}/{id:int?}"); + // var context = CreateLinkGeneratorContext( + // 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 = CreateEndpoint("{controller}/{action}/{id:int:range(1,20)}"); + // var context = CreateLinkGeneratorContext( + // values: new { action = "Index", controller = "Home", id = 14 }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/Home/Index/14", link); + // + // + //} + + //[Fact] + //public void GetLink_InlineConstraints_CompositeConstraint_FromConstructor() + //{ + // // Arrange + // var constraint = new MaxLengthRouteConstraint(20); + // var endpoint = CreateEndpoint( + // template: "{controller}/{action}/{name:alpha}", + // defaultValues: null, + // handleRequest: true, + // constraints: new { name = constraint }); + + // var context = CreateLinkGeneratorContext( + // values: new { action = "Index", controller = "Home", name = "products" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/Home/Index/products", link); + // + // + //} + + [Fact] + public void GetLink_OptionalParameter_ParameterPresentInValues() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}/{name?}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { action = "Index", controller = "Home", name = "products" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index/products", link); + } + + [Fact] + public void GetLink_OptionalParameter_ParameterNotPresentInValues() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}/{name?}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { action = "Index", controller = "Home" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index", link); + } + + [Fact] + public void GetLink_OptionalParameter_ParameterPresentInValuesAndDefaults() + { + // Arrange + var endpoint = CreateEndpoint( + template: "{controller}/{action}/{name?}", + defaultValues: new { name = "default-products" }); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { action = "Index", controller = "Home", name = "products" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index/products", link); + } + + [Fact] + public void GetLink_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults() + { + // Arrange + var endpoint = CreateEndpoint( + template: "{controller}/{action}/{name?}", + defaultValues: new { name = "products" }); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { action = "Index", controller = "Home" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index", link); + } + + [Fact] + public void GetLink_ParameterNotPresentInTemplate_PresentInValues() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}/{name}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { action = "Index", controller = "Home", name = "products", format = "json" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index/products?format=json", link); + } + + //[Fact] + //public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent() + //{ + // // Arrange + // var endpoint = CreateEndpoint( + // template: "{controller}/{action}/.{name?}", + // defaultValues: null, + // handleRequest: true, + // constraints: null); + + // var context = CreateLinkGeneratorContext( + // values: new { action = "Index", controller = "Home", name = "products" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/Home/Index/.products", link); + // + // + //} + + //[Fact] + //public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent() + //{ + // // Arrange + // var endpoint = CreateEndpoint( + // template: "{controller}/{action}/.{name?}", + // defaultValues: null, + // handleRequest: true, + // constraints: null); + + // var context = CreateLinkGeneratorContext( + // values: new { action = "Index", controller = "Home" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/Home/Index/", link); + // + // + //} + + [Fact] + public void GetLink_OptionalParameter_InSimpleSegment() + { + // Arrange + var endpoint = CreateEndpoint("{controller}/{action}/{name?}"); + var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + var context = CreateLinkGeneratorContext( + suppliedValues: new { action = "Index", controller = "Home" }); + + // Act + var link = linkGenerator.GetLink(context); + + // Assert + Assert.Equal("/Home/Index", link); + } + + //[Fact] + //public void GetLink_TwoOptionalParameters_OneValueFromAmbientValues() + //{ + // // Arrange + // var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}"); + // var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + // var context = CreateLinkGeneratorContext( + // suppliedValues: new { }, + // ambientValues: new { c = "17" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/a/15/17", link); + //} + + //[Fact] + //public void GetLink_OptionalParameterAfterDefault_OneValueFromAmbientValues() + //{ + // // Arrange + // var endpoint = CreateEndpoint("a/{b=15}/{c?}"); + // var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint)); + // var context = CreateLinkGeneratorContext( + // suppliedValues: new { }, + // ambientValues: new { c = "17" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.Equal("/a/15/17", link); + //} + + //[Fact] + //public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues() + //{ + // // Arrange + // var endpoint = CreateEndpoint( + // template: "a/{b=15}/{c?}/{d?}", + // defaultValues: null, + // handleRequest: true, + // constraints: null); + + // var context = CreateLinkGeneratorContext( + // values: new { }, + // ambientValues: new { d = "17" }); + + // // Act + // var link = linkGenerator.GetLink(context); + + // // Assert + // Assert.NotNull(pathData); + // Assert.Equal("/a", link); + // + // + //} + + private LinkGeneratorContext CreateLinkGeneratorContext(object suppliedValues, object ambientValues = null) + { + var context = new LinkGeneratorContext(); + context.SuppliedValues = new RouteValueDictionary(suppliedValues); + context.AmbientValues = new RouteValueDictionary(ambientValues); + return context; + } + + private MatcherEndpoint CreateEndpoint(string template, object defaultValues = null) + { + return new MatcherEndpoint( + next => (httpContext) => Task.CompletedTask, + template, + defaultValues, + 0, + EndpointMetadataCollection.Empty, + null, + new Address("foo")); + } + + private ILinkGenerator CreateLinkGenerator(IEndpointFinder endpointFinder) + { + return new DefaultLinkGenerator( + endpointFinder, + new DefaultObjectPool(new UriBuilderContextPooledObjectPolicy()), + Mock.Of>()); + } + + private DefaultEndpointFinder CreateEndpointFinder(params Endpoint[] endpoints) + { + return new DefaultEndpointFinder( + new CompositeEndpointDataSource(new[] { new DefaultEndpointDataSource(endpoints) }), + NullLogger.Instance); + } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs index cc2d881df1..6438020dc8 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs @@ -24,11 +24,13 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints // Arrange var defaultEndpoint = new TestEndpoint( EndpointMetadataCollection.Empty, - "No constraint endpoint"); + "No constraint endpoint", + address: null); var postEndpoint = new TestEndpoint( new EndpointMetadataCollection(new object[] { new HttpMethodEndpointConstraint(new[] { "POST" }) }), - "POST constraint endpoint"); + "POST constraint endpoint", + address: null); var endpoints = new Endpoint[] { @@ -60,11 +62,13 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints var defaultEndpoint1 = new TestEndpoint( EndpointMetadataCollection.Empty, - "Ambiguous1"); + "Ambiguous1", + address: null); var defaultEndpoint2 = new TestEndpoint( EndpointMetadataCollection.Empty, - "Ambiguous2"); + "Ambiguous2", + address: null); var endpoints = new Endpoint[] { @@ -96,8 +100,8 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints var actions = new Endpoint[] { - new TestEndpoint(EndpointMetadataCollection.Empty, "A1"), - new TestEndpoint(EndpointMetadataCollection.Empty, "A2"), + new TestEndpoint(EndpointMetadataCollection.Empty, "A1", address: null), + new TestEndpoint(EndpointMetadataCollection.Empty, "A2", address: null), }; var selector = CreateSelector(actions, loggerFactory); @@ -120,9 +124,13 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints // Arrange var actionWithConstraints = new TestEndpoint( new EndpointMetadataCollection(new[] { new HttpMethodEndpointConstraint(new string[] { "POST" }) }), - "Has constraint"); + "Has constraint", + address: null); - var actionWithoutConstraints = new TestEndpoint(EndpointMetadataCollection.Empty, "No constraint"); + var actionWithoutConstraints = new TestEndpoint( + EndpointMetadataCollection.Empty, + "No constraint", + address: null); var actions = new Endpoint[] { actionWithConstraints, actionWithoutConstraints }; @@ -140,9 +148,15 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints public void SelectBestCandidate_ConstraintsRejectAll() { // Arrange - var action1 = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }), "action1"); + var action1 = new TestEndpoint( + new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }), + "action1", + address: null); - var action2 = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }), "action2"); + var action2 = new TestEndpoint( + new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }), + "action2", + address: null); var actions = new Endpoint[] { action1, action2 }; @@ -164,13 +178,17 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints { new BooleanConstraint() { Pass = false, Order = 0 }, new BooleanConstraint() { Pass = true, Order = 1 }, - }), "action1"); + }), + "action1", + address: null); var action2 = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = true, Order = 0 }, new BooleanConstraint() { Pass = false, Order = 1 }, - }), "action2"); + }), + "action2", + address: null); var actions = new Endpoint[] { action1, action2 }; @@ -194,9 +212,14 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints { Constraint = new BooleanConstraint() { Pass = true }, }, - }), "actionWithConstraints"); + }), + "actionWithConstraints", + address: null); - var actionWithoutConstraints = new TestEndpoint(EndpointMetadataCollection.Empty, "actionWithoutConstraints"); + var actionWithoutConstraints = new TestEndpoint( + EndpointMetadataCollection.Empty, + "actionWithoutConstraints", + address: null); var actions = new Endpoint[] { actionWithConstraints, actionWithoutConstraints }; @@ -214,7 +237,7 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints public void SelectBestCandidate_MultipleCallsNoConstraint_ReturnsEndpoint() { // Arrange - var noConstraint = new TestEndpoint(EndpointMetadataCollection.Empty, "noConstraint"); + var noConstraint = new TestEndpoint(EndpointMetadataCollection.Empty, "noConstraint", address: null); var actions = new Endpoint[] { noConstraint }; @@ -237,7 +260,9 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints var noConstraint = new TestEndpoint(new EndpointMetadataCollection(new[] { new object(), - }), "noConstraint"); + }), + "noConstraint", + address: null); var actions = new Endpoint[] { noConstraint }; @@ -260,7 +285,9 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints var nullConstraint = new TestEndpoint(new EndpointMetadataCollection(new[] { new ConstraintFactory(), - }), "nullConstraint"); + }), + "nullConstraint", + address: null); var actions = new Endpoint[] { nullConstraint }; @@ -284,9 +311,14 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints var actionWithConstraints = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraintMarker() { Pass = true }, - }), "actionWithConstraints"); + }), + "actionWithConstraints", + address: null); - var actionWithoutConstraints = new TestEndpoint(EndpointMetadataCollection.Empty, "actionWithoutConstraints"); + var actionWithoutConstraints = new TestEndpoint( + EndpointMetadataCollection.Empty, + "actionWithoutConstraints", + address: null); var actions = new Endpoint[] { actionWithConstraints, actionWithoutConstraints, }; @@ -308,12 +340,16 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints var best = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = true, Order = 0, }, - }), "best"); + }), + "best", + address: null); var worst = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = true, Order = 1, }, - }), "worst"); + }), + "worst", + address: null); var actions = new Endpoint[] { best, worst }; @@ -337,14 +373,18 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints new BooleanConstraint() { Pass = true, Order = 0, }, new BooleanConstraint() { Pass = true, Order = 1, }, new BooleanConstraint() { Pass = true, Order = 2, }, - }), "best"); + }), + "best", + address: null); var worst = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = true, Order = 0, }, new BooleanConstraint() { Pass = true, Order = 1, }, new BooleanConstraint() { Pass = true, Order = 3, }, - }), "worst"); + }), + "worst", + address: null); var actions = new Endpoint[] { best, worst }; @@ -367,16 +407,20 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints new BooleanConstraint() { Pass = true, Order = 0, }, new BooleanConstraint() { Pass = true, Order = 1, }, new BooleanConstraint() { Pass = false, Order = 2, }, - }), "nomatch1"); + }), + "nomatch1", + address: null); var nomatch2 = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = true, Order = 0, }, new BooleanConstraint() { Pass = true, Order = 1, }, new BooleanConstraint() { Pass = false, Order = 3, }, - }), "nomatch2"); + }), + "nomatch2", + address: null); - var best = new TestEndpoint(EndpointMetadataCollection.Empty, "best"); + var best = new TestEndpoint(EndpointMetadataCollection.Empty, "best", address: null); var actions = new Endpoint[] { best, nomatch1, nomatch2 }; diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Internal/HttpMethodEndpointConstraintTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Internal/HttpMethodEndpointConstraintTest.cs index 81e35c4535..1d79fc1b9b 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Internal/HttpMethodEndpointConstraintTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Internal/HttpMethodEndpointConstraintTest.cs @@ -61,7 +61,9 @@ namespace Microsoft.AspNetCore.Routing.Internal { var context = new EndpointConstraintContext(); - var endpointSelectorCandidate = new EndpointSelectorCandidate(new TestEndpoint(EndpointMetadataCollection.Empty, string.Empty), new List { constraint }); + var endpointSelectorCandidate = new EndpointSelectorCandidate( + new TestEndpoint(EndpointMetadataCollection.Empty, string.Empty, address: null), + new List { constraint }); context.Candidates = new List { endpointSelectorCandidate }; context.CurrentCandidate = context.Candidates[0]; diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/MatcherConformanceTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/MatcherConformanceTest.cs index a0b2fca129..350fa57f70 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/MatcherConformanceTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/MatcherConformanceTest.cs @@ -72,7 +72,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers template, null, 0, - EndpointMetadataCollection.Empty, "endpoint: " + template); + EndpointMetadataCollection.Empty, + "endpoint: " + template, + address: null); } internal (Matcher matcher, MatcherEndpoint endpoint) CreateMatcher(string template) diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/TreeMatcherTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/TreeMatcherTests.cs index fa1c78aed4..a08e68ede7 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/TreeMatcherTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/TreeMatcherTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers { private MatcherEndpoint CreateEndpoint(string template, int order, object values = null, EndpointMetadataCollection metadata = null) { - return new MatcherEndpoint((next) => null, template, values, order, metadata ?? EndpointMetadataCollection.Empty, template); + return new MatcherEndpoint((next) => null, template, values, order, metadata ?? EndpointMetadataCollection.Empty, template, address: null); } private TreeMatcher CreateTreeMatcher(EndpointDataSource endpointDataSource) @@ -88,6 +88,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers // Assert Assert.Equal(endpointWithConstraint, endpointFeature.Endpoint); - } } } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestEndpoint.cs b/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestEndpoint.cs index 6cce52a5de..92a1ac8d1b 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestEndpoint.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestEndpoint.cs @@ -5,7 +5,8 @@ namespace Microsoft.AspNetCore.Routing.TestObjects { internal class TestEndpoint : Endpoint { - public TestEndpoint(EndpointMetadataCollection metadata, string displayName) : base(metadata, displayName) + public TestEndpoint(EndpointMetadataCollection metadata, string displayName, Address address) + : base(metadata, displayName, address) { } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestMatcher.cs b/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestMatcher.cs index 3522a5ea53..5ee7031fb5 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestMatcher.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/TestObjects/TestMatcher.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Routing.TestObjects { if (_isHandled) { - feature.Endpoint = new TestEndpoint(EndpointMetadataCollection.Empty, "Test endpoint"); + feature.Endpoint = new TestEndpoint(EndpointMetadataCollection.Empty, "Test endpoint", address: null); } return Task.CompletedTask;