diff --git a/benchmarks/Microsoft.AspNetCore.Dispatcher.Performance/DispatcherBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Dispatcher.Performance/DispatcherBenchmark.cs index 56952ddb24..a8232e0b02 100644 --- a/benchmarks/Microsoft.AspNetCore.Dispatcher.Performance/DispatcherBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Dispatcher.Performance/DispatcherBenchmark.cs @@ -2,13 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Tree; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; @@ -30,8 +28,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Performance var treeBuilder = new TreeRouteBuilder( NullLoggerFactory.Instance, - UrlEncoder.Default, - new DefaultObjectPool(new UriBuilderContextPooledObjectPolicy()), + new RoutePatternBinderFactory(UrlEncoder.Default, new DefaultObjectPoolProvider()), new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()))); treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets"), "default", 0); diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/RoutingBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/RoutingBenchmark.cs index 2a9298c66f..fedeb5b2e4 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/RoutingBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/RoutingBenchmark.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -30,8 +29,7 @@ namespace Microsoft.AspNetCore.Routing.Performance var treeBuilder = new TreeRouteBuilder( NullLoggerFactory.Instance, - UrlEncoder.Default, - new DefaultObjectPool(new UriBuilderContextPooledObjectPolicy()), + new RoutePatternBinderFactory(UrlEncoder.Default, new DefaultObjectPoolProvider()), new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()))); treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets"), "default", 0); diff --git a/samples/DispatcherSample/Startup.cs b/samples/DispatcherSample/Startup.cs index da026af1a2..139867abe5 100644 --- a/samples/DispatcherSample/Startup.cs +++ b/samples/DispatcherSample/Startup.cs @@ -21,7 +21,6 @@ namespace DispatcherSample // This is a temporary layering issue, don't worry about :) services.AddRouting(); - services.AddSingleton(); services.AddSingleton(); // Imagine this was done by MVC or another framework. @@ -86,15 +85,16 @@ namespace DispatcherSample public static Task Home_Index(HttpContext httpContext) { - var url = httpContext.RequestServices.GetService(); + var templateFactory = httpContext.RequestServices.GetRequiredService(); + return httpContext.Response.WriteAsync( $"" + $"" + $"

Some links you can visit

" + - $"

Home:Index()

" + - $"

Home:About()

" + - $"

Admin:Index()

" + - $"

Admin:GetUsers()/Admin:EditUsers()

" + + $"

Home:Index()

" + + $"

Home:About()

" + + $"

Admin:Index()

" + + $"

Admin:GetUsers()/Admin:EditUsers()

