Added initial support for link generation when using Dispatcher

This commit is contained in:
Kiran Challa 2018-06-08 14:28:45 -07:00
parent 7423bb8702
commit d3ddc1709a
20 changed files with 1300 additions and 42 deletions

View File

@ -35,7 +35,8 @@ namespace Benchmarks
values: new { },
order: 0,
metadata: EndpointMetadataCollection.Empty,
displayName: "Plaintext"),
displayName: "Plaintext",
address: null),
}));
});
}

View File

@ -31,7 +31,8 @@ namespace Microsoft.AspNetCore.Routing.Matchers
new { },
0,
EndpointMetadataCollection.Empty,
template);
template,
address: null);
}
internal static int[] SampleRequests(int endpointCount, int count)

View File

@ -31,7 +31,7 @@ namespace DispatcherSample.Web
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
},
"/", new { }, 0, EndpointMetadataCollection.Empty, "Home"),
"/", new { }, 0, EndpointMetadataCollection.Empty, "Home", address: null),
new MatcherEndpoint((next) => (httpContext) =>
{
var response = httpContext.Response;
@ -41,7 +41,7 @@ namespace DispatcherSample.Web
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
},
"/plaintext", new { }, 0, EndpointMetadataCollection.Empty, "Plaintext"),
"/plaintext", new { }, 0, EndpointMetadataCollection.Empty, "Plaintext", address: null),
}));
});
}

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing
{
public class Address
{
public Address()
{
}
public Address(string name)
{
Name = name;
}
public string Name { get; set; }
}
}

View File

