From b177ba5309a055c942ac1b4a0277c4d44469031e Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Mon, 30 Jul 2018 15:44:37 -0700 Subject: [PATCH 1/9] Changed api of LinkGenerator --- .../UseEndpointRoutingStartup.cs | 8 +- .../LinkGenerator.cs | 148 ++- ...LinkGeneratorContext.cs => LinkOptions.cs} | 13 +- .../DefaultLinkGenerator.cs | 163 ++-- .../DefaultLinkGeneratorTest.cs | 889 +++++++----------- 5 files changed, 601 insertions(+), 620 deletions(-) rename src/Microsoft.AspNetCore.Routing.Abstractions/{LinkGeneratorContext.cs => LinkOptions.cs} (74%) diff --git a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs index 06dab7eddb..d67d5fbe2a 100644 --- a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs +++ b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs @@ -25,11 +25,11 @@ namespace RoutingSample.Web { options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor)); }); - + services.Configure(options => { - options.DataSources.Add(new DefaultEndpointDataSource(new[] - { + options.DataSources.Add(new DefaultEndpointDataSource(new[] + { new MatcherEndpoint((next) => (httpContext) => { var response = httpContext.Response; @@ -83,7 +83,7 @@ namespace RoutingSample.Web EndpointMetadataCollection.Empty, "withoptionalconstraints"), })); - }); + }); } public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app) diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs index df5c1b5910..f4101a609a 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs @@ -1,13 +1,157 @@ // 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 Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Routing { public abstract class LinkGenerator { - public abstract bool TryGetLink(LinkGeneratorContext context, out string link); + public string GetLink(object values) + { + return GetLink(httpContext: null, routeName: null, values, options: null); + } - public abstract string GetLink(LinkGeneratorContext context); + public string GetLink(object values, LinkOptions options) + { + return GetLink(httpContext: null, routeName: null, values, options); + } + + public bool TryGetLink(object values, out string link) + { + return TryGetLink(httpContext: null, routeName: null, values, options: null, out link); + } + + public bool TryGetLink(object values, LinkOptions options, out string link) + { + return TryGetLink(httpContext: null, routeName: null, values, options, out link); + } + + public string GetLink(HttpContext httpContext, object values) + { + return GetLink(httpContext, routeName: null, values, options: null); + } + + public bool TryGetLink(HttpContext httpContext, object values, out string link) + { + return TryGetLink(httpContext, routeName: null, values, options: null, out link); + } + + public string GetLink(HttpContext httpContext, object values, LinkOptions options) + { + return GetLink(httpContext, routeName: null, values, options); + } + + public bool TryGetLink(HttpContext httpContext, object values, LinkOptions options, out string link) + { + return TryGetLink(httpContext, routeName: null, values, options, out link); + } + + public string GetLink(string routeName, object values) + { + return GetLink(httpContext: null, routeName, values, options: null); + } + + public bool TryGetLink(string routeName, object values, out string link) + { + return TryGetLink(httpContext: null, routeName, values, options: null, out link); + } + + public string GetLink(string routeName, object values, LinkOptions options) + { + return GetLink(httpContext: null, routeName, values, options); + } + + public bool TryGetLink(string routeName, object values, LinkOptions options, out string link) + { + return TryGetLink(httpContext: null, routeName, values, options, out link); + } + + public string GetLink(HttpContext httpContext, string routeName, object values) + { + return GetLink(httpContext, routeName, values, options: null); + } + + public bool TryGetLink(HttpContext httpContext, string routeName, object values, out string link) + { + return TryGetLink(httpContext, routeName, values, options: null, out link); + } + + public string GetLink(HttpContext httpContext, string routeName, object values, LinkOptions options) + { + if (TryGetLink(httpContext, routeName, values, options, out var link)) + { + return link; + } + + throw new InvalidOperationException("Could not find a matching endpoint to generate a link."); + } + + public abstract bool TryGetLink( + HttpContext httpContext, + string routeName, + object values, + LinkOptions options, + out string link); + + public string GetLinkByAddress(TAddress address, object values) + { + return GetLinkByAddress(address, httpContext: null, values, options: null); + } + + public bool TryGetLinkByAddress(TAddress address, object values, out string link) + { + return TryGetLinkByAddress(address, values, options: null, out link); + } + + public string GetLinkByAddress(TAddress address, object values, LinkOptions options) + { + return GetLinkByAddress(address, httpContext: null, values, options); + } + + public bool TryGetLinkByAddress( + TAddress address, + object values, + LinkOptions options, + out string link) + { + return TryGetLinkByAddress(address, httpContext: null, values, options, out link); + } + + public string GetLinkByAddress(TAddress address, HttpContext httpContext, object values) + { + return GetLinkByAddress(address, httpContext, values, options: null); + } + + public bool TryGetLinkByAddress( + TAddress address, + HttpContext httpContext, + object values, + out string link) + { + return TryGetLinkByAddress(address, httpContext, values, options: null, out link); + } + + public string GetLinkByAddress( + TAddress address, + HttpContext httpContext, + object values, + LinkOptions options) + { + if (TryGetLinkByAddress(address, httpContext, values, options, out var link)) + { + return link; + } + + throw new InvalidOperationException("Could not find a matching endpoint to generate a link."); + } + + public abstract bool TryGetLinkByAddress( + TAddress address, + HttpContext httpContext, + object values, + LinkOptions options, + out string link); } } diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGeneratorContext.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkOptions.cs similarity index 74% rename from src/Microsoft.AspNetCore.Routing.Abstractions/LinkGeneratorContext.cs rename to src/Microsoft.AspNetCore.Routing.Abstractions/LinkOptions.cs index c1e04c17e0..a5bd0547f0 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGeneratorContext.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkOptions.cs @@ -1,21 +1,10 @@ // 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 { - public class LinkGeneratorContext + public class LinkOptions { - public HttpContext HttpContext { get; set; } - - public IEnumerable Endpoints { get; set; } - - public RouteValueDictionary ExplicitValues { get; set; } - - public RouteValueDictionary AmbientValues { get; set; } - /// /// Gets or sets a value indicating whether all generated paths URLs are lower-case. /// Use to configure the behavior for query strings. diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs index b515643139..13d2869d32 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Template; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; @@ -19,60 +20,106 @@ namespace Microsoft.AspNetCore.Routing private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' }; private readonly MatchProcessorFactory _matchProcessorFactory; private readonly ObjectPool _uriBuildingContextPool; - private readonly RouteOptions _options; private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + private readonly RouteOptions _options; public DefaultLinkGenerator( MatchProcessorFactory matchProcessorFactory, ObjectPool uriBuildingContextPool, IOptions routeOptions, - ILogger logger) + ILogger logger, + IServiceProvider serviceProvider) { _matchProcessorFactory = matchProcessorFactory; _uriBuildingContextPool = uriBuildingContextPool; _options = routeOptions.Value; _logger = logger; + _serviceProvider = serviceProvider; } - public override string GetLink(LinkGeneratorContext context) + public override bool TryGetLink( + HttpContext httpContext, + string routeName, + object values, + LinkOptions options, + out string link) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (TryGetLink(context, out var link)) - { - return link; - } - - throw new InvalidOperationException("Could not find a matching endpoint to generate a link."); + return TryGetLinkByRouteValues( + httpContext, + routeName, + values, + options, + out link); } - public override bool TryGetLink(LinkGeneratorContext context, out string link) + public override bool TryGetLinkByAddress( + TAddress address, + HttpContext httpContext, + object values, + LinkOptions options, + out string link) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } + return TryGetLinkByAddressInternal( + address, + httpContext, + explicitValues: values, + ambientValues: GetAmbientValues(httpContext), + options, + out link); + } + private bool TryGetLinkByRouteValues( + HttpContext httpContext, + string routeName, + object values, + LinkOptions options, + out string link) + { + var ambientValues = GetAmbientValues(httpContext); + + var address = new RouteValuesAddress + { + RouteName = routeName, + ExplicitValues = new RouteValueDictionary(values), + AmbientValues = ambientValues + }; + + return TryGetLinkByAddressInternal( + address, + httpContext, + explicitValues: values, + ambientValues: ambientValues, + options, + out link); + } + + private bool TryGetLinkByAddressInternal( + TAddress address, + HttpContext httpContext, + object explicitValues, + RouteValueDictionary ambientValues, + LinkOptions options, + out string link) + { link = null; - if (context.Endpoints == null) + var endpointFinder = _serviceProvider.GetRequiredService>(); + var endpoints = endpointFinder.FindEndpoints(address); + if (endpoints == null) { return false; } - var matcherEndpoints = context.Endpoints.OfType(); + var matcherEndpoints = endpoints.OfType(); if (!matcherEndpoints.Any()) { - //todo:log here return false; } foreach (var endpoint in matcherEndpoints) { - link = GetLink(endpoint, context); + link = GetLink(endpoint); if (link != null) { return true; @@ -80,34 +127,33 @@ namespace Microsoft.AspNetCore.Routing } return false; - } - private string GetLink(MatcherEndpoint endpoint, LinkGeneratorContext context) - { - var templateBinder = new TemplateBinder( - UrlEncoder.Default, - _uriBuildingContextPool, - new RouteTemplate(endpoint.RoutePattern), - new RouteValueDictionary(endpoint.RoutePattern.Defaults)); - - var templateValuesResult = templateBinder.GetValues( - ambientValues: context.AmbientValues, - explicitValues: context.ExplicitValues, - endpoint.RequiredValues.Keys); - - if (templateValuesResult == null) + string GetLink(MatcherEndpoint endpoint) { - // We're missing one of the required values for this route. - return null; - } + var templateBinder = new TemplateBinder( + UrlEncoder.Default, + _uriBuildingContextPool, + new RouteTemplate(endpoint.RoutePattern), + new RouteValueDictionary(endpoint.RoutePattern.Defaults)); - if (!MatchesConstraints(context.HttpContext, endpoint, templateValuesResult.CombinedValues)) - { - return null; - } + var templateValuesResult = templateBinder.GetValues( + ambientValues: ambientValues, + explicitValues: new RouteValueDictionary(explicitValues), + requiredKeys: endpoint.RequiredValues.Keys); + if (templateValuesResult == null) + { + // We're missing one of the required values for this route. + return null; + } - var url = templateBinder.BindValues(templateValuesResult.AcceptedValues); - return Normalize(context, url); + if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues)) + { + return null; + } + + var url = templateBinder.BindValues(templateValuesResult.AcceptedValues); + return Normalize(url, options); + } } private bool MatchesConstraints( @@ -138,13 +184,11 @@ namespace Microsoft.AspNetCore.Routing return true; } - private string Normalize(LinkGeneratorContext context, string url) + private string Normalize(string url, LinkOptions options) { - var lowercaseUrls = context.LowercaseUrls.HasValue ? context.LowercaseUrls.Value : _options.LowercaseUrls; - var lowercaseQueryStrings = context.LowercaseQueryStrings.HasValue ? - context.LowercaseQueryStrings.Value : _options.LowercaseQueryStrings; - var appendTrailingSlash = context.AppendTrailingSlash.HasValue ? - context.AppendTrailingSlash.Value : _options.AppendTrailingSlash; + var lowercaseUrls = options?.LowercaseUrls ?? _options.LowercaseUrls; + var lowercaseQueryStrings = options?.LowercaseQueryStrings ?? _options.LowercaseQueryStrings; + var appendTrailingSlash = options?.AppendTrailingSlash ?? _options.AppendTrailingSlash; if (!string.IsNullOrEmpty(url) && (lowercaseUrls || appendTrailingSlash)) { @@ -179,5 +223,18 @@ namespace Microsoft.AspNetCore.Routing return url; } + + private RouteValueDictionary GetAmbientValues(HttpContext httpContext) + { + if (httpContext != null) + { + var feature = httpContext.Features.Get(); + if (feature != null) + { + return feature.Values; + } + } + return null; + } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs index 6c5a47116e..1063db6819 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.TestObjects; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; @@ -24,17 +26,10 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress(new { controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(new { controller = "Home" }); // Assert Assert.Equal("/Home", link); @@ -46,18 +41,11 @@ namespace Microsoft.AspNetCore.Routing // Arrange var expectedMessage = "Could not find a matching endpoint to generate a link."; var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress(new { controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); // Act & Assert var exception = Assert.Throws( - () => linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - })); + () => linkGenerator.GetLink(new { controller = "Home" })); Assert.Equal(expectedMessage, exception.Message); } @@ -66,17 +54,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress(new { controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + new { controller = "Home" }, out var link); // Assert @@ -91,17 +73,10 @@ namespace Microsoft.AspNetCore.Routing var endpoint1 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{id?}"); var endpoint2 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); var endpoint3 = EndpointFactory.CreateMatcherEndpoint("{controller}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress(new { controller = "Home", action = "Index", id = "10" }); + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2, endpoint3); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint1, endpoint2, endpoint3 }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(new { controller = "Home", action = "Index", id = "10" }); // Assert Assert.Equal("/Home/Index/10", link); @@ -114,17 +89,10 @@ namespace Microsoft.AspNetCore.Routing var endpoint1 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{id}"); var endpoint2 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); var endpoint3 = EndpointFactory.CreateMatcherEndpoint("{controller}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress(new { controller = "Home", action = "Index" }); + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2, endpoint3); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint1, endpoint2, endpoint3 }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(new { controller = "Home", action = "Index" }); // Assert Assert.Equal("/Home/Index", link); @@ -135,19 +103,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { name = "name with %special #characters" }, - ambientValues: new { controller = "Home", action = "Index" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { name = "name with %special #characters" }); // Assert Assert.Equal("/Home/Index?name=name%20with%20%25special%20%23characters", link); @@ -158,19 +118,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - new { color = new List { "red", "green", "blue" } }, - new { controller = "Home", action = "Index" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var context = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(context, new { color = new List { "red", "green", "blue" } }); // Assert Assert.Equal("/Home/Index?color=red&color=green&color=blue", link); @@ -181,19 +133,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - new { items = new List { 10, 20, 30 } }, - new { controller = "Home", action = "Index" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { items = new List { 10, 20, 30 } }); // Assert Assert.Equal("/Home/Index?items=10&items=20&items=30", link); @@ -204,19 +148,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - new { color = new List { } }, - new { controller = "Home", action = "Index" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { color = new List { } }); // Assert Assert.Equal("/Home/Index", link); @@ -227,19 +163,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - new { page = 1, color = new List { "red", "green", "blue" }, message = "textfortest" }, - new { controller = "Home", action = "Index" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { page = 1, color = new List { "red", "green", "blue" }, message = "textfortest" }); // Assert Assert.Equal("/Home/Index?page=1&color=red&color=green&color=blue&message=textfortest", link); @@ -250,19 +180,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index" }, - ambientValues: new { controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Index" }); // Assert Assert.Equal("/Home/Index", link); @@ -273,19 +195,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index" }, - ambientValues: new { controller = "Home" }); + var linkGenerator = CreateLinkGenerator(new[] { endpoint }, new RouteOptions() { LowercaseUrls = true }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Index" }); // Assert Assert.Equal("/home/index", link); @@ -297,19 +211,14 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, - ambientValues: new { controller = "Home" }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }); // Assert Assert.Equal("/home/index?showstatus=true&info=detailed", link); @@ -321,19 +230,14 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = true }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, - ambientValues: new { controller = "Home" }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }); // Assert Assert.Equal("/Home/Index?ShowStatus=True&INFO=DETAILED", link); @@ -344,19 +248,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = true }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index" }, - ambientValues: new { controller = "Home" }); + var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, + new RouteOptions() { AppendTrailingSlash = true }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Index" }); // Assert Assert.Equal("/Home/Index/", link); @@ -368,19 +266,14 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, - ambientValues: new { controller = "Home" }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }); // Assert Assert.Equal("/home/index/?showstatus=true&info=detailed", link); @@ -391,18 +284,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "InDex" }, - ambientValues: new { controller = "HoMe" }); + var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, + new RouteOptions() { LowercaseUrls = true }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext + httpContext, + values: new { action = "InDex" }, + new LinkOptions { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues, LowercaseUrls = false }); @@ -415,18 +307,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = false }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "InDex" }, - ambientValues: new { controller = "HoMe" }); + var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, + new RouteOptions() { LowercaseUrls = false }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext + httpContext, + values: new { action = "InDex" }, + new LinkOptions { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues, LowercaseUrls = true }); @@ -440,18 +331,16 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, - ambientValues: new { controller = "Home" }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext + httpContext, + values: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, + new LinkOptions { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues, LowercaseUrls = false, LowercaseQueryStrings = false }); @@ -466,18 +355,16 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, - ambientValues: new { controller = "Home" }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext + httpContext, + values: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, + new LinkOptions { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues, LowercaseUrls = true, LowercaseQueryStrings = true }); @@ -491,18 +378,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = false }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index" }, - ambientValues: new { controller = "Home" }); + var linkGenerator = CreateLinkGenerator( + new[] { endpoint }, + new RouteOptions() { AppendTrailingSlash = false }); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext + httpContext, + values: new { action = "Index" }, + new LinkOptions { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues, AppendTrailingSlash = true }); @@ -514,23 +400,17 @@ namespace Microsoft.AspNetCore.Routing public void RouteGenerationRejectsConstraints() { // Arrange - var address = CreateRouteValuesAddress(new { p1 = "abcd" }); - var linkGenerator = CreateLinkGenerator(); - var endpoint = EndpointFactory.CreateMatcherEndpoint( "{p1}/{p2}", defaults: new { p2 = "catchall" }, constraints: new { p2 = "\\d{4}" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { p1 = "abcd" }, out var link); // Assert @@ -541,23 +421,17 @@ namespace Microsoft.AspNetCore.Routing public void RouteGenerationAcceptsConstraints() { // Arrange - var address = CreateRouteValuesAddress(new { p1 = "hello", p2 = "1234" }); - var linkGenerator = CreateLinkGenerator(); - var endpoint = EndpointFactory.CreateMatcherEndpoint( "{p1}/{p2}", defaults: new { p2 = "catchall" }, constraints: new { p2 = new RegexRouteConstraint("\\d{4}"), }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { p1 = "hello", p2 = "1234" }, out var link); // Assert @@ -569,23 +443,17 @@ namespace Microsoft.AspNetCore.Routing public void RouteWithCatchAllRejectsConstraints() { // Arrange - var address = CreateRouteValuesAddress(new { p1 = "abcd" }); - var linkGenerator = CreateLinkGenerator(); - var endpoint = EndpointFactory.CreateMatcherEndpoint( "{p1}/{*p2}", defaults: new { p2 = "catchall" }, constraints: new { p2 = new RegexRouteConstraint("\\d{4}") }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { p1 = "abcd" }, out var link); // Assert @@ -596,23 +464,17 @@ namespace Microsoft.AspNetCore.Routing public void RouteWithCatchAllAcceptsConstraints() { // Arrange - var address = CreateRouteValuesAddress(new { p1 = "hello", p2 = "1234" }); - var linkGenerator = CreateLinkGenerator(); - var endpoint = EndpointFactory.CreateMatcherEndpoint( "{p1}/{*p2}", defaults: new { p2 = "catchall" }, constraints: new { p2 = new RegexRouteConstraint("\\d{4}") }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { p1 = "hello", p2 = "1234" }, out var link); // Assert @@ -624,8 +486,6 @@ namespace Microsoft.AspNetCore.Routing public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString() { // Arrange - var address = CreateRouteValuesAddress(new { p1 = "hello", p2 = "1234" }); - var linkGenerator = CreateLinkGenerator(); var target = new Mock(); target .Setup( @@ -637,21 +497,17 @@ namespace Microsoft.AspNetCore.Routing It.IsAny())) .Returns(true) .Verifiable(); - var endpoint = EndpointFactory.CreateMatcherEndpoint( "{p1}/{p2}", defaults: new { p2 = "catchall" }, constraints: new { p2 = target.Object }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { p1 = "hello", p2 = "1234" }, out var link); // Assert @@ -667,28 +523,20 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var constraint = new CapturingConstraint(); - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "slug/Home/Store", defaults: new { controller = "Home", action = "Store" }, constraints: new { c = constraint }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Store" }, + 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 canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { action = "Store" }, out var link); // Assert @@ -704,28 +552,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var constraint = new CapturingConstraint(); - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "slug/Home/Store", defaults: new { controller = "Home", action = "Store", otherthing = "17" }, constraints: new { c = constraint }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Store" }, - ambientValues: new { controller = "Home", action = "Blog" }); - + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" }); var expectedValues = new RouteValueDictionary( new { controller = "Home", action = "Store" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Store" }); // Assert Assert.Equal("/slug/Home/Store", link); @@ -738,28 +575,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var constraint = new CapturingConstraint(); - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "slug/{controller}/{action}", defaults: new { action = "Index" }, constraints: new { c = constraint, }); - - var address = CreateRouteValuesAddress( - explicitValues: new { controller = "Shopping" }, - ambientValues: new { controller = "Home", action = "Blog" }); - + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" }); var expectedValues = new RouteValueDictionary( new { controller = "Shopping", action = "Index" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { controller = "Shopping" }); // Assert Assert.Equal("/slug/Shopping", link); @@ -773,14 +599,12 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var constraint = new CapturingConstraint(); - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "slug/Home/Store", defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }, constraints: new { c = constraint, }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Store", thirdthing = "13" }, + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext( ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" }); var expectedValues = new RouteValueDictionary( @@ -788,13 +612,8 @@ namespace Microsoft.AspNetCore.Routing // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Store", thirdthing = "13" }); // Assert Assert.Equal("/slug/Home/Store", link); @@ -805,24 +624,17 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_InlineConstraints_Success() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{id:int}", defaults: new { controller = "Home", action = "Index" }, constraints: new { }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", id = 4 }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home", id = 4 }); // Assert Assert.Equal("/Home/Index/4", link); @@ -832,24 +644,17 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_InlineConstraints_NonMatchingvalue() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{id}", defaults: new { controller = "Home", action = "Index" }, constraints: new { id = "int" }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", id = "not-an-integer" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { action = "Index", controller = "Home", id = "not-an-integer" }, out var link); // Assert @@ -860,23 +665,15 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_InlineConstraints_OptionalParameter_ValuePresent() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{id:int?}", defaults: new { controller = "Home", action = "Index" }, constraints: new { }); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", id = 98 }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Index", controller = "Home", id = 98 }); // Assert Assert.Equal("/Home/Index/98", link); @@ -886,24 +683,15 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_InlineConstraints_OptionalParameter_ValueNotPresent() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{id?}", defaults: new { controller = "Home", action = "Index" }, constraints: new { id = "int" }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Index", controller = "Home" }); // Assert Assert.Equal("/Home/Index", link); @@ -913,24 +701,17 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{id?}", defaults: new { controller = "Home", action = "Index" }, constraints: new { id = "int" }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", id = "not-an-integer" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { action = "Index", controller = "Home", id = "not-an-integer" }, out var link); // Assert @@ -941,24 +722,17 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_InlineConstraints_MultipleInlineConstraints() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{id:int:range(1,20)}", defaults: new { controller = "Home", action = "Index" }, constraints: new { }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", id = 14 }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home", id = 14 }); // Assert Assert.Equal("/Home/Index/14", link); @@ -968,24 +742,17 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_InlineConstraints_CompositeInlineConstraint_Fails() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{id:int:range(1,20)}", defaults: new { controller = "Home", action = "Index" }, constraints: new { }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", id = 50 }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { action = "Index", controller = "Home", id = 50 }, out var link); // Assert @@ -997,24 +764,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var constraint = new MaxLengthRouteConstraint(20); - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "Home/Index/{name}", defaults: new { controller = "Home", action = "Index" }, constraints: new { name = constraint }); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", name = "products" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home", name = "products" }); // Assert Assert.Equal("/Home/Index/products", link); @@ -1025,18 +785,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", name = "products" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home", name = "products" }); // Assert Assert.Equal("/Home/Index/products", link); @@ -1047,18 +802,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home" }); // Assert Assert.Equal("/Home/Index", link); @@ -1071,18 +821,13 @@ namespace Microsoft.AspNetCore.Routing var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "{controller}/{action}/{name}", defaults: new { name = "default-products" }); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", name = "products" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home", name = "products" }); // Assert Assert.Equal("/Home/Index/products", link); @@ -1095,18 +840,13 @@ namespace Microsoft.AspNetCore.Routing var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "{controller}/{action}/{name}", defaults: new { name = "products" }); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home" }); // Assert Assert.Equal("/Home/Index", link); @@ -1117,18 +857,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", name = "products", format = "json" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home", name = "products", format = "json" }); // Assert Assert.Equal("/Home/Index/products?format=json", link); @@ -1138,22 +873,15 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint( template: "{controller}/{action}/.{name?}"); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home", name = "products" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + httpContext, + new { action = "Index", controller = "Home", name = "products" }); // Assert Assert.Equal("/Home/Index/.products", link); @@ -1163,21 +891,12 @@ namespace Microsoft.AspNetCore.Routing public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent() { // Arrange - var linkGenerator = CreateLinkGenerator(); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/.{name?}"); - - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - HttpContext = new DefaultHttpContext(), - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Index", controller = "Home" }); // Assert Assert.Equal("/Home/Index/", link); @@ -1188,18 +907,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { action = "Index", controller = "Home" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { action = "Index", controller = "Home" }); // Assert Assert.Equal("/Home/Index", link); @@ -1210,19 +922,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}/{d?}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { }, - ambientValues: new { c = "17" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { c = "17" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { }); // Assert Assert.Equal("/a/15/17", link); @@ -1233,19 +937,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { }, - ambientValues: new { c = "17" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { c = "17" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { }); // Assert Assert.Equal("/a/15/17", link); @@ -1256,19 +952,11 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}/{d?}"); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { }, - ambientValues: new { d = "17" }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { d = "17" }); // Act - var link = linkGenerator.GetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }); + var link = linkGenerator.GetLink(httpContext, new { }); // Assert Assert.Equal("/a", link); @@ -1360,19 +1048,13 @@ namespace Microsoft.AspNetCore.Routing "Products/Edit/{id}", requiredValues: requiredValues, defaults: defaults); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: explicitValues, - ambientValues: ambientValues); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new RouteValueDictionary(explicitValues), out var link); // Assert @@ -1390,19 +1072,13 @@ namespace Microsoft.AspNetCore.Routing "Products/Edit/{id}", requiredValues: new { c = "Products", a = "Edit" }, defaults: new { c = "Products", a = "Edit" }); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { c = "Products", a = "Edit" }, - ambientValues: new { c = "Products", a = "Edit", id = 10 }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { c = "Products", a = "Edit" }, out var link); // Assert @@ -1420,19 +1096,13 @@ namespace Microsoft.AspNetCore.Routing "Products/Edit/{id}", requiredValues: new { c = "Products", a = "Edit" }, defaults: new { c = "Products", a = "Edit" }); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: new { c = "Products", a = "List" }, - ambientValues: new { c = "Products", a = "Edit", id = 10 }); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 }); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new { c = "Products", a = "List" }, out var link); // Assert @@ -1532,19 +1202,13 @@ namespace Microsoft.AspNetCore.Routing "Products/Edit/{id}", requiredValues: requiredValues, defaults: defaults); - var linkGenerator = CreateLinkGenerator(); - var address = CreateRouteValuesAddress( - explicitValues: explicitValues, - ambientValues: ambientValues); + var linkGenerator = CreateLinkGenerator(endpoint); + var httpContext = CreateHttpContext(ambientValues); // Act var canGenerateLink = linkGenerator.TryGetLink( - new LinkGeneratorContext - { - Endpoints = new[] { endpoint }, - ExplicitValues = address.ExplicitValues, - AmbientValues = address.AmbientValues - }, + httpContext, + new RouteValueDictionary(explicitValues), out var link); // Assert @@ -1552,27 +1216,154 @@ namespace Microsoft.AspNetCore.Routing Assert.Null(link); } - private RouteValuesAddress CreateRouteValuesAddress( - object explicitValues, - object ambientValues = null) + [Fact] + public void TryGetLink_WithCustomAddress_CanGenerateLink() { - var address = new RouteValuesAddress(); - address.ExplicitValues = new RouteValueDictionary(explicitValues); - address.AmbientValues = new RouteValueDictionary(ambientValues); - return address; + // Arrange + var services = GetBasicServices(); + services.TryAddEnumerable( + ServiceDescriptor.Singleton, EndpointFinderByName>()); + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Products/Details/{id}", + requiredValues: new { controller = "Products", action = "Details" }, + defaults: new { controller = "Products", action = "Details" }); + var endpoint2 = EndpointFactory.CreateMatcherEndpoint( + "Customers/Details/{id}", + requiredValues: new { controller = "Customers", action = "Details" }, + defaults: new { controller = "Customers", action = "Details" }, + metadata: new NameMetadata("CustomerDetails")); + var linkGenerator = CreateLinkGenerator(new[] { endpoint1, endpoint2 }, new RouteOptions(), services); + var httpContext = CreateHttpContext(ambientValues: new { }); + + // Act + var canGenerateLink = linkGenerator.TryGetLinkByAddress( + address: new NameMetadata("CustomerDetails"), + httpContext, + values: new { id = 10 }, + out var link); + + // Assert + Assert.True(canGenerateLink); + Assert.Equal("/Customers/Details/10", link); } - private LinkGenerator CreateLinkGenerator(RouteOptions routeOptions = null) + [Fact] + public void TryGetLink_WithCustomAddress_CanGenerateLink_RespectsLinkOptions_SuppliedAtCallSite() { + // Arrange + var services = GetBasicServices(); + services.TryAddEnumerable( + ServiceDescriptor.Singleton, EndpointFinderByName>()); + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Products/Details/{id}", + requiredValues: new { controller = "Products", action = "Details" }, + defaults: new { controller = "Products", action = "Details" }); + var endpoint2 = EndpointFactory.CreateMatcherEndpoint( + "Customers/Details/{id}", + requiredValues: new { controller = "Customers", action = "Details" }, + defaults: new { controller = "Customers", action = "Details" }, + metadata: new NameMetadata("CustomerDetails")); + var linkGenerator = CreateLinkGenerator(new[] { endpoint1, endpoint2 }, new RouteOptions(), services); + var httpContext = CreateHttpContext(ambientValues: new { }); + + // Act + var canGenerateLink = linkGenerator.TryGetLinkByAddress( + address: new NameMetadata("CustomerDetails"), + httpContext, + values: new { id = 10 }, + new LinkOptions + { + LowercaseUrls = true + }, + out var link); + + // Assert + Assert.True(canGenerateLink); + Assert.Equal("/customers/details/10", link); + } + + private LinkGenerator CreateLinkGenerator(params Endpoint[] endpoints) + { + return CreateLinkGenerator(endpoints, routeOptions: null); + } + + private LinkGenerator CreateLinkGenerator( + Endpoint[] endpoints, + RouteOptions routeOptions, + ServiceCollection services = null) + { + if (services == null) + { + services = GetBasicServices(); + } + + if (endpoints != null || endpoints.Length > 0) + { + services.Configure(o => + { + o.DataSources.Add(new DefaultEndpointDataSource(endpoints)); + }); + } + routeOptions = routeOptions ?? new RouteOptions(); var options = Options.Create(routeOptions); + var serviceProvider = services.BuildServiceProvider(); + return new DefaultLinkGenerator( - new DefaultMatchProcessorFactory( - options, - Mock.Of()), + new DefaultMatchProcessorFactory(options, serviceProvider), new DefaultObjectPool(new UriBuilderContextPooledObjectPolicy()), options, - NullLogger.Instance); + NullLogger.Instance, + serviceProvider); + } + + private HttpContext CreateHttpContext(object ambientValues) + { + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new EndpointFeature + { + Values = new RouteValueDictionary(ambientValues) + }); + return httpContext; + } + + private ServiceCollection GetBasicServices() + { + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddOptions(); + services.AddRouting(); + services.AddLogging(); + return services; + } + + private class EndpointFinderByName : IEndpointFinder + { + private readonly CompositeEndpointDataSource _dataSource; + + public EndpointFinderByName(CompositeEndpointDataSource dataSource) + { + _dataSource = dataSource; + } + + public IEnumerable FindEndpoints(INameMetadata address) + { + var endpoint = _dataSource.Endpoints.SingleOrDefault(e => + { + var nameMetadata = e.Metadata.GetMetadata(); + return nameMetadata != null && string.Equals(address.Name, nameMetadata.Name); + }); + return new[] { endpoint }; + } + } + + private class NameMetadata : INameMetadata + { + public NameMetadata(string name) + { + Name = name; + } + public string Name { get; } } } } From c8946a40e4311b535d50a79c35279f1a4ca27255 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Thu, 2 Aug 2018 07:43:11 -0700 Subject: [PATCH 2/9] Created LinkGenerationTemplate and friends --- .../LinkGenerationTemplate.cs | 29 ++ .../LinkGenerator.cs | 326 +++++++++++++++++- .../DefaultLinkGenerationTemplate.cs | 62 ++++ .../DefaultLinkGenerator.cs | 167 +++++++-- .../IEndpointFinderOfT.cs | 9 + .../DefaultLinkGeneratorTest.cs | 188 +++++++++- 6 files changed, 729 insertions(+), 52 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs create mode 100644 src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs new file mode 100644 index 0000000000..ec3736228a --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerationTemplate.cs @@ -0,0 +1,29 @@ +// 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 +{ + /// + /// Defines a contract to generate a URL from a template. + /// + public abstract class LinkGenerationTemplate + { + /// + /// Generates a URL with an absolute path from the specified route values. + /// + /// An object that contains route values. + /// The generated URL. + public string MakeUrl(object values) + { + return MakeUrl(values, options: null); + } + + /// + /// Generates a URL with an absolute path from the specified route values and link options. + /// + /// An object that contains route values. + /// The . + /// The generated URL. + public abstract string MakeUrl(object values, LinkOptions options); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs index f4101a609a..35b9456bf6 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/LinkGenerator.cs @@ -6,78 +6,191 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Routing { + /// + /// Defines a contract to generate URLs to endpoints. + /// public abstract class LinkGenerator { + /// + /// Generates a URL with an absolute path from the specified route values. + /// + /// An object that contains route values. + /// The generated URL. public string GetLink(object values) { return GetLink(httpContext: null, routeName: null, values, options: null); } + /// + /// Generates a URL with an absolute path from the specified route values and link options. + /// + /// An object that contains route values. + /// The . + /// The generated URL. public string GetLink(object values, LinkOptions options) { return GetLink(httpContext: null, routeName: null, values, options); } + /// + /// Generates a URL with an absolute path from the specified route values. + /// A return value indicates whether the operation succeeded. + /// + /// An object that contains route values. + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLink(object values, out string link) { return TryGetLink(httpContext: null, routeName: null, values, options: null, out link); } + /// + /// Generates a URL with an absolute path from the specified route values and link options. + /// A return value indicates whether the operation succeeded. + /// + /// An object that contains route values. + /// The . + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLink(object values, LinkOptions options, out string link) { return TryGetLink(httpContext: null, routeName: null, values, options, out link); } + /// + /// Generates a URL with an absolute path from the specified route values. + /// + /// The associated with current request. + /// An object that contains route values. + /// The generated URL. public string GetLink(HttpContext httpContext, object values) { return GetLink(httpContext, routeName: null, values, options: null); } + /// + /// Generates a URL with an absolute path from the specified route values. + /// A return value indicates whether the operation succeeded. + /// + /// The associated with current request. + /// An object that contains route values. + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLink(HttpContext httpContext, object values, out string link) { return TryGetLink(httpContext, routeName: null, values, options: null, out link); } + /// + /// Generates a URL with an absolute path from the specified route values and link options. + /// + /// The associated with current request. + /// An object that contains route values. + /// The . + /// The generated URL. public string GetLink(HttpContext httpContext, object values, LinkOptions options) { return GetLink(httpContext, routeName: null, values, options); } + /// + /// Generates a URL with an absolute path from the specified route values and link options. + /// A return value indicates whether the operation succeeded. + /// + /// The associated with current request. + /// An object that contains route values. + /// The . + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLink(HttpContext httpContext, object values, LinkOptions options, out string link) { return TryGetLink(httpContext, routeName: null, values, options, out link); } + /// + /// Generates a URL with an absolute path from the specified route name and route values. + /// + /// The name of the route to generate the URL to. + /// An object that contains route values. + /// The generated URL. public string GetLink(string routeName, object values) { return GetLink(httpContext: null, routeName, values, options: null); } + /// + /// Generates a URL with an absolute path from the specified route name and route values. + /// A return value indicates whether the operation succeeded. + /// + /// The name of the route to generate the URL to. + /// An object that contains route values. + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLink(string routeName, object values, out string link) { return TryGetLink(httpContext: null, routeName, values, options: null, out link); } + /// + /// Generates a URL with an absolute path from the specified route name and route values. + /// + /// The name of the route to generate the URL to. + /// An object that contains route values. + /// The . + /// The generated URL. public string GetLink(string routeName, object values, LinkOptions options) { return GetLink(httpContext: null, routeName, values, options); } + /// + /// Generates a URL with an absolute path from the specified route name, route values and link options. + /// A return value indicates whether the operation succeeded. + /// + /// The name of the route to generate the URL to. + /// An object that contains route values. + /// The . + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLink(string routeName, object values, LinkOptions options, out string link) { return TryGetLink(httpContext: null, routeName, values, options, out link); } + /// + /// Generates a URL with an absolute path from the specified route name and route values. + /// + /// The name of the route to generate the URL to. + /// The associated with current request. + /// An object that contains route values. + /// The generated URL. public string GetLink(HttpContext httpContext, string routeName, object values) { return GetLink(httpContext, routeName, values, options: null); } + /// + /// Generates a URL with an absolute path from the specified route name and route values. + /// A return value indicates whether the operation succeeded. + /// + /// The associated with current request. + /// The name of the route to generate the URL to. + /// An object that contains route values. + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLink(HttpContext httpContext, string routeName, object values, out string link) { return TryGetLink(httpContext, routeName, values, options: null, out link); } + /// + /// Generates a URL with an absolute path from the specified route name, route values and link options. + /// + /// The name of the route to generate the URL to. + /// The associated with current request. + /// An object that contains route values. + /// The . + /// The generated URL. public string GetLink(HttpContext httpContext, string routeName, object values, LinkOptions options) { if (TryGetLink(httpContext, routeName, values, options, out var link)) @@ -88,6 +201,16 @@ namespace Microsoft.AspNetCore.Routing throw new InvalidOperationException("Could not find a matching endpoint to generate a link."); } + /// + /// Generates a URL with an absolute path from the specified route name, route values and link options. + /// A return value indicates whether the operation succeeded. + /// + /// The associated with current request. + /// The name of the route to generate the URL to. + /// An object that contains route values. + /// The . + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public abstract bool TryGetLink( HttpContext httpContext, string routeName, @@ -95,51 +218,119 @@ namespace Microsoft.AspNetCore.Routing LinkOptions options, out string link); + /// + /// Generates a URL with an absolute path from the specified lookup information and route values. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// An object that contains route values. + /// The generated URL. public string GetLinkByAddress(TAddress address, object values) { - return GetLinkByAddress(address, httpContext: null, values, options: null); + return GetLinkByAddress(httpContext: null, address, values, options: null); } + /// + /// Generates a URL with an absolute path from the specified lookup information and route values. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// A return value indicates whether the operation succeeded. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// An object that contains route values. + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLinkByAddress(TAddress address, object values, out string link) { return TryGetLinkByAddress(address, values, options: null, out link); } + /// + /// Generates a URL with an absolute path from the specified lookup information, route values and link options. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// An object that contains route values. + /// The . + /// The generated URL. public string GetLinkByAddress(TAddress address, object values, LinkOptions options) { - return GetLinkByAddress(address, httpContext: null, values, options); + return GetLinkByAddress(httpContext: null, address, values, options); } + /// + /// Generates a URL with an absolute path from the specified lookup information, route values and link options. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// A return value indicates whether the operation succeeded. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// An object that contains route values. + /// The . + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLinkByAddress( TAddress address, object values, LinkOptions options, out string link) { - return TryGetLinkByAddress(address, httpContext: null, values, options, out link); + return TryGetLinkByAddress(httpContext: null, address, values, options, out link); } - public string GetLinkByAddress(TAddress address, HttpContext httpContext, object values) + /// + /// Generates a URL with an absolute path from the specified lookup information, route values and link options. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// The associated with current request. + /// An object that contains route values. + /// The generated URL. + public string GetLinkByAddress(HttpContext httpContext, TAddress address, object values) { - return GetLinkByAddress(address, httpContext, values, options: null); + return GetLinkByAddress(httpContext, address, values, options: null); } + /// + /// Generates a URL with an absolute path from the specified lookup information and route values. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// A return value indicates whether the operation succeeded. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// The associated with current request. + /// An object that contains route values. + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public bool TryGetLinkByAddress( - TAddress address, HttpContext httpContext, + TAddress address, object values, out string link) { - return TryGetLinkByAddress(address, httpContext, values, options: null, out link); + return TryGetLinkByAddress(httpContext, address, values, options: null, out link); } + /// + /// Generates a URL with an absolute path from the specified lookup information, route values and link options. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// The associated with current request. + /// An object that contains route values. + /// The . + /// The generated URL. public string GetLinkByAddress( - TAddress address, HttpContext httpContext, + TAddress address, object values, LinkOptions options) { - if (TryGetLinkByAddress(address, httpContext, values, options, out var link)) + if (TryGetLinkByAddress(httpContext, address, values, options, out var link)) { return link; } @@ -147,11 +338,126 @@ namespace Microsoft.AspNetCore.Routing throw new InvalidOperationException("Could not find a matching endpoint to generate a link."); } + /// + /// Generates a URL with an absolute path from the specified lookup information, route values and link options. + /// This lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// A return value indicates whether the operation succeeded. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for generating a URL. + /// The associated with current request. + /// An object that contains route values. + /// The . + /// The generated URL. + /// true if a URL was generated successfully; otherwise, false. public abstract bool TryGetLinkByAddress( - TAddress address, HttpContext httpContext, + TAddress address, object values, LinkOptions options, out string link); + + /// + /// Gets a to generate a URL from the specified route values. + /// This template object holds information of the endpoint(s) that were found and which can later be used to + /// generate a URL using the api. + /// + /// + /// An object that contains route values. These values are used to lookup endpoint(s). + /// + /// + /// If an endpoint(s) was found successfully, then this returns a template object representing that, + /// null otherwise. + /// + public LinkGenerationTemplate GetTemplate(object values) + { + return GetTemplate(httpContext: null, routeName: null, values); + } + + /// + /// Gets a to generate a URL from the specified route name and route values. + /// This template object holds information of the endpoint(s) that were found and which can later be used to + /// generate a URL using the api. + /// + /// The name of the route to generate the URL to. + /// + /// An object that contains route values. These values are used to lookup for endpoint(s). + /// + /// + /// If an endpoint(s) was found successfully, then this returns a template object representing that, + /// null otherwise. + /// + public LinkGenerationTemplate GetTemplate(string routeName, object values) + { + return GetTemplate(httpContext: null, routeName, values); + } + + /// + /// Gets a to generate a URL from the specified route values. + /// This template object holds information of the endpoint(s) that were found and which can later be used to + /// generate a URL using the api. + /// + /// The associated with current request. + /// + /// An object that contains route values. These values are used to lookup for endpoint(s). + /// + /// + /// If an endpoint(s) was found successfully, then this returns a template object representing that, + /// null otherwise. + /// + public LinkGenerationTemplate GetTemplate(HttpContext httpContext, object values) + { + return GetTemplate(httpContext, routeName: null, values); + } + + /// + /// Gets a to generate a URL from the specified route name and route values. + /// This template object holds information of the endpoint(s) that were found and which can later be used to + /// generate a URL using the api. + /// + /// The associated with current request. + /// The name of the route to generate the URL to. + /// + /// An object that contains route values. These values are used to lookup for endpoint(s). + /// + /// + /// If an endpoint(s) was found successfully, then this returns a template object representing that, + /// null otherwise. + /// + public abstract LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values); + + /// + /// Gets a to generate a URL from the specified lookup information. + /// This template object holds information of the endpoint(s) that were found and which can later be used to + /// generate a URL using the api. + /// The lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for creating a template. + /// + /// If an endpoint(s) was found successfully, then this returns a template object representing that, + /// null otherwise. + /// + public LinkGenerationTemplate GetTemplateByAddress(TAddress address) + { + return GetTemplateByAddress(httpContext: null, address); + } + + /// + /// Gets a to generate a URL from the specified lookup information. + /// This template object holds information of the endpoint(s) that were found and which can later be used to + /// generate a URL using the api. + /// The lookup information is used to find endpoints using a registered 'IEndpointFinder<TAddress>'. + /// + /// The address type to look up endpoints. + /// The information used to look up endpoints for creating a template. + /// The associated with current request. + /// + /// If an endpoint(s) was found successfully, then this returns a template object representing that, + /// null otherwise. + /// + public abstract LinkGenerationTemplate GetTemplateByAddress( + HttpContext httpContext, + TAddress address); } } diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs new file mode 100644 index 0000000000..2b432db71f --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerationTemplate.cs @@ -0,0 +1,62 @@ +// 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; +using Microsoft.AspNetCore.Routing.Matching; + +namespace Microsoft.AspNetCore.Routing +{ + internal class DefaultLinkGenerationTemplate : LinkGenerationTemplate + { + public DefaultLinkGenerationTemplate( + DefaultLinkGenerator linkGenerator, + IEnumerable endpoints, + HttpContext httpContext, + RouteValueDictionary explicitValues, + RouteValueDictionary ambientValues) + { + LinkGenerator = linkGenerator; + Endpoints = endpoints; + HttpContext = httpContext; + EarlierExplicitValues = explicitValues; + AmbientValues = ambientValues; + } + + internal DefaultLinkGenerator LinkGenerator { get; } + + internal IEnumerable Endpoints { get; } + + internal HttpContext HttpContext { get; } + + internal RouteValueDictionary EarlierExplicitValues { get; } + + internal RouteValueDictionary AmbientValues { get; } + + public override string MakeUrl(object values, LinkOptions options) + { + var currentValues = new RouteValueDictionary(values); + var mergedValuesDictionary = new RouteValueDictionary(EarlierExplicitValues); + + foreach (var kvp in currentValues) + { + mergedValuesDictionary[kvp.Key] = kvp.Value; + } + + foreach (var endpoint in Endpoints) + { + var link = LinkGenerator.MakeLink( + HttpContext, + endpoint, + AmbientValues, + mergedValuesDictionary, + options); + if (link != null) + { + return link; + } + } + return null; + } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs index 13d2869d32..d56e7092ea 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs @@ -2,6 +2,7 @@ // 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.Http; @@ -54,21 +55,78 @@ namespace Microsoft.AspNetCore.Routing } public override bool TryGetLinkByAddress( - TAddress address, HttpContext httpContext, + TAddress address, object values, LinkOptions options, out string link) { return TryGetLinkByAddressInternal( - address, httpContext, + address, explicitValues: values, ambientValues: GetAmbientValues(httpContext), options, out link); } + public override LinkGenerationTemplate GetTemplate(HttpContext httpContext, string routeName, object values) + { + var ambientValues = GetAmbientValues(httpContext); + var explicitValues = new RouteValueDictionary(values); + + return GetTemplateInternal( + httpContext, + new RouteValuesAddress + { + RouteName = routeName, + ExplicitValues = explicitValues, + AmbientValues = ambientValues + }, + ambientValues, + explicitValues, + values); + } + + public override LinkGenerationTemplate GetTemplateByAddress( + HttpContext httpContext, + TAddress address) + { + return GetTemplateInternal(httpContext, address, values: null); + } + + internal string MakeLink( + HttpContext httpContext, + MatcherEndpoint endpoint, + RouteValueDictionary ambientValues, + RouteValueDictionary explicitValues, + LinkOptions options) + { + var templateBinder = new TemplateBinder( + UrlEncoder.Default, + _uriBuildingContextPool, + new RouteTemplate(endpoint.RoutePattern), + new RouteValueDictionary(endpoint.RoutePattern.Defaults)); + + var templateValuesResult = templateBinder.GetValues( + ambientValues: ambientValues, + explicitValues: explicitValues, + requiredKeys: endpoint.RequiredValues.Keys); + if (templateValuesResult == null) + { + // We're missing one of the required values for this route. + return null; + } + + if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues)) + { + return null; + } + + var url = templateBinder.BindValues(templateValuesResult.AcceptedValues); + return Normalize(url, options); + } + private bool TryGetLinkByRouteValues( HttpContext httpContext, string routeName, @@ -86,8 +144,8 @@ namespace Microsoft.AspNetCore.Routing }; return TryGetLinkByAddressInternal( - address, httpContext, + address, explicitValues: values, ambientValues: ambientValues, options, @@ -95,8 +153,8 @@ namespace Microsoft.AspNetCore.Routing } private bool TryGetLinkByAddressInternal( - TAddress address, HttpContext httpContext, + TAddress address, object explicitValues, RouteValueDictionary ambientValues, LinkOptions options, @@ -104,22 +162,21 @@ namespace Microsoft.AspNetCore.Routing { link = null; - var endpointFinder = _serviceProvider.GetRequiredService>(); - var endpoints = endpointFinder.FindEndpoints(address); + var endpoints = FindEndpoints(address); if (endpoints == null) { return false; } - var matcherEndpoints = endpoints.OfType(); - if (!matcherEndpoints.Any()) + foreach (var endpoint in endpoints) { - return false; - } + link = MakeLink( + httpContext, + endpoint, + ambientValues, + new RouteValueDictionary(explicitValues), + options); - foreach (var endpoint in matcherEndpoints) - { - link = GetLink(endpoint); if (link != null) { return true; @@ -127,33 +184,49 @@ namespace Microsoft.AspNetCore.Routing } return false; + } - string GetLink(MatcherEndpoint endpoint) + private LinkGenerationTemplate GetTemplateInternal( + HttpContext httpContext, + TAddress address, + object values) + { + var endpoints = FindEndpoints(address); + if (endpoints == null) { - var templateBinder = new TemplateBinder( - UrlEncoder.Default, - _uriBuildingContextPool, - new RouteTemplate(endpoint.RoutePattern), - new RouteValueDictionary(endpoint.RoutePattern.Defaults)); - - var templateValuesResult = templateBinder.GetValues( - ambientValues: ambientValues, - explicitValues: new RouteValueDictionary(explicitValues), - requiredKeys: endpoint.RequiredValues.Keys); - if (templateValuesResult == null) - { - // We're missing one of the required values for this route. - return null; - } - - if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues)) - { - return null; - } - - var url = templateBinder.BindValues(templateValuesResult.AcceptedValues); - return Normalize(url, options); + return null; } + + var ambientValues = GetAmbientValues(httpContext); + var explicitValues = new RouteValueDictionary(values); + + return new DefaultLinkGenerationTemplate( + this, + endpoints, + httpContext, + explicitValues, + ambientValues); + } + + private LinkGenerationTemplate GetTemplateInternal( + HttpContext httpContext, + TAddress address, + RouteValueDictionary ambientValues, + RouteValueDictionary explicitValues, + object values) + { + var endpoints = FindEndpoints(address); + if (endpoints == null) + { + return null; + } + + return new DefaultLinkGenerationTemplate( + this, + endpoints, + httpContext, + explicitValues, + ambientValues); } private bool MatchesConstraints( @@ -234,7 +307,25 @@ namespace Microsoft.AspNetCore.Routing return feature.Values; } } - return null; + return new RouteValueDictionary(); + } + + private IEnumerable FindEndpoints(TAddress address) + { + var finder = _serviceProvider.GetRequiredService>(); + var endpoints = finder.FindEndpoints(address); + if (endpoints == null) + { + return null; + } + + var matcherEndpoints = endpoints.OfType(); + if (!matcherEndpoints.Any()) + { + return null; + } + + return matcherEndpoints; } } } diff --git a/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs b/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs index ba450f1c7b..618220a6e9 100644 --- a/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs +++ b/src/Microsoft.AspNetCore.Routing/IEndpointFinderOfT.cs @@ -5,8 +5,17 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Routing { + /// + /// Defines a contract to find endpoints based on the supplied lookup information. + /// + /// The address type to look up endpoints. public interface IEndpointFinder { + /// + /// Finds endpoints based on the supplied lookup information. + /// + /// The information used to look up endpoints. + /// A collection of . IEnumerable FindEndpoints(TAddress address); } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs index 1063db6819..c226e3edd6 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -1217,7 +1217,7 @@ namespace Microsoft.AspNetCore.Routing } [Fact] - public void TryGetLink_WithCustomAddress_CanGenerateLink() + public void TryGetLinkByAddress_WithCustomAddress_CanGenerateLink() { // Arrange var services = GetBasicServices(); @@ -1237,8 +1237,8 @@ namespace Microsoft.AspNetCore.Routing // Act var canGenerateLink = linkGenerator.TryGetLinkByAddress( - address: new NameMetadata("CustomerDetails"), httpContext, + address: new NameMetadata("CustomerDetails"), values: new { id = 10 }, out var link); @@ -1248,7 +1248,7 @@ namespace Microsoft.AspNetCore.Routing } [Fact] - public void TryGetLink_WithCustomAddress_CanGenerateLink_RespectsLinkOptions_SuppliedAtCallSite() + public void TryGetLinkByAddress_WithCustomAddress_CanGenerateLink_RespectsLinkOptions_SuppliedAtCallSite() { // Arrange var services = GetBasicServices(); @@ -1268,8 +1268,8 @@ namespace Microsoft.AspNetCore.Routing // Act var canGenerateLink = linkGenerator.TryGetLinkByAddress( - address: new NameMetadata("CustomerDetails"), httpContext, + address: new NameMetadata("CustomerDetails"), values: new { id = 10 }, new LinkOptions { @@ -1282,6 +1282,177 @@ namespace Microsoft.AspNetCore.Routing Assert.Equal("/customers/details/10", link); } + [Fact] + public void GetTemplate_ByRouteValues_ReturnsTemplate() + { + // Arrange + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Product/Edit/{id}", + requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }); + var linkGenerator = CreateLinkGenerator(endpoint1); + var values = new RouteValueDictionary(new { controller = "Product", action = "Edit" }); + + // Act + var template = linkGenerator.GetTemplate(values); + + // Assert + var defaultTemplate = Assert.IsType(template); + Assert.Same(linkGenerator, defaultTemplate.LinkGenerator); + Assert.Equal(new[] { endpoint1 }, defaultTemplate.Endpoints); + Assert.Equal(values, defaultTemplate.EarlierExplicitValues); + Assert.Null(defaultTemplate.HttpContext); + Assert.Empty(defaultTemplate.AmbientValues); + } + + [Fact] + public void GetTemplate_ByRouteName_ReturnsTemplate() + { + // Arrange + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Product/Edit/{id}", + requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + metadata: new RouteNameMetadata("EditProduct")); + var linkGenerator = CreateLinkGenerator(endpoint1); + + // Act + var template = linkGenerator.GetTemplate("EditProduct", values: new { }); + + // Assert + var defaultTemplate = Assert.IsType(template); + Assert.Same(linkGenerator, defaultTemplate.LinkGenerator); + Assert.Equal(new[] { endpoint1 }, defaultTemplate.Endpoints); + Assert.Empty(defaultTemplate.EarlierExplicitValues); + Assert.Null(defaultTemplate.HttpContext); + Assert.Empty(defaultTemplate.AmbientValues); + } + + [Fact] + public void GetTemplate_ByRouteName_ReturnsTemplate_WithMultipleEndpoints() + { + // Arrange + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Product/Edit/{id}", + requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + metadata: new RouteNameMetadata("default")); + var endpoint2 = EndpointFactory.CreateMatcherEndpoint( + "Product/Details/{id}", + requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + metadata: new RouteNameMetadata("default")); + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + // Act + var template = linkGenerator.GetTemplate("default", values: new { }); + + // Assert + var defaultTemplate = Assert.IsType(template); + Assert.Same(linkGenerator, defaultTemplate.LinkGenerator); + Assert.Equal(new[] { endpoint1, endpoint2 }, defaultTemplate.Endpoints); + Assert.Empty(defaultTemplate.EarlierExplicitValues); + Assert.Null(defaultTemplate.HttpContext); + Assert.Empty(defaultTemplate.AmbientValues); + } + + [Fact] + public void GetTemplateByAddress_ByCustomAddress_ReturnsTemplate() + { + // Arrange + var services = GetBasicServices(); + services.TryAddEnumerable( + ServiceDescriptor.Singleton, EndpointFinderByName>()); + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Product/Edit/{id}", + requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }); + var endpoint2 = EndpointFactory.CreateMatcherEndpoint( + "Customers/Details/{id}", + requiredValues: new { controller = "Customers", action = "Details" }, + defaults: new { controller = "Customers", action = "Details" }, + metadata: new NameMetadata("CustomerDetails")); + var linkGenerator = CreateLinkGenerator(new[] { endpoint1, endpoint2 }, new RouteOptions(), services); + + // Act + var template = linkGenerator.GetTemplateByAddress(new NameMetadata("CustomerDetails")); + + // Assert + var defaultTemplate = Assert.IsType(template); + Assert.Same(linkGenerator, defaultTemplate.LinkGenerator); + Assert.Equal(new[] { endpoint2 }, defaultTemplate.Endpoints); + Assert.Empty(defaultTemplate.EarlierExplicitValues); + Assert.Null(defaultTemplate.HttpContext); + Assert.Empty(defaultTemplate.AmbientValues); + } + + [Fact] + public void MakeUrl_Honors_LinkOptions() + { + // Arrange + var services = GetBasicServices(); + services.TryAddEnumerable( + ServiceDescriptor.Singleton, EndpointFinderByName>()); + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Product/Edit/{id}", + requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }); + var endpoint2 = EndpointFactory.CreateMatcherEndpoint( + "Customers/Details/{id}", + requiredValues: new { controller = "Customers", action = "Details" }, + defaults: new { controller = "Customers", action = "Details" }, + metadata: new NameMetadata("CustomerDetails")); + var linkGenerator = CreateLinkGenerator(new[] { endpoint1, endpoint2 }, new RouteOptions(), services); + + // Act1 + var template = linkGenerator.GetTemplateByAddress(new NameMetadata("CustomerDetails")); + + // Assert1 + Assert.NotNull(template); + + // Act2 + var link = template.MakeUrl(new { id = 10 }, new LinkOptions { LowercaseUrls = true }); + + // Assert2 + Assert.Equal("/customers/details/10", link); + + // Act3 + link = template.MakeUrl(new { id = 25 }); + + // Assert3 + Assert.Equal("/Customers/Details/25", link); + } + + [Fact] + public void MakeUrl_GeneratesLink_WithExtraRouteValues() + { + // Arrange + var endpoint1 = EndpointFactory.CreateMatcherEndpoint( + "Product/Edit/{id}", + requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, + defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }); + var linkGenerator = CreateLinkGenerator(endpoint1); + + // Act1 + var template = linkGenerator.GetTemplate( + values: new { controller = "Product", action = "Edit", foo = "bar" }); + + // Assert1 + Assert.NotNull(template); + + // Act2 + var link = template.MakeUrl(new { id = 10 }); + + // Assert2 + Assert.Equal("/Product/Edit/10?foo=bar", link); + + // Act3 + link = template.MakeUrl(new { id = 25, foo = "boo" }); + + // Assert3 + Assert.Equal("/Product/Edit/25?foo=boo", link); + } + private LinkGenerator CreateLinkGenerator(params Endpoint[] endpoints) { return CreateLinkGenerator(endpoints, routeOptions: null); @@ -1365,5 +1536,14 @@ namespace Microsoft.AspNetCore.Routing } public string Name { get; } } + + private class RouteNameMetadata : IRouteNameMetadata + { + public RouteNameMetadata(string name) + { + Name = name; + } + public string Name { get; } + } } } From 091cb94094a776b53ca6323c4251a38afc5ceef3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 2 Aug 2018 01:17:09 -0700 Subject: [PATCH 3/9] Change metadata namespace Change EndpointOptions visibility to internal Add IRequiredValuesMetadata --- .../Benchmarks/StartupUsingEndpointRouting.cs | 11 ++--- .../Matching/MatcherBenchmarkBase.cs | 2 - .../UseEndpointRoutingStartup.cs | 14 ++---- .../CompositeEndpointDataSource.cs | 14 +++--- .../EndpointOptions.cs | 3 +- .../{Metadata => }/HttpMethodMetadata.cs | 2 +- .../{Metadata => }/IHttpMethodMetadata.cs | 2 +- .../IRouteValuesAddressMetadata.cs | 13 +++++ .../ISuppressLinkGenerationMetadata.cs | 2 +- .../Matching/HttpMethodMatcherPolicy.cs | 2 - .../Matching/MatcherEndpoint.cs | 5 -- .../RouteValuesAddressMetadata.cs | 48 +++++++++++++++++++ .../RouteValuesBasedEndpointFinder.cs | 6 +-- ...a.cs => SuppressLinkGenerationMetadata.cs} | 5 +- .../CompositeEndpointDataSourceTest.cs | 2 - .../EndpointFactory.cs | 11 +++-- .../Matching/CandidateSetTest.cs | 1 - .../DataSourceDependentMatcherTest.cs | 1 - .../Matching/DefaultEndpointSelectorTest.cs | 1 - .../Matching/DfaMatcherBuilderTest.cs | 1 - .../Matching/DfaMatcherTest.cs | 1 - .../HttpMethodMatcherPolicyIntegrationTest.cs | 2 - .../Matching/HttpMethodMatcherPolicyTest.cs | 2 - .../Matching/MatcherConformanceTest.cs | 1 - .../Matching/MatcherEndpointComparerTest.cs | 1 - .../RouteValueBasedEndpointFinderTest.cs | 20 ++------ .../RouteValuesAddressMetadataTests.cs | 33 +++++++++++++ 27 files changed, 133 insertions(+), 73 deletions(-) rename src/Microsoft.AspNetCore.Routing/{Metadata => }/HttpMethodMetadata.cs (96%) rename src/Microsoft.AspNetCore.Routing/{Metadata => }/IHttpMethodMetadata.cs (88%) create mode 100644 src/Microsoft.AspNetCore.Routing/IRouteValuesAddressMetadata.cs create mode 100644 src/Microsoft.AspNetCore.Routing/RouteValuesAddressMetadata.cs rename src/Microsoft.AspNetCore.Routing/{IRouteNameMetadata.cs => SuppressLinkGenerationMetadata.cs} (71%) create mode 100644 test/Microsoft.AspNetCore.Routing.Tests/RouteValuesAddressMetadataTests.cs diff --git a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs index c04f28decc..2c5fa5a7d9 100644 --- a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs +++ b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace Benchmarks { @@ -18,9 +19,7 @@ namespace Benchmarks { services.AddRouting(); - services.Configure(options => - { - options.DataSources.Add(new DefaultEndpointDataSource(new[] + var endpointDataSource = new DefaultEndpointDataSource(new[] { new MatcherEndpoint( invoker: (next) => (httpContext) => @@ -33,12 +32,12 @@ namespace Benchmarks return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength); }, routePattern: RoutePatternFactory.Parse("/plaintext"), - requiredValues: new RouteValueDictionary(), order: 0, metadata: EndpointMetadataCollection.Empty, displayName: "Plaintext"), - })); - }); + }); + + services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource)); } public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app) diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBenchmarkBase.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBenchmarkBase.cs index 76802c758d..4b030f84dc 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBenchmarkBase.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBenchmarkBase.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; @@ -44,7 +43,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template), - new RouteValueDictionary(), 0, new EndpointMetadataCollection(metadata), template); diff --git a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs index d67d5fbe2a..77b41e87a8 100644 --- a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs +++ b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace RoutingSample.Web { @@ -26,9 +27,7 @@ namespace RoutingSample.Web options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor)); }); - services.Configure(options => - { - options.DataSources.Add(new DefaultEndpointDataSource(new[] + var endpointDataSource = new DefaultEndpointDataSource(new[] { new MatcherEndpoint((next) => (httpContext) => { @@ -40,7 +39,6 @@ namespace RoutingSample.Web return response.Body.WriteAsync(_homePayload, 0, payloadLength); }, RoutePatternFactory.Parse("/"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "Home"), @@ -54,7 +52,6 @@ namespace RoutingSample.Web return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength); }, RoutePatternFactory.Parse("/plaintext"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "Plaintext"), @@ -66,7 +63,6 @@ namespace RoutingSample.Web return response.WriteAsync("WithConstraints"); }, RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "withconstraints"), @@ -78,12 +74,12 @@ namespace RoutingSample.Web return response.WriteAsync("withoptionalconstraints"); }, RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "withoptionalconstraints"), - })); - }); + }); + + services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource)); } public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app) diff --git a/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs index e9157ea578..3341b21796 100644 --- a/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Text; using System.Threading; using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Routing @@ -127,15 +126,18 @@ namespace Microsoft.AspNetCore.Routing var template = matcherEndpoint.RoutePattern.RawText; template = string.IsNullOrEmpty(template) ? "\"\"" : template; sb.Append(template); - sb.Append(", Required Values: new { "); - sb.Append(string.Join(", ", FormatValues(matcherEndpoint.RequiredValues))); - sb.Append(" }"); sb.Append(", Defaults: new { "); sb.Append(string.Join(", ", FormatValues(matcherEndpoint.RoutePattern.Defaults))); sb.Append(" }"); - var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); + var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); sb.Append(", Route Name: "); - sb.Append(routeNameMetadata?.Name); + sb.Append(routeValuesAddressMetadata?.Name); + if (routeValuesAddressMetadata?.RequiredValues != null) + { + sb.Append(", Required Values: new { "); + sb.Append(string.Join(", ", FormatValues(routeValuesAddressMetadata.RequiredValues))); + sb.Append(" }"); + } sb.Append(", Order: "); sb.Append(matcherEndpoint.Order); diff --git a/src/Microsoft.AspNetCore.Routing/EndpointOptions.cs b/src/Microsoft.AspNetCore.Routing/EndpointOptions.cs index bd03d3c7a2..8f364d6eaa 100644 --- a/src/Microsoft.AspNetCore.Routing/EndpointOptions.cs +++ b/src/Microsoft.AspNetCore.Routing/EndpointOptions.cs @@ -5,7 +5,8 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Routing { - public class EndpointOptions + // Internal for 2.2. Public API for configuring endpoints will be added in 3.0 + internal class EndpointOptions { public IList DataSources { get; } = new List(); } diff --git a/src/Microsoft.AspNetCore.Routing/Metadata/HttpMethodMetadata.cs b/src/Microsoft.AspNetCore.Routing/HttpMethodMetadata.cs similarity index 96% rename from src/Microsoft.AspNetCore.Routing/Metadata/HttpMethodMetadata.cs rename to src/Microsoft.AspNetCore.Routing/HttpMethodMetadata.cs index af342a2f36..7d76b56cd0 100644 --- a/src/Microsoft.AspNetCore.Routing/Metadata/HttpMethodMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/HttpMethodMetadata.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -namespace Microsoft.AspNetCore.Routing.Metadata +namespace Microsoft.AspNetCore.Routing { [DebuggerDisplay("{DebuggerToString(),nq}")] public sealed class HttpMethodMetadata : IHttpMethodMetadata diff --git a/src/Microsoft.AspNetCore.Routing/Metadata/IHttpMethodMetadata.cs b/src/Microsoft.AspNetCore.Routing/IHttpMethodMetadata.cs similarity index 88% rename from src/Microsoft.AspNetCore.Routing/Metadata/IHttpMethodMetadata.cs rename to src/Microsoft.AspNetCore.Routing/IHttpMethodMetadata.cs index 9d6447d1e0..83b99d8f72 100644 --- a/src/Microsoft.AspNetCore.Routing/Metadata/IHttpMethodMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/IHttpMethodMetadata.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.AspNetCore.Routing.Metadata +namespace Microsoft.AspNetCore.Routing { public interface IHttpMethodMetadata { diff --git a/src/Microsoft.AspNetCore.Routing/IRouteValuesAddressMetadata.cs b/src/Microsoft.AspNetCore.Routing/IRouteValuesAddressMetadata.cs new file mode 100644 index 0000000000..0d60e5db92 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/IRouteValuesAddressMetadata.cs @@ -0,0 +1,13 @@ +// 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 IRouteValuesAddressMetadata + { + string Name { get; } + IReadOnlyDictionary RequiredValues { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs b/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs index 2e6a7da5d8..23c5e168d8 100644 --- a/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs @@ -6,4 +6,4 @@ namespace Microsoft.AspNetCore.Routing public interface ISuppressLinkGenerationMetadata { } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs b/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs index 315c3d8409..9968395b29 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Primitives; @@ -232,7 +231,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return Task.CompletedTask; }, RoutePatternFactory.Parse("/"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, Http405EndpointDisplayName); diff --git a/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs b/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs index 9dc34cac6a..194fb0dc3d 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs @@ -19,7 +19,6 @@ namespace Microsoft.AspNetCore.Routing.Matching public MatcherEndpoint( Func invoker, RoutePattern routePattern, - RouteValueDictionary requiredValues, int order, EndpointMetadataCollection metadata, string displayName) @@ -37,7 +36,6 @@ namespace Microsoft.AspNetCore.Routing.Matching Invoker = invoker; RoutePattern = routePattern; - RequiredValues = requiredValues; Order = order; } @@ -45,9 +43,6 @@ namespace Microsoft.AspNetCore.Routing.Matching public int Order { get; } - // Values required by an endpoint for it to be successfully matched on link generation - public IReadOnlyDictionary RequiredValues { get; } - public RoutePattern RoutePattern { get; } } } diff --git a/src/Microsoft.AspNetCore.Routing/RouteValuesAddressMetadata.cs b/src/Microsoft.AspNetCore.Routing/RouteValuesAddressMetadata.cs new file mode 100644 index 0000000000..6d110a70d6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/RouteValuesAddressMetadata.cs @@ -0,0 +1,48 @@ +// 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.Diagnostics; +using System.Linq; + +namespace Microsoft.AspNetCore.Routing +{ + [DebuggerDisplay("{DebuggerToString(),nq}")] + public sealed class RouteValuesAddressMetadata : IRouteValuesAddressMetadata + { + public RouteValuesAddressMetadata(string name, IReadOnlyDictionary requiredValues) + { + Name = name; + RequiredValues = requiredValues; + } + + public string Name { get; } + + public IReadOnlyDictionary RequiredValues { get; } + + internal string DebuggerToString() + { + return $"Name: {Name} - Required values: {string.Join(", ", FormatValues(RequiredValues))}"; + + IEnumerable FormatValues(IEnumerable> values) + { + if (values == null) + { + return Array.Empty(); + } + + return values.Select( + kvp => + { + var value = "null"; + if (kvp.Value != null) + { + value = "\"" + kvp.Value.ToString() + "\""; + } + return kvp.Key + " = " + value; + }); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs b/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs index 19a24fe439..2821c73aaf 100644 --- a/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs +++ b/src/Microsoft.AspNetCore.Routing/RouteValuesBasedEndpointFinder.cs @@ -137,16 +137,16 @@ namespace Microsoft.AspNetCore.Routing private OutboundRouteEntry CreateOutboundRouteEntry(MatcherEndpoint endpoint) { - var routeNameMetadata = endpoint.Metadata.GetMetadata(); + var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata(); var entry = new OutboundRouteEntry() { Handler = NullRouter.Instance, Order = endpoint.Order, Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern), - RequiredLinkValues = new RouteValueDictionary(endpoint.RequiredValues), + RequiredLinkValues = new RouteValueDictionary(routeValuesAddressMetadata?.RequiredValues), RouteTemplate = new RouteTemplate(endpoint.RoutePattern), Data = endpoint, - RouteName = routeNameMetadata?.Name, + RouteName = routeValuesAddressMetadata?.Name, }; entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults); return entry; diff --git a/src/Microsoft.AspNetCore.Routing/IRouteNameMetadata.cs b/src/Microsoft.AspNetCore.Routing/SuppressLinkGenerationMetadata.cs similarity index 71% rename from src/Microsoft.AspNetCore.Routing/IRouteNameMetadata.cs rename to src/Microsoft.AspNetCore.Routing/SuppressLinkGenerationMetadata.cs index efc1136904..599267c570 100644 --- a/src/Microsoft.AspNetCore.Routing/IRouteNameMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/SuppressLinkGenerationMetadata.cs @@ -3,8 +3,7 @@ namespace Microsoft.AspNetCore.Routing { - public interface IRouteNameMetadata + public sealed class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { - string Name { get; } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs index dad43417ab..d04f0d96de 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/CompositeEndpointDataSourceTest.cs @@ -149,14 +149,12 @@ namespace Microsoft.AspNetCore.Routing private MatcherEndpoint CreateEndpoint( string template, object defaults = null, - object requiredValues = null, int order = 0, string routeName = null) { return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(requiredValues), order, EndpointMetadataCollection.Empty, null); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs b/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs index 6711ff64d6..6ff5f8b909 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs @@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; +using System; +using System.Collections.Generic; namespace Microsoft.AspNetCore.Routing { @@ -17,18 +19,17 @@ namespace Microsoft.AspNetCore.Routing string displayName = null, params object[] metadata) { - var metadataCollection = EndpointMetadataCollection.Empty; - if (metadata != null) + var d = new List(metadata ?? Array.Empty()); + if (requiredValues != null) { - metadataCollection = new EndpointMetadataCollection(metadata); + d.Add(new RouteValuesAddressMetadata(null, new RouteValueDictionary(requiredValues))); } return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, defaults, constraints), - new RouteValueDictionary(requiredValues), order, - metadataCollection, + new EndpointMetadataCollection(d), displayName); } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/CandidateSetTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/CandidateSetTest.cs index 54db9ca02d..ac78dcaf7b 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/CandidateSetTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/CandidateSetTest.cs @@ -85,7 +85,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "test"); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DataSourceDependentMatcherTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DataSourceDependentMatcherTest.cs index e88c707217..07c1db9537 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DataSourceDependentMatcherTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DataSourceDependentMatcherTest.cs @@ -37,7 +37,6 @@ namespace Microsoft.AspNetCore.Routing.Matching var endpoint = new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse("a/b/c"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "test"); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultEndpointSelectorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultEndpointSelectorTest.cs index d8586f23d6..5ec095bc4e 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultEndpointSelectorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultEndpointSelectorTest.cs @@ -216,7 +216,6 @@ test: /test3", ex.Message); return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, $"test: {template}"); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherBuilderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherBuilderTest.cs index e712210e44..a8735c02fa 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherBuilderTest.cs @@ -860,7 +860,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints)), - new RouteValueDictionary(), 0, new EndpointMetadataCollection(metadata), "test"); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherTest.cs index 7e80607008..b77650deda 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DfaMatcherTest.cs @@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(), order, metadata ?? EndpointMetadataCollection.Empty, template); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyIntegrationTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyIntegrationTest.cs index d3ae78485b..5d5c0eccd8 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyIntegrationTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -343,7 +342,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, defaults, constraints), - new RouteValueDictionary(), order, new EndpointMetadataCollection(metadata), displayName); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyTest.cs index f302bebb10..e8c7121597 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/HttpMethodMatcherPolicyTest.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; using Xunit; using static Microsoft.AspNetCore.Routing.Matching.HttpMethodMatcherPolicy; @@ -288,7 +287,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template), - new RouteValueDictionary(), 0, new EndpointMetadataCollection(metadata), $"test: {template}"); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs index 9e4986c543..9dbc881548 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs @@ -43,7 +43,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, defaults, constraints), - new RouteValueDictionary(), order ?? 0, EndpointMetadataCollection.Empty, "endpoint: " + template); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointComparerTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointComparerTest.cs index 87bbc0c877..96539d8304 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointComparerTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointComparerTest.cs @@ -222,7 +222,6 @@ namespace Microsoft.AspNetCore.Routing.Matching return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template), - new RouteValueDictionary(), order, new EndpointMetadataCollection(metadata), "test: " + template); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs index c18b0f5f2f..af2cd3faf9 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs @@ -263,37 +263,29 @@ namespace Microsoft.AspNetCore.Routing { if (metadataCollection == null) { - metadataCollection = EndpointMetadataCollection.Empty; - if (!string.IsNullOrEmpty(routeName)) + var metadata = new List(); + if (!string.IsNullOrEmpty(routeName) || requiredValues != null) { - metadataCollection = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) }); + metadata.Add(new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues))); } + metadataCollection = new EndpointMetadataCollection(metadata); } return new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(requiredValues), order, metadataCollection, null); } - private class RouteNameMetadata : IRouteNameMetadata - { - public RouteNameMetadata(string name) - { - Name = name; - } - public string Name { get; } - } - private class NameMetadata : INameMetadata { public NameMetadata(string name) { Name = name; } + public string Name { get; } } @@ -318,7 +310,5 @@ namespace Microsoft.AspNetCore.Routing return matches; } } - - private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/RouteValuesAddressMetadataTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/RouteValuesAddressMetadataTests.cs new file mode 100644 index 0000000000..f05e763484 --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/RouteValuesAddressMetadataTests.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Microsoft.AspNetCore.Routing +{ + public class RouteValuesAddressMetadataTests + { + [Fact] + public void DebuggerToString_NoNameAndRequiredValues_ReturnsString() + { + var metadata = new RouteValuesAddressMetadata(null, null); + + Assert.Equal("Name: - Required values: ", metadata.DebuggerToString()); + } + + [Fact] + public void DebuggerToString_HasNameAndRequiredValues_ReturnsString() + { + var metadata = new RouteValuesAddressMetadata("Name!", new Dictionary + { + ["requiredValue1"] = "One", + ["requiredValue2"] = 2, + }); + + Assert.Equal("Name: Name! - Required values: requiredValue1 = \"One\", requiredValue2 = \"2\"", metadata.DebuggerToString()); + } + } +} From 09ce9c3041ba05a42a925f34548c0f6c0487c2e8 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Thu, 2 Aug 2018 16:33:07 -0700 Subject: [PATCH 4/9] Api clean up --- .../EndsWithStringMatchProcessor.cs | 29 ++++++++++-- .../RoutingServiceCollectionExtensions.cs | 1 - .../INameMetadata.cs | 10 ---- .../Matching/MatchProcessorBase.cs | 42 ----------------- .../NameBasedEndpointFinder.cs | 46 ------------------- .../DefaultMatchProcessorFactoryTest.cs | 36 +++++++++++++-- .../RouteValueBasedEndpointFinderTest.cs | 34 +------------- 7 files changed, 57 insertions(+), 141 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Routing/INameMetadata.cs delete mode 100644 src/Microsoft.AspNetCore.Routing/Matching/MatchProcessorBase.cs delete mode 100644 src/Microsoft.AspNetCore.Routing/NameBasedEndpointFinder.cs diff --git a/samples/RoutingSample.Web/EndsWithStringMatchProcessor.cs b/samples/RoutingSample.Web/EndsWithStringMatchProcessor.cs index ea50812015..f0201f79d9 100644 --- a/samples/RoutingSample.Web/EndsWithStringMatchProcessor.cs +++ b/samples/RoutingSample.Web/EndsWithStringMatchProcessor.cs @@ -3,12 +3,14 @@ using System; using System.Globalization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.Extensions.Logging; namespace RoutingSample.Web { - internal class EndsWithStringMatchProcessor : MatchProcessorBase + internal class EndsWithStringMatchProcessor : MatchProcessor { private readonly ILogger _logger; @@ -17,15 +19,34 @@ namespace RoutingSample.Web _logger = logger; } - public override bool Process(object value) + public string ParameterName { get; private set; } + + public string ConstraintArgument { get; private set; } + + public override void Initialize(string parameterName, string constraintArgument) { - if (value == null) + ParameterName = parameterName; + ConstraintArgument = constraintArgument; + } + + public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary values) + { + return Process(values); + } + + public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values) + { + return Process(values); + } + + private bool Process(RouteValueDictionary values) + { + if (!values.TryGetValue(ParameterName, out var value) || value == null) { return false; } var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); - var endsWith = valueString.EndsWith(ConstraintArgument, StringComparison.OrdinalIgnoreCase); if (!endsWith) diff --git a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs index 42433f64c0..19f3d0e77a 100644 --- a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs @@ -67,7 +67,6 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddTransient(); // Link generation related services - services.TryAddSingleton, NameBasedEndpointFinder>(); services.TryAddSingleton, RouteValuesBasedEndpointFinder>(); services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Routing/INameMetadata.cs b/src/Microsoft.AspNetCore.Routing/INameMetadata.cs deleted file mode 100644 index c38ae7f0e5..0000000000 --- a/src/Microsoft.AspNetCore.Routing/INameMetadata.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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 INameMetadata - { - string Name { get; } - } -} diff --git a/src/Microsoft.AspNetCore.Routing/Matching/MatchProcessorBase.cs b/src/Microsoft.AspNetCore.Routing/Matching/MatchProcessorBase.cs deleted file mode 100644 index 9ac2196671..0000000000 --- a/src/Microsoft.AspNetCore.Routing/Matching/MatchProcessorBase.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Routing.Matching -{ - public abstract class MatchProcessorBase : MatchProcessor - { - public string ParameterName { get; private set; } - - public string ConstraintArgument { get; private set; } - - public override void Initialize(string parameterName, string constraintArgument) - { - ParameterName = parameterName; - ConstraintArgument = constraintArgument; - } - - public abstract bool Process(object value); - - public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary values) - { - return Process(values); - } - - public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values) - { - return Process(values); - } - - private bool Process(RouteValueDictionary values) - { - if (!values.TryGetValue(ParameterName, out var value) || value == null) - { - return false; - } - - return Process(value); - } - } -} diff --git a/src/Microsoft.AspNetCore.Routing/NameBasedEndpointFinder.cs b/src/Microsoft.AspNetCore.Routing/NameBasedEndpointFinder.cs deleted file mode 100644 index b10c2b7934..0000000000 --- a/src/Microsoft.AspNetCore.Routing/NameBasedEndpointFinder.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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.Routing.Matching; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Routing -{ - internal class NameBasedEndpointFinder : IEndpointFinder - { - private readonly CompositeEndpointDataSource _endpointDatasource; - private readonly ILogger _logger; - - public NameBasedEndpointFinder( - CompositeEndpointDataSource endpointDataSource, - ILogger logger) - { - _endpointDatasource = endpointDataSource; - _logger = logger; - } - - public IEnumerable FindEndpoints(string endpointName) - { - if (string.IsNullOrEmpty(endpointName)) - { - return Array.Empty(); - } - - var endpoints = _endpointDatasource.Endpoints.OfType(); - - foreach (var endpoint in endpoints) - { - var nameMetadata = endpoint.Metadata.GetMetadata(); - if (nameMetadata != null && - string.Equals(endpointName, nameMetadata.Name, StringComparison.OrdinalIgnoreCase)) - { - return new[] { endpoint }; - } - } - return Array.Empty(); - } - } -} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultMatchProcessorFactoryTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultMatchProcessorFactoryTest.cs index 1be24950b1..acfd78d29f 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultMatchProcessorFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultMatchProcessorFactoryTest.cs @@ -61,8 +61,8 @@ namespace Microsoft.AspNetCore.Routing.Matching var factory = GetMatchProcessorFactory(); var parameter = RoutePatternFactory.ParameterPart( - "id", - @default: null, + "id", + @default: null, parameterKind: RoutePatternParameterKind.Standard, constraints: new[] { RoutePatternFactory.Constraint("int"), }); @@ -282,12 +282,38 @@ namespace Microsoft.AspNetCore.Routing.Matching } } - private class EndsWithStringMatchProcessor : MatchProcessorBase + private class EndsWithStringMatchProcessor : MatchProcessor { - public override bool Process(object value) + public string ParameterName { get; private set; } + + public string ConstraintArgument { get; private set; } + + public override void Initialize(string parameterName, string constraintArgument) { + ParameterName = parameterName; + ConstraintArgument = constraintArgument; + } + + public override bool ProcessInbound(HttpContext httpContext, RouteValueDictionary values) + { + return Process(values); + } + + public override bool ProcessOutbound(HttpContext httpContext, RouteValueDictionary values) + { + return Process(values); + } + + private bool Process(RouteValueDictionary values) + { + if (!values.TryGetValue(ParameterName, out var value) || value == null) + { + return false; + } + var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); - return valueString.EndsWith(ConstraintArgument); + var endsWith = valueString.EndsWith(ConstraintArgument, StringComparison.OrdinalIgnoreCase); + return endsWith; } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs index af2cd3faf9..25ce04bf82 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/RouteValueBasedEndpointFinderTest.cs @@ -1,6 +1,7 @@ // 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.Threading.Tasks; @@ -79,29 +80,6 @@ namespace Microsoft.AspNetCore.Routing Assert.Same(endpoint3, Assert.IsType(namedMatches[1].Match.Entry.Data)); } - [Fact] - public void GetOutboundMatches_DoesNotGetNamedMatchesFor_EndpointsHaving_INameMetadata() - { - // Arrange - var endpoint1 = CreateEndpoint("/a"); - var endpoint2 = CreateEndpoint("/a", routeName: "named"); - var endpoint3 = CreateEndpoint( - "/b", - metadataCollection: new EndpointMetadataCollection(new[] { new NameMetadata("named") })); - - // Act - var finder = CreateEndpointFinder(endpoint1, endpoint2); - - // Assert - Assert.NotNull(finder.AllMatches); - Assert.Equal(2, finder.AllMatches.Count()); - Assert.NotNull(finder.NamedMatches); - Assert.True(finder.NamedMatches.TryGetValue("named", out var namedMatches)); - var namedMatch = Assert.Single(namedMatches); - var actual = Assert.IsType(namedMatch.Match.Entry.Data); - Assert.Same(endpoint2, actual); - } - [Fact] public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches() { @@ -279,16 +257,6 @@ namespace Microsoft.AspNetCore.Routing null); } - private class NameMetadata : INameMetadata - { - public NameMetadata(string name) - { - Name = name; - } - - public string Name { get; } - } - private class CustomRouteValuesBasedEndpointFinder : RouteValuesBasedEndpointFinder { public CustomRouteValuesBasedEndpointFinder( From 4d706f045f81782a62c29117893d3c7f03e8a6a4 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 3 Aug 2018 12:45:31 -0700 Subject: [PATCH 5/9] rebase changes --- .../DefaultLinkGenerator.cs | 3 +- .../DefaultLinkGeneratorTest.cs | 29 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs index d56e7092ea..f8e82f074f 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs @@ -108,10 +108,11 @@ namespace Microsoft.AspNetCore.Routing new RouteTemplate(endpoint.RoutePattern), new RouteValueDictionary(endpoint.RoutePattern.Defaults)); + var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata(); var templateValuesResult = templateBinder.GetValues( ambientValues: ambientValues, explicitValues: explicitValues, - requiredKeys: endpoint.RequiredValues.Keys); + requiredKeys: routeValuesAddressMetadata?.RequiredValues.Keys); if (templateValuesResult == null) { // We're missing one of the required values for this route. diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs index c226e3edd6..84e9701cd8 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -1311,9 +1311,10 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint1 = EndpointFactory.CreateMatcherEndpoint( "Product/Edit/{id}", - requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, - metadata: new RouteNameMetadata("EditProduct")); + metadata: new RouteValuesAddressMetadata( + "EditProduct", + new RouteValueDictionary(new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }))); var linkGenerator = CreateLinkGenerator(endpoint1); // Act @@ -1334,14 +1335,16 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint1 = EndpointFactory.CreateMatcherEndpoint( "Product/Edit/{id}", - requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, - metadata: new RouteNameMetadata("default")); + metadata: new RouteValuesAddressMetadata( + "default", + new RouteValueDictionary(new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }))); var endpoint2 = EndpointFactory.CreateMatcherEndpoint( "Product/Details/{id}", - requiredValues: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, defaults: new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }, - metadata: new RouteNameMetadata("default")); + metadata: new RouteValuesAddressMetadata( + "default", + new RouteValueDictionary(new { controller = "Product", action = "Edit", area = (string)null, page = (string)null }))); var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); // Act @@ -1528,6 +1531,11 @@ namespace Microsoft.AspNetCore.Routing } } + private interface INameMetadata + { + string Name { get; } + } + private class NameMetadata : INameMetadata { public NameMetadata(string name) @@ -1536,14 +1544,5 @@ namespace Microsoft.AspNetCore.Routing } public string Name { get; } } - - private class RouteNameMetadata : IRouteNameMetadata - { - public RouteNameMetadata(string name) - { - Name = name; - } - public string Name { get; } - } } } From 27a35d5d9bfa439c1c774da09dd995e4a7b82a64 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 3 Aug 2018 16:51:09 -0700 Subject: [PATCH 6/9] fix benchmarks manifest --- benchmarkapps/Benchmarks/benchmarks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarkapps/Benchmarks/benchmarks.json b/benchmarkapps/Benchmarks/benchmarks.json index 794ea8c770..1779a80c8c 100644 --- a/benchmarkapps/Benchmarks/benchmarks.json +++ b/benchmarkapps/Benchmarks/benchmarks.json @@ -22,7 +22,7 @@ "PlaintextDispatcher": { "Path": "/plaintext" }, - "PlaintextGlobalRouting": { + "PlaintextEndpointRouting": { "Path": "/plaintext" } } From 5ee3ae90023c691c80e89a0ae059f1cc8828b614 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 4 Aug 2018 13:34:25 +1200 Subject: [PATCH 7/9] Add XML docs to data sources and metadata (#692) --- .../CompositeEndpointDataSource.cs | 11 ++++++++ .../DefaultEndpointDataSource.cs | 19 ++++++++++++++ .../EndpointDataSource.cs | 11 ++++++++ .../HttpMethodMetadata.cs | 25 +++++++++++++++++++ .../IHttpMethodMetadata.cs | 10 ++++++++ .../ISuppressLinkGenerationMetadata.cs | 4 +++ .../SuppressLinkGenerationMetadata.cs | 4 +++ 7 files changed, 84 insertions(+) diff --git a/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs index 3341b21796..b247291db4 100644 --- a/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs @@ -12,6 +12,9 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Routing { + /// + /// Represents an whose values come from a collection of instances. + /// [DebuggerDisplay("{DebuggerDisplayString,nq}")] public sealed class CompositeEndpointDataSource : EndpointDataSource { @@ -33,12 +36,20 @@ namespace Microsoft.AspNetCore.Routing _lock = new object(); } + /// + /// Gets a used to signal invalidation of cached + /// instances. + /// + /// The . public override IChangeToken GetChangeToken() { EnsureInitialized(); return _consumerChangeToken; } + /// + /// Returns a read-only collection of instances. + /// public override IReadOnlyList Endpoints { get diff --git a/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSource.cs index b73b8fcc0b..7ec3f4c6e6 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSource.cs @@ -8,15 +8,26 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Routing { + /// + /// Provides a collection of instances. + /// public sealed class DefaultEndpointDataSource : EndpointDataSource { private readonly List _endpoints; + /// + /// Initializes a new instance of the class. + /// + /// The instances that the data source will return. public DefaultEndpointDataSource(params Endpoint[] endpoints) : this((IEnumerable) endpoints) { } + /// + /// Initializes a new instance of the class. + /// + /// The instances that the data source will return. public DefaultEndpointDataSource(IEnumerable endpoints) { if (endpoints == null) @@ -28,8 +39,16 @@ namespace Microsoft.AspNetCore.Routing _endpoints.AddRange(endpoints); } + /// + /// Gets a used to signal invalidation of cached + /// instances. + /// + /// The . public override IChangeToken GetChangeToken() => NullChangeToken.Singleton; + /// + /// Returns a read-only collection of instances. + /// public override IReadOnlyList Endpoints => _endpoints; } } diff --git a/src/Microsoft.AspNetCore.Routing/EndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/EndpointDataSource.cs index b5a1c40ebe..6ae887d0a0 100644 --- a/src/Microsoft.AspNetCore.Routing/EndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Routing/EndpointDataSource.cs @@ -6,10 +6,21 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Routing { + /// + /// Provides a collection of instances. + /// public abstract class EndpointDataSource { + /// + /// Gets a used to signal invalidation of cached + /// instances. + /// + /// The . public abstract IChangeToken GetChangeToken(); + /// + /// Returns a read-only collection of instances. + /// public abstract IReadOnlyList Endpoints { get; } } } diff --git a/src/Microsoft.AspNetCore.Routing/HttpMethodMetadata.cs b/src/Microsoft.AspNetCore.Routing/HttpMethodMetadata.cs index 7d76b56cd0..f01bf2a2a5 100644 --- a/src/Microsoft.AspNetCore.Routing/HttpMethodMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/HttpMethodMetadata.cs @@ -8,14 +8,32 @@ using System.Linq; namespace Microsoft.AspNetCore.Routing { + /// + /// Represents HTTP method metadata used during routing. + /// [DebuggerDisplay("{DebuggerToString(),nq}")] public sealed class HttpMethodMetadata : IHttpMethodMetadata { + /// + /// Initializes a new instance of the class. + /// + /// + /// The HTTP methods used during routing. + /// An empty collection means any HTTP method will be accepted. + /// public HttpMethodMetadata(IEnumerable httpMethods) : this(httpMethods, acceptCorsPreflight: false) { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The HTTP methods used during routing. + /// An empty collection means any HTTP method will be accepted. + /// + /// A value indicating whether routing accepts CORS preflight requests. public HttpMethodMetadata(IEnumerable httpMethods, bool acceptCorsPreflight) { if (httpMethods == null) @@ -27,8 +45,15 @@ namespace Microsoft.AspNetCore.Routing AcceptCorsPreflight = acceptCorsPreflight; } + /// + /// Returns a value indicating whether the associated endpoint should accept CORS preflight requests. + /// public bool AcceptCorsPreflight { get; } + /// + /// Returns a read-only collection of HTTP methods used during routing. + /// An empty collection means any HTTP method will be accepted. + /// public IReadOnlyList HttpMethods { get; } private string DebuggerToString() diff --git a/src/Microsoft.AspNetCore.Routing/IHttpMethodMetadata.cs b/src/Microsoft.AspNetCore.Routing/IHttpMethodMetadata.cs index 83b99d8f72..af67c6e952 100644 --- a/src/Microsoft.AspNetCore.Routing/IHttpMethodMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/IHttpMethodMetadata.cs @@ -5,10 +5,20 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Routing { + /// + /// Represents HTTP method metadata used during routing. + /// public interface IHttpMethodMetadata { + /// + /// Returns a value indicating whether the associated endpoint should accept CORS preflight requests. + /// bool AcceptCorsPreflight { get; } + /// + /// Returns a read-only collection of HTTP methods used during routing. + /// An empty collection means any HTTP method will be accepted. + /// IReadOnlyList HttpMethods { get; } } } diff --git a/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs b/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs index 23c5e168d8..4725b1a8c2 100644 --- a/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/ISuppressLinkGenerationMetadata.cs @@ -3,6 +3,10 @@ namespace Microsoft.AspNetCore.Routing { + /// + /// Represents metadata used during link generation. + /// The associated endpoint will not be considered for link generation. + /// public interface ISuppressLinkGenerationMetadata { } diff --git a/src/Microsoft.AspNetCore.Routing/SuppressLinkGenerationMetadata.cs b/src/Microsoft.AspNetCore.Routing/SuppressLinkGenerationMetadata.cs index 599267c570..88efd9077f 100644 --- a/src/Microsoft.AspNetCore.Routing/SuppressLinkGenerationMetadata.cs +++ b/src/Microsoft.AspNetCore.Routing/SuppressLinkGenerationMetadata.cs @@ -3,6 +3,10 @@ namespace Microsoft.AspNetCore.Routing { + /// + /// Represents metadata used during link generation. + /// The associated endpoint will not be considered for link generation. + /// public sealed class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } From e53a9f57dba3d53cddcc6393d08372316fac64a3 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 3 Aug 2018 13:48:55 -0700 Subject: [PATCH 8/9] adding more docs --- .../Matching/CandidateSet.cs | 36 ++++++++++++++-- .../Matching/CandidateState.cs | 30 ++++++++++++++ .../Matching/EndpointMetadataComparer.cs | 41 +++++++++++++++++++ .../Matching/EndpointSelector.cs | 17 ++++++++ .../Matching/HttpMethodMatcherPolicy.cs | 26 ++++++++++++ .../Matching/IEndpointComparerPolicy.cs | 22 ++++++++++ .../Matching/IEndpointSelectorPolicy.cs | 18 ++++++++ .../Matching/MatcherEndpoint.cs | 26 ++++++++++++ .../Matching/MatcherPolicy.cs | 18 ++++++++ 9 files changed, 230 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Routing/Matching/CandidateSet.cs b/src/Microsoft.AspNetCore.Routing/Matching/CandidateSet.cs index dc51752923..be134bf6ce 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/CandidateSet.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/CandidateSet.cs @@ -6,6 +6,11 @@ using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// Represents a set of candidates that have been matched + /// by the routing system. Used by implementations of + /// and . + /// public sealed class CandidateSet { // We inline storage for 4 candidates here to avoid allocations in common @@ -18,8 +23,18 @@ namespace Microsoft.AspNetCore.Routing.Matching private CandidateState[] _additionalCandidates; - // Provided to make testing possible/easy for someone implementing - // an EndpointSelector. + /// + /// + /// Initializes a new instances of the candidate set structure with the provided list of endpoints + /// and associated scores. + /// + /// + /// The constructor is provided to enable unit tests of implementations of + /// and . + /// + /// + /// The list of endpoints, sorted in descending priority order. + /// The list of endpoint scores. . public CandidateSet(MatcherEndpoint[] endpoints, int[] scores) { Count = endpoints.Length; @@ -112,12 +127,25 @@ namespace Microsoft.AspNetCore.Routing.Matching } } + /// + /// Gets the count of candidates in the set. + /// public int Count { get; } - // Note that this is a ref-return because of both mutability and performance. - // We don't want to copy these fat structs if it can be avoided. + /// + /// Gets the associated with the candidate + /// at . + /// + /// The candidate index. + /// + /// A reference to the . The result is returned by reference + /// and intended to be mutated. + /// public ref CandidateState this[int index] { + // Note that this is a ref-return because of both mutability and performance. + // We don't want to copy these fat structs if it can be avoided. + // PERF: Force inlining [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/src/Microsoft.AspNetCore.Routing/Matching/CandidateState.cs b/src/Microsoft.AspNetCore.Routing/Matching/CandidateState.cs index 4cb525bfac..cf27c04c78 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/CandidateState.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/CandidateState.cs @@ -3,6 +3,9 @@ namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// The mutable state associated with a candidate in a . + /// public struct CandidateState { internal CandidateState(MatcherEndpoint endpoint, int score) @@ -14,12 +17,39 @@ namespace Microsoft.AspNetCore.Routing.Matching Values = null; } + /// + /// Gets the . + /// public MatcherEndpoint Endpoint { get; } + /// + /// Gets the score of the within the current + /// . + /// + /// + /// + /// Candidates within a set are ordered in priority order and then assigned a + /// sequential score value based on that ordering. Candiates with the same + /// score are considered to have equal priority. + /// + /// + /// The score values are used in the to determine + /// whether a set of matching candidates is an ambiguous match. + /// + /// public int Score { get; } + /// + /// Gets or sets a value which indicates where the is considered + /// a valid candiate for the current request. Set this value to false to exclude an + /// from consideration. + /// public bool IsValidCandidate { get; set; } + /// + /// Gets or sets the associated with the + /// and the current request. + /// public RouteValueDictionary Values { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Routing/Matching/EndpointMetadataComparer.cs b/src/Microsoft.AspNetCore.Routing/Matching/EndpointMetadataComparer.cs index 6c617f5d03..b2a7b9ec00 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/EndpointMetadataComparer.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/EndpointMetadataComparer.cs @@ -6,10 +6,30 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// A base class for implementations that use + /// a specific type of metadata from for comparison. + /// Useful for implementing . + /// + /// + /// The type of metadata to compare. Typically this is a type of metadata related + /// to the application concern being handled. + /// public abstract class EndpointMetadataComparer : IComparer where TMetadata : class { public static readonly EndpointMetadataComparer Default = new DefaultComparer(); + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, + /// or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// An implementation of this method must return a value less than zero if + /// x is less than y, zero if x is equal to y, or a value greater than zero if x is + /// greater than y. + /// public int Compare(Endpoint x, Endpoint y) { if (x == null) @@ -25,11 +45,32 @@ namespace Microsoft.AspNetCore.Routing.Matching return CompareMetadata(GetMetadata(x), GetMetadata(y)); } + /// + /// Gets the metadata of type from the provided endpoint. + /// + /// The . + /// The instance or null. protected virtual TMetadata GetMetadata(Endpoint endpoint) { return endpoint.Metadata.GetMetadata(); } + /// + /// Compares two instances. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// An implementation of this method must return a value less than zero if + /// x is less than y, zero if x is equal to y, or a value greater than zero if x is + /// greater than y. + /// + /// + /// The base-class implementation of this method will compare metadata based on whether + /// or not they are null. The effect of this is that when endpoints are being + /// compared, the endpoint that defines an instance of + /// will be considered higher priority. + /// protected virtual int CompareMetadata(TMetadata x, TMetadata y) { // The default policy is that if x endpoint defines TMetadata, and diff --git a/src/Microsoft.AspNetCore.Routing/Matching/EndpointSelector.cs b/src/Microsoft.AspNetCore.Routing/Matching/EndpointSelector.cs index 0af172bdca..436c3df6f5 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/EndpointSelector.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/EndpointSelector.cs @@ -6,8 +6,25 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// A service that is responsible for the final selection + /// decision. To use a custom register an implementation + /// of in the dependency injection container as a singleton. + /// public abstract class EndpointSelector { + /// + /// Asynchronously selects an from the . + /// + /// The associated with the current request. + /// The associated with the current request. + /// The . + /// A that completes asynchronously once endpoint selection is complete. + /// + /// An should assign the , + /// , and properties + /// once an endpoint is selected. + /// public abstract Task SelectAsync( HttpContext httpContext, IEndpointFeature feature, diff --git a/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs b/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs index 9968395b29..0be502b1a2 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs @@ -12,6 +12,10 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// An that implements filtering and selection by + /// the HTTP method of a request. + /// public sealed class HttpMethodMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy { // Used in tests @@ -25,12 +29,23 @@ namespace Microsoft.AspNetCore.Routing.Matching // Used in tests internal const string AnyMethod = "*"; + /// + /// For framework use only. + /// public IComparer Comparer => new HttpMethodMetadataEndpointComparer(); // The order value is chosen to be less than 0, so that it comes before naively // written policies. + /// + /// For framework use only. + /// public override int Order => -1000; + /// + /// For framework use only. + /// + /// + /// public bool AppliesToNode(IReadOnlyList endpoints) { if (endpoints == null) @@ -49,6 +64,11 @@ namespace Microsoft.AspNetCore.Routing.Matching return false; } + /// + /// For framework use only. + /// + /// + /// public IReadOnlyList GetEdges(IReadOnlyList endpoints) { // The algorithm here is designed to be preserve the order of the endpoints @@ -180,6 +200,12 @@ namespace Microsoft.AspNetCore.Routing.Matching } } + /// + /// For framework use only. + /// + /// + /// + /// public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList edges) { var destinations = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/Microsoft.AspNetCore.Routing/Matching/IEndpointComparerPolicy.cs b/src/Microsoft.AspNetCore.Routing/Matching/IEndpointComparerPolicy.cs index 9c187a6fec..a1010c181c 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/IEndpointComparerPolicy.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/IEndpointComparerPolicy.cs @@ -5,8 +5,30 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// A interface that can be implemented to sort + /// endpoints. Implementations of must + /// inherit from and should be registered in + /// the dependency injection container as singleton services of type . + /// + /// + /// + /// Candidates in a are sorted based on their priority. Defining + /// a adds an additional criterion to the sorting + /// operation used to order candidates. + /// + /// + /// As an example, the implementation of implements + /// to ensure that endpoints matching specific HTTP + /// methods are sorted with a higher priority than endpoints without a specific HTTP method + /// requirement. + /// + /// public interface IEndpointComparerPolicy { + /// + /// Gets an that will be used to sort the endpoints. + /// IComparer Comparer { get; } } } diff --git a/src/Microsoft.AspNetCore.Routing/Matching/IEndpointSelectorPolicy.cs b/src/Microsoft.AspNetCore.Routing/Matching/IEndpointSelectorPolicy.cs index 63fe7dd8fd..7dfd04b7bd 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/IEndpointSelectorPolicy.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/IEndpointSelectorPolicy.cs @@ -5,8 +5,26 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// A interface that can implemented to filter endpoints + /// in a . Implementations of must + /// inherit from and should be registered in + /// the dependency injection container as singleton services of type . + /// public interface IEndpointSelectorPolicy { + /// + /// Applies the policy to the . + /// + /// + /// The associated with the current request. + /// + /// The . + /// + /// Implementations of should implement this method + /// and filter the set of candidates in the by setting + /// to false where desired. + /// void Apply(HttpContext httpContext, CandidateSet candidates); } } diff --git a/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs b/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs index 194fb0dc3d..ff5067cb81 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpoint.cs @@ -9,6 +9,9 @@ using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Routing.Matching { + /// + /// Represents an that can be used in URL matching or URL generation. + /// public sealed class MatcherEndpoint : Endpoint { internal static readonly Func EmptyInvoker = (next) => @@ -16,6 +19,16 @@ namespace Microsoft.AspNetCore.Routing.Matching return (context) => Task.CompletedTask; }; + /// + /// Initializes a new instance of the class. + /// + /// The delegate to invoke to create a . + /// The to use in URL matching. + /// The order assigned to the endpoint. + /// + /// The or metadata associated with the endpoint. + /// + /// The informational display name of the endpoint. public MatcherEndpoint( Func invoker, RoutePattern routePattern, @@ -39,10 +52,23 @@ namespace Microsoft.AspNetCore.Routing.Matching Order = order; } + /// + /// Gets the invoker. The invoker is a delegate used to create a . + /// public Func Invoker { get; } + /// + /// Gets the order value of endpoint. + /// + /// + /// The order value provides absolute control over the priority + /// of an endpoint. Endpoints with a lower numeric value of order have higher priority. + /// public int Order { get; } + /// + /// Gets the associated with the endpoint. + /// public RoutePattern RoutePattern { get; } } } diff --git a/src/Microsoft.AspNetCore.Routing/Matching/MatcherPolicy.cs b/src/Microsoft.AspNetCore.Routing/Matching/MatcherPolicy.cs index 92d715c936..05a654efd9 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/MatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/MatcherPolicy.cs @@ -1,10 +1,28 @@ // 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.Matching; + namespace Microsoft.AspNetCore.Routing { + /// + /// Defines a policy that applies behaviors to the URL matcher. Implementations + /// of and related interfaces must be registered + /// in the dependency injection container as singleton services of type + /// . + /// + /// + /// implementations can implement the following + /// interfaces , , + /// and . + /// public abstract class MatcherPolicy { + /// + /// Gets a value that determines the order the should + /// be applied. Policies are applied in ascending numeric value of the + /// property. + /// public abstract int Order { get; } } } From c44f2303e7dfce50a85cb60abab38b357d8c5af1 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 5 Aug 2018 19:25:57 +0000 Subject: [PATCH 9/9] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 54 ++++++++++++++++++++-------------------- korebuild-lock.txt | 4 +-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 252281f058..2446afb639 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,33 +4,33 @@ 0.10.13 - 2.2.0-preview1-17102 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-preview1-20180731.1 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 2.0.9 2.1.2 2.2.0-preview1-26618-02 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 6b8da29e6b..c7af2292c7 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17102 -commithash:e7e2b5a97ca92cfc6acc4def534cb0901a6d1eb9 +version:2.2.0-preview1-20180731.1 +commithash:29fde58465439f4bb9df40830635ed758e063daf