" + $"" + $""); } diff --git a/src/Microsoft.AspNetCore.Dispatcher.Abstractions/AddressGroup.cs b/src/Microsoft.AspNetCore.Dispatcher.Abstractions/AddressGroup.cs deleted file mode 100644 index 3162e47b9c..0000000000 --- a/src/Microsoft.AspNetCore.Dispatcher.Abstractions/AddressGroup.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.AspNetCore.Dispatcher.Abstractions -{ - class AddressGroup - { - } -} diff --git a/src/Microsoft.AspNetCore.Dispatcher.Abstractions/Template.cs b/src/Microsoft.AspNetCore.Dispatcher.Abstractions/Template.cs new file mode 100644 index 0000000000..94b56d402c --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher.Abstractions/Template.cs @@ -0,0 +1,27 @@ +// 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.Dispatcher +{ + public abstract class Template + { + public virtual string GetUrl() + { + return GetUrl(null, new DispatcherValueCollection()); + } + + public virtual string GetUrl(HttpContext httpContext) + { + return GetUrl(httpContext, new DispatcherValueCollection()); + } + + public virtual string GetUrl(DispatcherValueCollection values) + { + return GetUrl(null, values); + } + + public abstract string GetUrl(HttpContext httpContext, DispatcherValueCollection values); + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/BufferValue.cs b/src/Microsoft.AspNetCore.Dispatcher/BufferValue.cs index 40ad3b6047..c4f0031c96 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/BufferValue.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/BufferValue.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Dispatcher { - public struct BufferValue + internal struct BufferValue { public BufferValue(string value, bool requiresEncoding) { diff --git a/src/Microsoft.AspNetCore.Dispatcher/DefaultTemplateFactory.cs b/src/Microsoft.AspNetCore.Dispatcher/DefaultTemplateFactory.cs new file mode 100644 index 0000000000..e984e08683 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/DefaultTemplateFactory.cs @@ -0,0 +1,49 @@ +// 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; + +namespace Microsoft.AspNetCore.Dispatcher +{ + public class DefaultTemplateFactory : TemplateFactory + { + private readonly ITemplateFactoryComponent[] _components; + + public DefaultTemplateFactory(IEnumerable components) + { + if (components == null) + { + throw new ArgumentNullException(nameof(components)); + } + + _components = components.ToArray(); + } + + public override Template GetTemplateFromKey(TKey key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + for (var i = 0; i < _components.Length; i++) + { + var component = _components[i] as TemplateFactory; + if (component == null) + { + continue; + } + + var template = component.GetTemplate(key); + if (template != null) + { + return template; + } + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs index 45c6c8a18b..58444b4357 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs @@ -25,17 +25,18 @@ namespace Microsoft.Extensions.DependencyInjection // Adds a default dispatcher which will collect all data sources and endpoint selectors from DI. services.TryAddEnumerable(ServiceDescriptor.Transient, DefaultDispatcherConfigureOptions>()); - services.AddSingleton(); - services.AddSingleton(); + // + // Addresses + Templates + // + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); // - // Infrastructure + // Misc Infrastructure // - services.AddSingleton>(s => - { - var provider = s.GetRequiredService(); - return provider.Create(new UriBuilderContextPooledObjectPolicy()); - }); + services.TryAddSingleton(); services.TryAddEnumerable(ServiceDescriptor.Singleton()); diff --git a/src/Microsoft.AspNetCore.Dispatcher/ITemplateAddress.cs b/src/Microsoft.AspNetCore.Dispatcher/ITemplateAddress.cs index dc4697b29e..71fd9e4b1d 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/ITemplateAddress.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/ITemplateAddress.cs @@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Dispatcher { string Template { get; } - DispatcherValueCollection Values { get; } + DispatcherValueCollection Defaults { get; } } } diff --git a/src/Microsoft.AspNetCore.Dispatcher/ITemplateFactoryComponent.cs b/src/Microsoft.AspNetCore.Dispatcher/ITemplateFactoryComponent.cs new file mode 100644 index 0000000000..071f3e1913 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/ITemplateFactoryComponent.cs @@ -0,0 +1,9 @@ +// 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.Dispatcher +{ + public interface ITemplateFactoryComponent + { + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/RoutePatternBinder.cs b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternBinder.cs index 41f6fb8e5a..b0e272d1f3 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/RoutePatternBinder.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternBinder.cs @@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.Dispatcher private readonly DispatcherValueCollection _filters; private readonly RoutePattern _pattern; - public RoutePatternBinder( + internal RoutePatternBinder( UrlEncoder urlEncoder, ObjectPool pool, - RoutePattern template, + RoutePattern pattern, DispatcherValueCollection defaults) { if (urlEncoder == null) @@ -36,14 +36,14 @@ namespace Microsoft.AspNetCore.Dispatcher throw new ArgumentNullException(nameof(pool)); } - if (template == null) + if (pattern == null) { - throw new ArgumentNullException(nameof(template)); + throw new ArgumentNullException(nameof(pattern)); } _urlEncoder = urlEncoder; _pool = pool; - _pattern = template; + _pattern = pattern; _defaults = defaults; // Any default that doesn't have a corresponding parameter is a 'filter' and if a value diff --git a/src/Microsoft.AspNetCore.Dispatcher/RoutePatternBinderFactory.cs b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternBinderFactory.cs new file mode 100644 index 0000000000..51829e6745 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternBinderFactory.cs @@ -0,0 +1,96 @@ +// 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.Text.Encodings.Web; +using Microsoft.AspNetCore.Dispatcher.Patterns; +using Microsoft.Extensions.ObjectPool; + +namespace Microsoft.AspNetCore.Dispatcher +{ + public class RoutePatternBinderFactory + { + private readonly UrlEncoder _encoder; + private readonly ObjectPool _pool; + + public RoutePatternBinderFactory(UrlEncoder encoder, ObjectPoolProvider objectPoolProvider) + { + if (encoder == null) + { + throw new ArgumentNullException(nameof(encoder)); + } + + if (objectPoolProvider == null) + { + throw new ArgumentNullException(nameof(objectPoolProvider)); + } + + _encoder = encoder; + _pool = objectPoolProvider.Create(new UriBuilderContextPooledObjectPolicy()); + } + + public RoutePatternBinder Create(string pattern) + { + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + return Create(RoutePattern.Parse(pattern), new DispatcherValueCollection()); + } + + public RoutePatternBinder Create(string pattern, DispatcherValueCollection defaults) + { + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + if (defaults == null) + { + throw new ArgumentNullException(nameof(defaults)); + } + + return Create(RoutePattern.Parse(pattern), defaults); + } + + public RoutePatternBinder Create(RoutePattern pattern) + { + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + return Create(pattern, new DispatcherValueCollection()); + } + + public RoutePatternBinder Create(RoutePattern pattern, DispatcherValueCollection defaults) + { + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + if (defaults == null) + { + throw new ArgumentNullException(nameof(defaults)); + } + + return new RoutePatternBinder(_encoder, _pool, pattern, defaults); + } + + private class UriBuilderContextPooledObjectPolicy : IPooledObjectPolicy + { + public UriBuildingContext Create() + { + return new UriBuildingContext(); + } + + public bool Return(UriBuildingContext obj) + { + obj.Clear(); + return true; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/RoutePatternTemplate.cs b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternTemplate.cs new file mode 100644 index 0000000000..31327d6c72 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternTemplate.cs @@ -0,0 +1,66 @@ +// 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.Dispatcher +{ + public class RoutePatternTemplate : Template + { + private readonly RoutePatternBinder _binder; + + public RoutePatternTemplate(RoutePatternBinder binder) + { + if (binder == null) + { + throw new ArgumentNullException(nameof(binder)); + } + + _binder = binder; + } + + public override string GetUrl(DispatcherValueCollection values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + return GetUrl(null, values); + } + + public override string GetUrl(HttpContext httpContext, DispatcherValueCollection values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + var ambientValues = GetAmbientValues(httpContext); + var result = _binder.GetValues(ambientValues, values); + if (result.acceptedValues == null) + { + return null; + } + + return _binder.BindValues(result.acceptedValues); + } + + private DispatcherValueCollection GetAmbientValues(HttpContext httpContext) + { + if (httpContext == null) + { + return new DispatcherValueCollection(); + } + + var feature = httpContext.Features.Get(); + if (feature == null) + { + return new DispatcherValueCollection(); + } + + return feature.Values; + } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/RoutePatternTemplateFactory.cs b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternTemplateFactory.cs new file mode 100644 index 0000000000..4372cb4726 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/RoutePatternTemplateFactory.cs @@ -0,0 +1,51 @@ +// 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; + +namespace Microsoft.AspNetCore.Dispatcher +{ + internal class RoutePatternTemplateFactory : TemplateFactory + { + private readonly TemplateAddressSelector _selector; + private readonly RoutePatternBinderFactory _binderFactory; + + public RoutePatternTemplateFactory(TemplateAddressSelector selector, RoutePatternBinderFactory binderFactory) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (binderFactory == null) + { + throw new ArgumentNullException(nameof(binderFactory)); + } + + _selector = selector; + _binderFactory = binderFactory; + } + + public override Template GetTemplate(DispatcherValueCollection key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var address = _selector.SelectAddress(key); + if (address == null) + { + return null; + } + + if (address is ITemplateAddress templateAddress) + { + var binder = _binderFactory.Create(templateAddress.Template, templateAddress.Defaults); + return new RoutePatternTemplate(binder); + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/SegmentState.cs b/src/Microsoft.AspNetCore.Dispatcher/SegmentState.cs index 9aa4469a27..19843b71bd 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/SegmentState.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/SegmentState.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Dispatcher // used a value for {p1}, we have to output the entire segment up to the next "/". // Otherwise we could end up with the partial segment "v1" instead of the entire // segment "v1-v2.xml". - public enum SegmentState + internal enum SegmentState { Beginning, Inside, diff --git a/src/Microsoft.AspNetCore.Dispatcher/TemplateAddress.cs b/src/Microsoft.AspNetCore.Dispatcher/TemplateAddress.cs index 606f9997f2..3053d0d936 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/TemplateAddress.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/TemplateAddress.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Dispatcher } Template = template; - Values = new DispatcherValueCollection(values); + Defaults = new DispatcherValueCollection(values); DisplayName = displayName; Metadata = metadata.ToArray(); } @@ -38,6 +38,6 @@ namespace Microsoft.AspNetCore.Dispatcher public string Template { get; } - public DispatcherValueCollection Values { get; } + public DispatcherValueCollection Defaults { get; } } } diff --git a/src/Microsoft.AspNetCore.Dispatcher/TemplateAddressSelector.cs b/src/Microsoft.AspNetCore.Dispatcher/TemplateAddressSelector.cs index efb405f6bf..4ce2d6a43f 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/TemplateAddressSelector.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/TemplateAddressSelector.cs @@ -21,6 +21,11 @@ namespace Microsoft.AspNetCore.Dispatcher _addressTable = addressTable; } + public Address SelectAddress(object values) + { + return SelectAddress(new DispatcherValueCollection(values)); + } + public Address SelectAddress(DispatcherValueCollection values) { if (values == null) @@ -69,7 +74,7 @@ namespace Microsoft.AspNetCore.Dispatcher private bool IsMatch(ITemplateAddress address, DispatcherValueCollection values) { - foreach (var kvp in address.Values) + foreach (var kvp in address.Defaults) { values.TryGetValue(kvp.Key, out var value); diff --git a/src/Microsoft.AspNetCore.Dispatcher/TemplateFactory.cs b/src/Microsoft.AspNetCore.Dispatcher/TemplateFactory.cs new file mode 100644 index 0000000000..5fdbabc93b --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/TemplateFactory.cs @@ -0,0 +1,15 @@ +// 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.Dispatcher +{ + public abstract class TemplateFactory + { + public Template GetTemplate(object values) + { + return GetTemplateFromKey(new DispatcherValueCollection(values)); + } + + public abstract Template GetTemplateFromKey(TKey key) where TKey : class; + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/TemplateFactoryOfT.cs b/src/Microsoft.AspNetCore.Dispatcher/TemplateFactoryOfT.cs new file mode 100644 index 0000000000..10ddc0cf1a --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/TemplateFactoryOfT.cs @@ -0,0 +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. + +namespace Microsoft.AspNetCore.Dispatcher +{ + public abstract class TemplateFactory : ITemplateFactoryComponent + { + public abstract Template GetTemplate(TKey key); + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/UriBuilderContextPooledObjectPolicy.cs b/src/Microsoft.AspNetCore.Dispatcher/UriBuilderContextPooledObjectPolicy.cs deleted file mode 100644 index 0e44bcb3f8..0000000000 --- a/src/Microsoft.AspNetCore.Dispatcher/UriBuilderContextPooledObjectPolicy.cs +++ /dev/null @@ -1,21 +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.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Dispatcher -{ - public class UriBuilderContextPooledObjectPolicy : IPooledObjectPolicy - { - public UriBuildingContext Create() - { - return new UriBuildingContext(); - } - - public bool Return(UriBuildingContext obj) - { - obj.Clear(); - return true; - } - } -} diff --git a/src/Microsoft.AspNetCore.Dispatcher/UriBuildingContext.cs b/src/Microsoft.AspNetCore.Dispatcher/UriBuildingContext.cs index d9b58a88fa..55ebc8b304 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/UriBuildingContext.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/UriBuildingContext.cs @@ -10,7 +10,7 @@ using System.Text.Encodings.Web; namespace Microsoft.AspNetCore.Dispatcher { [DebuggerDisplay("{DebuggerToString(),nq}")] - public class UriBuildingContext + internal class UriBuildingContext { // Holds the 'accepted' parts of the uri. private readonly StringBuilder _uri; diff --git a/src/Microsoft.AspNetCore.Routing/Dispatcher/RouteTemplateUrlGenerator.cs b/src/Microsoft.AspNetCore.Routing/Dispatcher/RouteTemplateUrlGenerator.cs deleted file mode 100644 index 7f4e631e5f..0000000000 --- a/src/Microsoft.AspNetCore.Routing/Dispatcher/RouteTemplateUrlGenerator.cs +++ /dev/null @@ -1,58 +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.Text.Encodings.Web; -using Microsoft.AspNetCore.Dispatcher; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing.Internal; -using Microsoft.AspNetCore.Routing.Template; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Routing.Dispatcher -{ - // This isn't a proposed design, just a placeholder to demonstrate that things are wired up correctly. - public class RouteTemplateUrlGenerator - { - private readonly TemplateAddressSelector _addressSelector; - private readonly ObjectPool _pool; - private readonly UrlEncoder _urlEncoder; - - public RouteTemplateUrlGenerator(TemplateAddressSelector addressSelector, UrlEncoder urlEncoder, ObjectPool pool) - { - _addressSelector = addressSelector; - _urlEncoder = urlEncoder; - _pool = pool; - } - - public string GenerateUrl(HttpContext httpContext, object values) - { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (values == null) - { - throw new ArgumentNullException(nameof(values)); - } - - var address = _addressSelector.SelectAddress(new DispatcherValueCollection(values)) as ITemplateAddress; - if (address == null) - { - throw new InvalidOperationException("Can't find address"); - } - - var binder = new TemplateBinder(_urlEncoder, _pool, Template.TemplateParser.Parse(address.Template), new RouteValueDictionary()); - - var feature = httpContext.Features.Get(); - var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values)); - if (result == null) - { - return null; - } - - return binder.BindValues(result.AcceptedValues); - } - } -} diff --git a/src/Microsoft.AspNetCore.Routing/RouteBase.cs b/src/Microsoft.AspNetCore.Routing/RouteBase.cs index 9e4eaf530e..90ef1b05e9 100644 --- a/src/Microsoft.AspNetCore.Routing/RouteBase.cs +++ b/src/Microsoft.AspNetCore.Routing/RouteBase.cs @@ -242,9 +242,8 @@ namespace Microsoft.AspNetCore.Routing { if (_binder == null) { - var urlEncoder = context.RequestServices.GetRequiredService(); - var pool = context.RequestServices.GetRequiredService>(); - _binder = new TemplateBinder(urlEncoder, pool, ParsedTemplate, Defaults); + var binderFactory = context.RequestServices.GetRequiredService(); + _binder = new TemplateBinder(binderFactory.Create(ParsedTemplate.ToRoutePattern(), Defaults)); } } diff --git a/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs b/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs index b20f461aa5..48364b352b 100644 --- a/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs +++ b/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Text.Encodings.Web; using Microsoft.AspNetCore.Dispatcher; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Routing.Template { @@ -12,28 +10,14 @@ namespace Microsoft.AspNetCore.Routing.Template { private readonly RoutePatternBinder _binder; - public TemplateBinder( - UrlEncoder urlEncoder, - ObjectPool pool, - RouteTemplate template, - RouteValueDictionary defaults) + public TemplateBinder(RoutePatternBinder binder) { - if (urlEncoder == null) + if (binder == null) { - throw new ArgumentNullException(nameof(urlEncoder)); + throw new ArgumentNullException(nameof(binder)); } - if (pool == null) - { - throw new ArgumentNullException(nameof(pool)); - } - - if (template == null) - { - throw new ArgumentNullException(nameof(template)); - } - - _binder = new RoutePatternBinder(urlEncoder, pool, template.ToRoutePattern(), defaults); + _binder = binder; } // Step 1: Get the list of values we're going to try to use to match and generate this URI diff --git a/src/Microsoft.AspNetCore.Routing/Tree/TreeRouteBuilder.cs b/src/Microsoft.AspNetCore.Routing/Tree/TreeRouteBuilder.cs index 622f099604..f44aebf160 100644 --- a/src/Microsoft.AspNetCore.Routing/Tree/TreeRouteBuilder.cs +++ b/src/Microsoft.AspNetCore.Routing/Tree/TreeRouteBuilder.cs @@ -20,21 +20,12 @@ namespace Microsoft.AspNetCore.Routing.Tree { private readonly ILogger _logger; private readonly ILogger _constraintLogger; - private readonly UrlEncoder _urlEncoder; - private readonly ObjectPool _objectPool; + private readonly RoutePatternBinderFactory _binderFactory; private readonly IInlineConstraintResolver _constraintResolver; - /// - /// Initializes a new instance of . - /// - /// The . - /// The . - /// The . - /// The . public TreeRouteBuilder( ILoggerFactory loggerFactory, - UrlEncoder urlEncoder, - ObjectPool objectPool, + RoutePatternBinderFactory binderFactory, IInlineConstraintResolver constraintResolver) { if (loggerFactory == null) @@ -42,14 +33,9 @@ namespace Microsoft.AspNetCore.Routing.Tree throw new ArgumentNullException(nameof(loggerFactory)); } - if (urlEncoder == null) + if (binderFactory == null) { - throw new ArgumentNullException(nameof(urlEncoder)); - } - - if (objectPool == null) - { - throw new ArgumentNullException(nameof(objectPool)); + throw new ArgumentNullException(nameof(binderFactory)); } if (constraintResolver == null) @@ -57,8 +43,7 @@ namespace Microsoft.AspNetCore.Routing.Tree throw new ArgumentNullException(nameof(constraintResolver)); } - _urlEncoder = urlEncoder; - _objectPool = objectPool; + _binderFactory = binderFactory; _constraintResolver = constraintResolver; _logger = loggerFactory.CreateLogger(); @@ -251,8 +236,7 @@ namespace Microsoft.AspNetCore.Routing.Tree return new TreeRouter( trees.Values.OrderBy(tree => tree.Order).ToArray(), OutboundEntries, - _urlEncoder, - _objectPool, + _binderFactory, _logger, _constraintLogger, version); diff --git a/src/Microsoft.AspNetCore.Routing/Tree/TreeRouter.cs b/src/Microsoft.AspNetCore.Routing/Tree/TreeRouter.cs index 5f9bc989ed..e7ab54341e 100644 --- a/src/Microsoft.AspNetCore.Routing/Tree/TreeRouter.cs +++ b/src/Microsoft.AspNetCore.Routing/Tree/TreeRouter.cs @@ -33,22 +33,10 @@ namespace Microsoft.AspNetCore.Routing.Tree private readonly ILogger _logger; private readonly ILogger _constraintLogger; - /// - /// Creates a new . - /// - /// The list of that contains the route entries. - /// The set of . - /// The . - /// The . - /// The instance. - /// The instance used - /// in . - /// The version of this route. public TreeRouter( UrlMatchingTree[] trees, IEnumerable linkGenerationEntries, - UrlEncoder urlEncoder, - ObjectPool objectPool, + RoutePatternBinderFactory binderFactory, ILogger routeLogger, ILogger constraintLogger, int version) @@ -63,14 +51,9 @@ namespace Microsoft.AspNetCore.Routing.Tree throw new ArgumentNullException(nameof(linkGenerationEntries)); } - if (urlEncoder == null) + if (binderFactory == null) { - throw new ArgumentNullException(nameof(urlEncoder)); - } - - if (objectPool == null) - { - throw new ArgumentNullException(nameof(objectPool)); + throw new ArgumentNullException(nameof(binderFactory)); } if (routeLogger == null) @@ -93,8 +76,7 @@ namespace Microsoft.AspNetCore.Routing.Tree foreach (var entry in linkGenerationEntries) { - - var binder = new TemplateBinder(urlEncoder, objectPool, entry.RouteTemplate, entry.Defaults); + var binder = new TemplateBinder(binderFactory.Create(entry.RouteTemplate.ToRoutePattern(), entry.Defaults)); var outboundMatch = new OutboundMatch() { Entry = entry, TemplateBinder = binder }; outboundMatches.Add(outboundMatch); diff --git a/test/Microsoft.AspNetCore.Dispatcher.FunctionalTest/ApiAppStartup.cs b/test/Microsoft.AspNetCore.Dispatcher.FunctionalTest/ApiAppStartup.cs index 1b3d707636..10fd842549 100644 --- a/test/Microsoft.AspNetCore.Dispatcher.FunctionalTest/ApiAppStartup.cs +++ b/test/Microsoft.AspNetCore.Dispatcher.FunctionalTest/ApiAppStartup.cs @@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest // This is a temporary layering issue, don't worry about it :) services.AddRouting(); - services.AddSingleton(); services.AddSingleton(); services.Configure(ConfigureDispatcher); diff --git a/test/Microsoft.AspNetCore.Dispatcher.Test/DefaultTemplateFactoryTest.cs b/test/Microsoft.AspNetCore.Dispatcher.Test/DefaultTemplateFactoryTest.cs new file mode 100644 index 0000000000..90ab9ad210 --- /dev/null +++ b/test/Microsoft.AspNetCore.Dispatcher.Test/DefaultTemplateFactoryTest.cs @@ -0,0 +1,64 @@ +// 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 Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Dispatcher +{ + public class DefaultTemplateFactoryTest + { + [Fact] + public void GetTemplateFromKey_UsesMatchingComponent_SelectsTemplate() + { + // Arrange + var expected = Mock.Of