@ -8,15 +8,21 @@ namespace Microsoft.AspNetCore.Routing
[DebuggerDisplay("{DisplayName,nq}")]
public abstract class Endpoint
{
protected Endpoint(EndpointMetadataCollection metadata, string displayName)
protected Endpoint(
EndpointMetadataCollection metadata,
string displayName,
Address address)
{
// Both allowed to be null
// All are allowed to be null
Metadata = metadata ?? EndpointMetadataCollection.Empty;
DisplayName = displayName;
Address = address;
}
public string DisplayName { get; }
public EndpointMetadataCollection Metadata { get; }
public Address Address { get; }
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Routing
{
public interface IEndpointFinder
{
IEnumerable<Endpoint> FindEndpoints(Address address);
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing
{
public interface ILinkGenerator
{
bool TryGetLink(LinkGeneratorContext context, out string link);
string GetLink(LinkGeneratorContext context);
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing
{
public class LinkGeneratorContext
{
public Address Address { get; set; }
public RouteValueDictionary AmbientValues { get; set; }
public RouteValueDictionary SuppliedValues { get; set; }
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Routing
{
internal class DefaultEndpointFinder : IEndpointFinder
{
private readonly CompositeEndpointDataSource _endpointDatasource;
private readonly ILogger<DefaultEndpointFinder> _logger;
public DefaultEndpointFinder(
CompositeEndpointDataSource endpointDataSource,
ILogger<DefaultEndpointFinder> logger)
{
_endpointDatasource = endpointDataSource;
_logger = logger;
}
public IEnumerable<Endpoint> FindEndpoints(Address lookupAddress)
{
var allEndpoints = _endpointDatasource.Endpoints;
if (lookupAddress == null || string.IsNullOrEmpty(lookupAddress.Name))
{
return allEndpoints;
}
var endpointsWithAddress = allEndpoints.Where(ep => ep.Address != null);
if (!endpointsWithAddress.Any())
{
return allEndpoints;
}
foreach (var endpoint in endpointsWithAddress)
{
if (string.Equals(lookupAddress.Name, endpoint.Address.Name, StringComparison.OrdinalIgnoreCase))
{
return new[] { endpoint };
}
}
_logger.LogDebug(
$"Could not find an endpoint having an address with name '{lookupAddress.Name}'.");
return Enumerable.Empty<Endpoint>();
}
}
}

View File

@ -0,0 +1,98 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matchers;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
namespace Microsoft.AspNetCore.Routing
{
public class DefaultLinkGenerator : ILinkGenerator
{
private readonly IEndpointFinder _endpointFinder;
private readonly ObjectPool<UriBuildingContext> _uriBuildingContextPool;
private readonly ILogger<DefaultLinkGenerator> _logger;
public DefaultLinkGenerator(
IEndpointFinder endpointFinder,
ObjectPool<UriBuildingContext> uriBuildingContextPool,
ILogger<DefaultLinkGenerator> logger)
{
_endpointFinder = endpointFinder;
_uriBuildingContextPool = uriBuildingContextPool;
_logger = logger;
}
public string GetLink(LinkGeneratorContext context)
{
if (TryGetLink(context, out var link))
{
return link;
}
throw new InvalidOperationException("Could not find a matching endpoint to generate a link.");
}
public bool TryGetLink(LinkGeneratorContext context, out string link)
{
var address = context.Address;
var endpoints = _endpointFinder.FindEndpoints(address);
link = null;
if (endpoints == null)
{
return false;
}
var matcherEndpoints = endpoints.OfType<MatcherEndpoint>();
if (!matcherEndpoints.Any())
{
//todo:log here
return false;
}
foreach (var endpoint in matcherEndpoints)
{
link = GetLink(endpoint.ParsedTemlate, endpoint.Values, context);
if (link != null)
{
return true;
}
}
return false;
}
private string GetLink(
RouteTemplate template,
IReadOnlyDictionary<string, object> defaultValues,
LinkGeneratorContext context)
{
var defaults = new RouteValueDictionary(defaultValues);
var templateBinder = new TemplateBinder(
UrlEncoder.Default,
_uriBuildingContextPool,
template,
defaults);
var values = templateBinder.GetValues(
new RouteValueDictionary(context.AmbientValues),
new RouteValueDictionary(context.SuppliedValues));
if (values == null)
{
// We're missing one of the required values for this route.
return null;
}
//TODO: route constraint matching here
return templateBinder.BindValues(values.AcceptedValues);
}
}
}

View File

@ -34,6 +34,9 @@ namespace Microsoft.Extensions.DependencyInjection
//
services.TryAddSingleton<MatcherFactory, TreeMatcherFactory>();
// Link generation related services
services.TryAddSingleton<IEndpointFinder, DefaultEndpointFinder>();
services.TryAddSingleton<ILinkGenerator, DefaultLinkGenerator>();
//
// Endpoint Selection
//

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Template;
namespace Microsoft.AspNetCore.Routing.Matchers
{
@ -21,8 +22,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers
object values,
int order,
EndpointMetadataCollection metadata,
string displayName)
: base(metadata, displayName)
string displayName,
Address address)
: base(metadata, displayName, address)
{
if (invoker == null)
{
@ -36,14 +38,42 @@ namespace Microsoft.AspNetCore.Routing.Matchers
Invoker = invoker;
Template = template;
Values = new RouteValueDictionary(values);
ParsedTemlate = TemplateParser.Parse(template);
var mergedDefaults = GetDefaults(ParsedTemlate, new RouteValueDictionary(values));
Values = mergedDefaults;
Order = order;
}
public int Order { get; }
public Func<RequestDelegate, RequestDelegate> Invoker { get; }
public string Template { get; }
public IReadOnlyDictionary<string, object> Values { get; }
// Todo: needs review
public RouteTemplate ParsedTemlate { get; }
private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate, RouteValueDictionary defaults)
{
var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
foreach (var parameter in parsedTemplate.Parameters)
{
if (parameter.DefaultValue != null)
{
if (result.ContainsKey(parameter.Name))
{
throw new InvalidOperationException(
Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(
parameter.Name));
}
else
{
result.Add(parameter.Name, parameter.DefaultValue);
}
}
}
return result;
}
}
}

View File

@ -0,0 +1,161 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Microsoft.AspNetCore.Routing
{
public class DefaultEndpointFinderTest
{
[Fact]
public void FindEndpoints_IgnoresCase_ForRouteNameLookup()
{
// Arrange
var endpoint1 = CreateEndpoint(new Address("home"));
var endpoint2 = CreateEndpoint(new Address("admin"));
var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2);
// Act
var result = endpointFinder.FindEndpoints(new Address("Admin"));
// Assert
var endpoint = Assert.Single(result);
Assert.Same(endpoint2, endpoint);
}
[Fact]
public void FindEndpoints_MultipleEndpointsWithSameName_ReturnsFirstEndpoint_WithMatchingName()
{
// Arrange
var name = "common-tag-for-all-my-section's-routes";
var endpoint1 = CreateEndpoint(new Address(name));
var endpoint2 = CreateEndpoint(new Address("admin"));
var endpoint3 = CreateEndpoint(new Address(name));
var endpoint4 = CreateEndpoint(new Address("products"));
var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2, endpoint3, endpoint4);
// Act
var result = endpointFinder.FindEndpoints(new Address(name));
// Assert
var endpoint = Assert.Single(result);
Assert.Same(endpoint, endpoint1);
}
[Fact]
public void FindEndpoints_ReturnsAllEndpoints_WhenNoEndpointsHaveAddress()
{
// Arrange
var endpoint1 = CreateEndpoint(address: null);
var endpoint2 = CreateEndpoint(address: null);
var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2);
// Act
var result = endpointFinder.FindEndpoints(new Address("Admin"));
// Assert
Assert.Collection(
result,
(ep) => Assert.Same(endpoint1, ep),
(ep) => Assert.Same(endpoint2, ep));
}
[Fact]
public void FindEndpoints_ReturnsAllEndpoints_WhenLookupAddress_IsNull()
{
// Arrange
var endpoint1 = CreateEndpoint(new Address("home"));
var endpoint2 = CreateEndpoint(new Address("admin"));
var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2);
// Act
var result = endpointFinder.FindEndpoints(lookupAddress: null);
// Assert
Assert.Collection(
result,
(ep) => Assert.Same(endpoint1, ep),
(ep) => Assert.Same(endpoint2, ep));
}
[Fact]
public void FindEndpoints_ReturnsAllEndpoints_WhenNoEndpointsHaveAddress_AndLookupAddress_IsNull()
{
// Arrange
var endpoint1 = CreateEndpoint(address: null);
var endpoint2 = CreateEndpoint(address: null);
var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2);
// Act
var result = endpointFinder.FindEndpoints(lookupAddress: null);
// Assert
Assert.Collection(
result,
(ep) => Assert.Same(endpoint1, ep),
(ep) => Assert.Same(endpoint2, ep));
}
[Fact]
public void FindEndpoints_ReturnsAllEndpoints_WhenNoInformationGiven_OnLookupAddress()
{
// Arrange
var endpoint1 = CreateEndpoint(new Address("home"));
var endpoint2 = CreateEndpoint(new Address("admin"));
var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2);
// Act
var result = endpointFinder.FindEndpoints(new Address(name: null));
// Assert
Assert.Collection(
result,
(ep) => Assert.Same(endpoint1, ep),
(ep) => Assert.Same(endpoint2, ep));
}
[Fact]
public void FindEndpoints_ReturnsEmpty_WhenNoEndpointFound_WithLookupAddress_Name()
{
// Arrange
var endpoint1 = CreateEndpoint(new Address("home"));
var endpoint2 = CreateEndpoint(new Address("admin"));
var endpointFinder = CreateDefaultEndpointFinder(endpoint1, endpoint2);
// Act
var result = endpointFinder.FindEndpoints(new Address("DoesNotExist"));
// Assert
Assert.Empty(result);
}
private Endpoint CreateEndpoint(Address address)
{
return new TestEndpoint(
EndpointMetadataCollection.Empty,
displayName: null,
address: address);
}
private DefaultEndpointFinder CreateDefaultEndpointFinder(params Endpoint[] endpoints)
{
return new DefaultEndpointFinder(
new CompositeEndpointDataSource(new[] { new DefaultEndpointDataSource(endpoints) }),
NullLogger<DefaultEndpointFinder>.Instance);
}
private class HomeController
{
public void Index() { }
public void Contact() { }
}
private class AdminController
{
public void Index() { }
public void Contact() { }
}
}
}

View File

@ -0,0 +1,799 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matchers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Routing
{
public class DefaultLinkGeneratorTest
{
[Fact]
public void GetLink_Success()
{
// Arrange
var endpoint = CreateEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(new { controller = "Home" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home", link);
}
[Fact]
public void GetLink_Fail_ThrowsException()
{
// Arrange
var expectedMessage = "Could not find a matching endpoint to generate a link.";
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(new { controller = "Home" });
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => linkGenerator.GetLink(context));
Assert.Equal(expectedMessage, exception.Message);
}
[Fact]
public void TryGetLink_Fail()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(new { controller = "Home" });
// Act
var canGenerateLink = linkGenerator.TryGetLink(context, out var link);
// Assert
Assert.False(canGenerateLink);
Assert.Null(link);
}
[Fact]
public void GetLink_MultipleEndpoints_Success()
{
// Arrange
var endpoint1 = CreateEndpoint("{controller}/{action}/{id?}");
var endpoint2 = CreateEndpoint("{controller}/{action}");
var endpoint3 = CreateEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint1, endpoint2, endpoint3));
var context = CreateLinkGeneratorContext(new { controller = "Home", action = "Index", id = "10" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index/10", link);
}
[Fact]
public void GetLink_MultipleEndpoints_Success2()
{
// Arrange
var endpoint1 = CreateEndpoint("{controller}/{action}/{id}");
var endpoint2 = CreateEndpoint("{controller}/{action}");
var endpoint3 = CreateEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint1, endpoint2, endpoint3));
var context = CreateLinkGeneratorContext(new { controller = "Home", action = "Index" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index", link);
}
[Fact]
public void GetLink_EncodesValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { name = "name with %special #characters" },
ambientValues: new { controller = "Home", action = "Index" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index?name=name%20with%20%25special%20%23characters", link);
}
[Fact]
public void GetLink_ForListOfStrings()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
new { color = new List<string> { "red", "green", "blue" } },
new { controller = "Home", action = "Index" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index?color=red&color=green&color=blue", link);
}
[Fact]
public void GetLink_ForListOfInts()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
new { items = new List<int> { 10, 20, 30 } },
new { controller = "Home", action = "Index" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index?items=10&items=20&items=30", link);
}
[Fact]
public void GetLink_ForList_Empty()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
new { color = new List<string> { } },
new { controller = "Home", action = "Index" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index", link);
}
[Fact]
public void GetLink_ForList_StringWorkaround()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
new { page = 1, color = new List<string> { "red", "green", "blue" }, message = "textfortest" },
new { controller = "Home", action = "Index" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index?page=1&color=red&color=green&color=blue&message=textfortest", link);
}
[Fact]
public void GetLink_Success_AmbientValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { action = "Index" },
ambientValues: new { controller = "Home" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index", link);
}
//[Fact]
//public void RouteGenerationRejectsConstraints()
//{
// // Arrange
// var context = CreateLinkGeneratorContext(new { p1 = "abcd" });
// var endpoint = CreateEndpoint(
// "{p1}/{p2}",
// new { p2 = "catchall" },
// true,
// new RouteValueDictionary(new { p2 = "\\d{4}" }));
// // Act
// var virtualPath = route.GetLink(context);
// // Assert
// Assert.Null(virtualPath);
//}
//[Fact]
//public void RouteGenerationAcceptsConstraints()
//{
// // Arrange
// var context = CreateLinkGeneratorContext(new { p1 = "hello", p2 = "1234" });
// var endpoint = CreateEndpoint(
// "{p1}/{p2}",
// new { p2 = "catchall" },
// true,
// new RouteValueDictionary(new { p2 = "\\d{4}" }));
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.NotNull(pathData);
// Assert.Equal("/hello/1234", link);
//
//
//}
//[Fact]
//public void RouteWithCatchAllRejectsConstraints()
//{
// // Arrange
// var context = CreateLinkGeneratorContext(new { p1 = "abcd" });
// var endpoint = CreateEndpoint(
// "{p1}/{*p2}",
// new { p2 = "catchall" },
// true,
// new RouteValueDictionary(new { p2 = "\\d{4}" }));
// // Act
// var virtualPath = route.GetLink(context);
// // Assert
// Assert.Null(virtualPath);
//}
//[Fact]
//public void RouteWithCatchAllAcceptsConstraints()
//{
// // Arrange
// var context = CreateLinkGeneratorContext(new { p1 = "hello", p2 = "1234" });
// var endpoint = CreateEndpoint(
// "{p1}/{*p2}",
// new { p2 = "catchall" },
// true,
// new RouteValueDictionary(new { p2 = "\\d{4}" }));
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.NotNull(pathData);
// Assert.Equal("/hello/1234", link);
//
//
//}
//[Fact]
//public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString()
//{
// // Arrange
// var context = CreateLinkGeneratorContext(new { p1 = "hello", p2 = "1234" });
// var target = new Mock<IRouteConstraint>();
// target
// .Setup(
// e => e.Match(
// It.IsAny<HttpContext>(),
// It.IsAny<IRouter>(),
// It.IsAny<string>(),
// It.IsAny<RouteValueDictionary>(),
// It.IsAny<RouteDirection>()))
// .Returns(true)
// .Verifiable();
// var endpoint = CreateEndpoint(
// "{p1}/{p2}",
// new { p2 = "catchall" },
// true,
// new RouteValueDictionary(new { p2 = target.Object }));
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.NotNull(pathData);
// Assert.Equal("/hello/1234", link);
//
//
// target.VerifyAll();
//}
//// Any ambient values from the current request should be visible to constraint, even
//// if they have nothing to do with the route generating a link
//[Fact]
//public void GetLink_ConstraintsSeeAmbientValues()
//{
// // Arrange
// var constraint = new CapturingConstraint();
// var endpoint = CreateEndpoint(
// template: "slug/{controller}/{action}",
// defaultValues: null,
// handleRequest: true,
// constraints: new { c = constraint });
// var context = CreateLinkGeneratorContext(
// values: new { action = "Store" },
// ambientValues: new { Controller = "Home", action = "Blog", extra = "42" });
// var expectedValues = new RouteValueDictionary(
// new { controller = "Home", action = "Store", extra = "42" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/slug/Home/Store", link);
//
//
// Assert.Equal(expectedValues, constraint.Values);
//}
//// Non-parameter default values from the routing generating a link are not in the 'values'
//// collection when constraints are processed.
//[Fact]
//public void GetLink_ConstraintsDontSeeDefaults_WhenTheyArentParameters()
//{
// // Arrange
// var constraint = new CapturingConstraint();
// var endpoint = CreateEndpoint(
// template: "slug/{controller}/{action}",
// defaultValues: new { otherthing = "17" },
// handleRequest: true,
// constraints: new { c = constraint });
// var context = CreateLinkGeneratorContext(
// values: new { action = "Store" },
// ambientValues: new { Controller = "Home", action = "Blog" });
// var expectedValues = new RouteValueDictionary(
// new { controller = "Home", action = "Store" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/slug/Home/Store", link);
//
//
// Assert.Equal(expectedValues, constraint.Values);
//}
//// Default values are visible to the constraint when they are used to fill a parameter.
//[Fact]
//public void GetLink_ConstraintsSeesDefault_WhenThereItsAParamter()
//{
// // Arrange
// var constraint = new CapturingConstraint();
// var endpoint = CreateEndpoint(
// template: "slug/{controller}/{action}",
// defaultValues: new { action = "Index" },
// handleRequest: true,
// constraints: new { c = constraint });
// var context = CreateLinkGeneratorContext(
// values: new { controller = "Shopping" },
// ambientValues: new { Controller = "Home", action = "Blog" });
// var expectedValues = new RouteValueDictionary(
// new { controller = "Shopping", action = "Index" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/slug/Shopping", link);
//
//
// Assert.Equal(expectedValues, constraint.Values);
//}
//// Default values from the routing generating a link are in the 'values' collection when
//// constraints are processed - IFF they are specified as values or ambient values.
//[Fact]
//public void GetLink_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient()
//{
// // Arrange
// var constraint = new CapturingConstraint();
// var endpoint = CreateEndpoint(
// template: "slug/{controller}/{action}",
// defaultValues: new { otherthing = "17", thirdthing = "13" },
// handleRequest: true,
// constraints: new { c = constraint });
// var context = CreateLinkGeneratorContext(
// values: new { action = "Store", thirdthing = "13" },
// ambientValues: new { Controller = "Home", action = "Blog", otherthing = "17" });
// var expectedValues = new RouteValueDictionary(
// new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/slug/Home/Store", link);
//
//
// Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
//}
//[Fact]
//public void GetLink_InlineConstraints_Success()
//{
// // Arrange
// var endpoint = CreateEndpoint("{controller}/{action}/{id:int}");
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home", id = 4 });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/Home/Index/4", link);
//
//
//}
//[Fact]
//public void GetLink_InlineConstraints_NonMatchingvalue()
//{
// // Arrange
// var endpoint = CreateEndpoint("{controller}/{action}/{id:int}");
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home", id = "asf" });
// // Act
// var path = route.GetLink(context);
// // Assert
// Assert.Null(path);
//}
//[Fact]
//public void GetLink_InlineConstraints_OptionalParameter_ValuePresent()
//{
// // Arrange
// var endpoint = CreateEndpoint("{controller}/{action}/{id:int?}");
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home", id = 98 });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/Home/Index/98", link);
//
//
//}
//[Fact]
//public void GetLink_InlineConstraints_OptionalParameter_ValueNotPresent()
//{
// // Arrange
// var endpoint = CreateEndpoint("{controller}/{action}/{id:int?}");
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/Home/Index", link);
//
//
//}
//[Fact]
//public void GetLink_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails()
//{
// // Arrange
// var endpoint = CreateEndpoint("{controller}/{action}/{id:int?}");
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home", id = "sdfd" });
// // Act
// var path = route.GetLink(context);
// // Assert
// Assert.Null(path);
//}
//[Fact]
//public void GetLink_InlineConstraints_CompositeInlineConstraint()
//{
// // Arrange
// var endpoint = CreateEndpoint("{controller}/{action}/{id:int:range(1,20)}");
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home", id = 14 });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/Home/Index/14", link);
//
//
//}
//[Fact]
//public void GetLink_InlineConstraints_CompositeConstraint_FromConstructor()
//{
// // Arrange
// var constraint = new MaxLengthRouteConstraint(20);
// var endpoint = CreateEndpoint(
// template: "{controller}/{action}/{name:alpha}",
// defaultValues: null,
// handleRequest: true,
// constraints: new { name = constraint });
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home", name = "products" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/Home/Index/products", link);
//
//
//}
[Fact]
public void GetLink_OptionalParameter_ParameterPresentInValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index/products", link);
}
[Fact]
public void GetLink_OptionalParameter_ParameterNotPresentInValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index", link);
}
[Fact]
public void GetLink_OptionalParameter_ParameterPresentInValuesAndDefaults()
{
// Arrange
var endpoint = CreateEndpoint(
template: "{controller}/{action}/{name?}",
defaultValues: new { name = "default-products" });
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index/products", link);
}
[Fact]
public void GetLink_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
{
// Arrange
var endpoint = CreateEndpoint(
template: "{controller}/{action}/{name?}",
defaultValues: new { name = "products" });
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index", link);
}
[Fact]
public void GetLink_ParameterNotPresentInTemplate_PresentInValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products", format = "json" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index/products?format=json", link);
}
//[Fact]
//public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent()
//{
// // Arrange
// var endpoint = CreateEndpoint(
// template: "{controller}/{action}/.{name?}",
// defaultValues: null,
// handleRequest: true,
// constraints: null);
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home", name = "products" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/Home/Index/.products", link);
//
//
//}
//[Fact]
//public void GetLink_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent()
//{
// // Arrange
// var endpoint = CreateEndpoint(
// template: "{controller}/{action}/.{name?}",
// defaultValues: null,
// handleRequest: true,
// constraints: null);
// var context = CreateLinkGeneratorContext(
// values: new { action = "Index", controller = "Home" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/Home/Index/", link);
//
//
//}
[Fact]
public void GetLink_OptionalParameter_InSimpleSegment()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
var context = CreateLinkGeneratorContext(
suppliedValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(context);
// Assert
Assert.Equal("/Home/Index", link);
}
//[Fact]
//public void GetLink_TwoOptionalParameters_OneValueFromAmbientValues()
//{
// // Arrange
// var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}");
// var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
// var context = CreateLinkGeneratorContext(
// suppliedValues: new { },
// ambientValues: new { c = "17" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/a/15/17", link);
//}
//[Fact]
//public void GetLink_OptionalParameterAfterDefault_OneValueFromAmbientValues()
//{
// // Arrange
// var endpoint = CreateEndpoint("a/{b=15}/{c?}");
// var linkGenerator = CreateLinkGenerator(CreateEndpointFinder(endpoint));
// var context = CreateLinkGeneratorContext(
// suppliedValues: new { },
// ambientValues: new { c = "17" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.Equal("/a/15/17", link);
//}
//[Fact]
//public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
//{
// // Arrange
// var endpoint = CreateEndpoint(
// template: "a/{b=15}/{c?}/{d?}",
// defaultValues: null,
// handleRequest: true,
// constraints: null);
// var context = CreateLinkGeneratorContext(
// values: new { },
// ambientValues: new { d = "17" });
// // Act
// var link = linkGenerator.GetLink(context);
// // Assert
// Assert.NotNull(pathData);
// Assert.Equal("/a", link);
//
//
//}
private LinkGeneratorContext CreateLinkGeneratorContext(object suppliedValues, object ambientValues = null)
{
var context = new LinkGeneratorContext();
context.SuppliedValues = new RouteValueDictionary(suppliedValues);
context.AmbientValues = new RouteValueDictionary(ambientValues);
return context;
}
private MatcherEndpoint CreateEndpoint(string template, object defaultValues = null)
{
return new MatcherEndpoint(
next => (httpContext) => Task.CompletedTask,
template,
defaultValues,
0,
EndpointMetadataCollection.Empty,
null,
new Address("foo"));
}
private ILinkGenerator CreateLinkGenerator(IEndpointFinder endpointFinder)
{
return new DefaultLinkGenerator(
endpointFinder,
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
Mock.Of<ILogger<DefaultLinkGenerator>>());
}
private DefaultEndpointFinder CreateEndpointFinder(params Endpoint[] endpoints)
{
return new DefaultEndpointFinder(
new CompositeEndpointDataSource(new[] { new DefaultEndpointDataSource(endpoints) }),
NullLogger<DefaultEndpointFinder>.Instance);
}
}
}

View File

@ -24,11 +24,13 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
// Arrange
var defaultEndpoint = new TestEndpoint(
EndpointMetadataCollection.Empty,
"No constraint endpoint");
"No constraint endpoint",
address: null);
var postEndpoint = new TestEndpoint(
new EndpointMetadataCollection(new object[] { new HttpMethodEndpointConstraint(new[] { "POST" }) }),
"POST constraint endpoint");
"POST constraint endpoint",
address: null);
var endpoints = new Endpoint[]
{
@ -60,11 +62,13 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
var defaultEndpoint1 = new TestEndpoint(
EndpointMetadataCollection.Empty,
"Ambiguous1");
"Ambiguous1",
address: null);
var defaultEndpoint2 = new TestEndpoint(
EndpointMetadataCollection.Empty,
"Ambiguous2");
"Ambiguous2",
address: null);
var endpoints = new Endpoint[]
{
@ -96,8 +100,8 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
var actions = new Endpoint[]
{
new TestEndpoint(EndpointMetadataCollection.Empty, "A1"),
new TestEndpoint(EndpointMetadataCollection.Empty, "A2"),
new TestEndpoint(EndpointMetadataCollection.Empty, "A1", address: null),
new TestEndpoint(EndpointMetadataCollection.Empty, "A2", address: null),
};
var selector = CreateSelector(actions, loggerFactory);
@ -120,9 +124,13 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
// Arrange
var actionWithConstraints = new TestEndpoint(
new EndpointMetadataCollection(new[] { new HttpMethodEndpointConstraint(new string[] { "POST" }) }),
"Has constraint");
"Has constraint",
address: null);
var actionWithoutConstraints = new TestEndpoint(EndpointMetadataCollection.Empty, "No constraint");
var actionWithoutConstraints = new TestEndpoint(
EndpointMetadataCollection.Empty,
"No constraint",
address: null);
var actions = new Endpoint[] { actionWithConstraints, actionWithoutConstraints };
@ -140,9 +148,15 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
public void SelectBestCandidate_ConstraintsRejectAll()
{
// Arrange
var action1 = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }), "action1");
var action1 = new TestEndpoint(
new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }),
"action1",
address: null);
var action2 = new TestEndpoint(new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }), "action2");
var action2 = new TestEndpoint(
new EndpointMetadataCollection(new[] { new BooleanConstraint() { Pass = false, } }),
"action2",
address: null);
var actions = new Endpoint[] { action1, action2 };
@ -164,13 +178,17 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
{
new BooleanConstraint() { Pass = false, Order = 0 },
new BooleanConstraint() { Pass = true, Order = 1 },
}), "action1");
}),
"action1",
address: null);
var action2 = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new BooleanConstraint() { Pass = true, Order = 0 },
new BooleanConstraint() { Pass = false, Order = 1 },
}), "action2");
}),
"action2",
address: null);
var actions = new Endpoint[] { action1, action2 };
@ -194,9 +212,14 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
{
Constraint = new BooleanConstraint() { Pass = true },
},
}), "actionWithConstraints");
}),
"actionWithConstraints",
address: null);
var actionWithoutConstraints = new TestEndpoint(EndpointMetadataCollection.Empty, "actionWithoutConstraints");
var actionWithoutConstraints = new TestEndpoint(
EndpointMetadataCollection.Empty,
"actionWithoutConstraints",
address: null);
var actions = new Endpoint[] { actionWithConstraints, actionWithoutConstraints };
@ -214,7 +237,7 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
public void SelectBestCandidate_MultipleCallsNoConstraint_ReturnsEndpoint()
{
// Arrange
var noConstraint = new TestEndpoint(EndpointMetadataCollection.Empty, "noConstraint");
var noConstraint = new TestEndpoint(EndpointMetadataCollection.Empty, "noConstraint", address: null);
var actions = new Endpoint[] { noConstraint };
@ -237,7 +260,9 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
var noConstraint = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new object(),
}), "noConstraint");
}),
"noConstraint",
address: null);
var actions = new Endpoint[] { noConstraint };
@ -260,7 +285,9 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
var nullConstraint = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new ConstraintFactory(),
}), "nullConstraint");
}),
"nullConstraint",
address: null);
var actions = new Endpoint[] { nullConstraint };
@ -284,9 +311,14 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
var actionWithConstraints = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new BooleanConstraintMarker() { Pass = true },
}), "actionWithConstraints");
}),
"actionWithConstraints",
address: null);
var actionWithoutConstraints = new TestEndpoint(EndpointMetadataCollection.Empty, "actionWithoutConstraints");
var actionWithoutConstraints = new TestEndpoint(
EndpointMetadataCollection.Empty,
"actionWithoutConstraints",
address: null);
var actions = new Endpoint[] { actionWithConstraints, actionWithoutConstraints, };
@ -308,12 +340,16 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
var best = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new BooleanConstraint() { Pass = true, Order = 0, },
}), "best");
}),
"best",
address: null);
var worst = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new BooleanConstraint() { Pass = true, Order = 1, },
}), "worst");
}),
"worst",
address: null);
var actions = new Endpoint[] { best, worst };
@ -337,14 +373,18 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
new BooleanConstraint() { Pass = true, Order = 0, },
new BooleanConstraint() { Pass = true, Order = 1, },
new BooleanConstraint() { Pass = true, Order = 2, },
}), "best");
}),
"best",
address: null);
var worst = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new BooleanConstraint() { Pass = true, Order = 0, },
new BooleanConstraint() { Pass = true, Order = 1, },
new BooleanConstraint() { Pass = true, Order = 3, },
}), "worst");
}),
"worst",
address: null);
var actions = new Endpoint[] { best, worst };
@ -367,16 +407,20 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints
new BooleanConstraint() { Pass = true, Order = 0, },
new BooleanConstraint() { Pass = true, Order = 1, },
new BooleanConstraint() { Pass = false, Order = 2, },
}), "nomatch1");
}),
"nomatch1",
address: null);
var nomatch2 = new TestEndpoint(new EndpointMetadataCollection(new[]
{
new BooleanConstraint() { Pass = true, Order = 0, },
new BooleanConstraint() { Pass = true, Order = 1, },
new BooleanConstraint() { Pass = false, Order = 3, },
}), "nomatch2");
}),
"nomatch2",
address: null);
var best = new TestEndpoint(EndpointMetadataCollection.Empty, "best");
var best = new TestEndpoint(EndpointMetadataCollection.Empty, "best", address: null);
var actions = new Endpoint[] { best, nomatch1, nomatch2 };

View File

@ -61,7 +61,9 @@ namespace Microsoft.AspNetCore.Routing.Internal
{
var context = new EndpointConstraintContext();
var endpointSelectorCandidate = new EndpointSelectorCandidate(new TestEndpoint(EndpointMetadataCollection.Empty, string.Empty), new List<IEndpointConstraint> { constraint });
var endpointSelectorCandidate = new EndpointSelectorCandidate(
new TestEndpoint(EndpointMetadataCollection.Empty, string.Empty, address: null),
new List<IEndpointConstraint> { constraint });
context.Candidates = new List<EndpointSelectorCandidate> { endpointSelectorCandidate };
context.CurrentCandidate = context.Candidates[0];

View File

@ -72,7 +72,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers
template,
null,
0,
EndpointMetadataCollection.Empty, "endpoint: " + template);
EndpointMetadataCollection.Empty,
"endpoint: " + template,
address: null);
}
internal (Matcher matcher, MatcherEndpoint endpoint) CreateMatcher(string template)

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
{
private MatcherEndpoint CreateEndpoint(string template, int order, object values = null, EndpointMetadataCollection metadata = null)
{
return new MatcherEndpoint((next) => null, template, values, order, metadata ?? EndpointMetadataCollection.Empty, template);
return new MatcherEndpoint((next) => null, template, values, order, metadata ?? EndpointMetadataCollection.Empty, template, address: null);
}
private TreeMatcher CreateTreeMatcher(EndpointDataSource endpointDataSource)
@ -88,6 +88,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers
// Assert
Assert.Equal(endpointWithConstraint, endpointFeature.Endpoint);
}
}
}
}

View File

@ -5,7 +5,8 @@ namespace Microsoft.AspNetCore.Routing.TestObjects
{
internal class TestEndpoint : Endpoint
{
public TestEndpoint(EndpointMetadataCollection metadata, string displayName) : base(metadata, displayName)
public TestEndpoint(EndpointMetadataCollection metadata, string displayName, Address address)
: base(metadata, displayName, address)
{
}
}

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Routing.TestObjects
{
if (_isHandled)
{
feature.Endpoint = new TestEndpoint(EndpointMetadataCollection.Empty, "Test endpoint");
feature.Endpoint = new TestEndpoint(EndpointMetadataCollection.Empty, "Test endpoint", address: null);
}
return Task.CompletedTask;