Add Template abstraction
This change adds the Template as a top level abstraction. URL templating is now a two-stage process. First you use a 'key' to look up a Template, then you use the Template to create the URL. This change also has some cleanup of the way RoutePatternBinder gets instantiated. I added a factory service so that most of the complex things can be made internal to Dispatcher. Now it's much easier to constuct and use. These impacts some pubternal APIs that we already broke, but makes them actually nice :) Also cleaned up some tests and fixed one that was broken and not running.
This commit is contained in:
parent
2d661396df
commit
736b49294d
|
|
@ -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<UriBuildingContext>(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);
|
||||
|
|
|
|||
|
|
@ -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<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
new RoutePatternBinderFactory(UrlEncoder.Default, new DefaultObjectPoolProvider()),
|
||||
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())));
|
||||
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets"), "default", 0);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ namespace DispatcherSample
|
|||
|
||||
// This is a temporary layering issue, don't worry about :)
|
||||
services.AddRouting();
|
||||
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||
services.AddSingleton<IDefaultMatcherFactory, TreeMatcherFactory>();
|
||||
|
||||
// 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<RouteTemplateUrlGenerator>();
|
||||
var templateFactory = httpContext.RequestServices.GetRequiredService<TemplateFactory>();
|
||||
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<h1>Some links you can visit</h1>" +
|
||||
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Home", action = "Index", })}\">Home:Index()</a></p>" +
|
||||
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Home", action = "About", })}\">Home:About()</a></p>" +
|
||||
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Admin", action = "Index", })}\">Admin:Index()</a></p>" +
|
||||
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Admin", action = "Users", })}\">Admin:GetUsers()/Admin:EditUsers()</a></p>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Home", action = "Index", }).GetUrl(httpContext)}\">Home:Index()</a></p>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Home", action = "About", }).GetUrl(httpContext)}\">Home:About()</a></p>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Admin", action = "Index", }).GetUrl(httpContext)}\">Admin:Index()</a></p>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Admin", action = "Users", }).GetUrl(httpContext)}\">Admin:GetUsers()/Admin:EditUsers()</a></p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Abstractions
|
||||
{
|
||||
class AddressGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public struct BufferValue
|
||||
internal struct BufferValue
|
||||
{
|
||||
public BufferValue(string value, bool requiresEncoding)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<ITemplateFactoryComponent> components)
|
||||
{
|
||||
if (components == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(components));
|
||||
}
|
||||
|
||||
_components = components.ToArray();
|
||||
}
|
||||
|
||||
public override Template GetTemplateFromKey<TKey>(TKey key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
for (var i = 0; i < _components.Length; i++)
|
||||
{
|
||||
var component = _components[i] as TemplateFactory<TKey>;
|
||||
if (component == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var template = component.GetTemplate(key);
|
||||
if (template != null)
|
||||
{
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IConfigureOptions<DispatcherOptions>, DefaultDispatcherConfigureOptions>());
|
||||
|
||||
services.AddSingleton<AddressTable, DefaultAddressTable>();
|
||||
services.AddSingleton<TemplateAddressSelector>();
|
||||
//
|
||||
// Addresses + Templates
|
||||
//
|
||||
services.TryAddSingleton<AddressTable, DefaultAddressTable>();
|
||||
services.TryAddSingleton<TemplateFactory, DefaultTemplateFactory>();
|
||||
services.TryAddSingleton<ITemplateFactoryComponent, RoutePatternTemplateFactory>();
|
||||
services.TryAddSingleton<TemplateAddressSelector>();
|
||||
|
||||
//
|
||||
// Infrastructure
|
||||
// Misc Infrastructure
|
||||
//
|
||||
services.AddSingleton<ObjectPool<UriBuildingContext>>(s =>
|
||||
{
|
||||
var provider = s.GetRequiredService<ObjectPoolProvider>();
|
||||
return provider.Create<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy());
|
||||
});
|
||||
services.TryAddSingleton<RoutePatternBinderFactory>();
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHandlerFactory, TemplateEndpointHandlerFactory>());
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
string Template { get; }
|
||||
|
||||
DispatcherValueCollection Values { get; }
|
||||
DispatcherValueCollection Defaults { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
private readonly DispatcherValueCollection _filters;
|
||||
private readonly RoutePattern _pattern;
|
||||
|
||||
public RoutePatternBinder(
|
||||
internal RoutePatternBinder(
|
||||
UrlEncoder urlEncoder,
|
||||
ObjectPool<UriBuildingContext> 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
|
||||
|
|
|
|||
|
|
@ -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<UriBuildingContext> _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<UriBuildingContext>
|
||||
{
|
||||
public UriBuildingContext Create()
|
||||
{
|
||||
return new UriBuildingContext();
|
||||
}
|
||||
|
||||
public bool Return(UriBuildingContext obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IDispatcherFeature>();
|
||||
if (feature == null)
|
||||
{
|
||||
return new DispatcherValueCollection();
|
||||
}
|
||||
|
||||
return feature.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DispatcherValueCollection>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>(TKey key) where TKey : class;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TKey> : ITemplateFactoryComponent
|
||||
{
|
||||
public abstract Template GetTemplate(TKey key);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UriBuildingContext>
|
||||
{
|
||||
public UriBuildingContext Create()
|
||||
{
|
||||
return new UriBuildingContext();
|
||||
}
|
||||
|
||||
public bool Return(UriBuildingContext obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<UriBuildingContext> _pool;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
|
||||
public RouteTemplateUrlGenerator(TemplateAddressSelector addressSelector, UrlEncoder urlEncoder, ObjectPool<UriBuildingContext> 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<IDispatcherFeature>();
|
||||
var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values));
|
||||
if (result == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return binder.BindValues(result.AcceptedValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,9 +242,8 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
if (_binder == null)
|
||||
{
|
||||
var urlEncoder = context.RequestServices.GetRequiredService<UrlEncoder>();
|
||||
var pool = context.RequestServices.GetRequiredService<ObjectPool<UriBuildingContext>>();
|
||||
_binder = new TemplateBinder(urlEncoder, pool, ParsedTemplate, Defaults);
|
||||
var binderFactory = context.RequestServices.GetRequiredService<RoutePatternBinderFactory>();
|
||||
_binder = new TemplateBinder(binderFactory.Create(ParsedTemplate.ToRoutePattern(), Defaults));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<UriBuildingContext> 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
|
||||
|
|
|
|||
|
|
@ -20,21 +20,12 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
|||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger _constraintLogger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
private readonly ObjectPool<UriBuildingContext> _objectPool;
|
||||
private readonly RoutePatternBinderFactory _binderFactory;
|
||||
private readonly IInlineConstraintResolver _constraintResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="TreeRouteBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
/// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
|
||||
/// <param name="objectPool">The <see cref="ObjectPool{UrlBuildingContext}"/>.</param>
|
||||
/// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
|
||||
public TreeRouteBuilder(
|
||||
ILoggerFactory loggerFactory,
|
||||
UrlEncoder urlEncoder,
|
||||
ObjectPool<UriBuildingContext> 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<TreeRouter>();
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -33,22 +33,10 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
|||
private readonly ILogger _logger;
|
||||
private readonly ILogger _constraintLogger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="TreeRouter"/>.
|
||||
/// </summary>
|
||||
/// <param name="trees">The list of <see cref="UrlMatchingTree"/> that contains the route entries.</param>
|
||||
/// <param name="linkGenerationEntries">The set of <see cref="OutboundRouteEntry"/>.</param>
|
||||
/// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
|
||||
/// <param name="objectPool">The <see cref="ObjectPool{T}"/>.</param>
|
||||
/// <param name="routeLogger">The <see cref="ILogger"/> instance.</param>
|
||||
/// <param name="constraintLogger">The <see cref="ILogger"/> instance used
|
||||
/// in <see cref="RouteConstraintMatcher"/>.</param>
|
||||
/// <param name="version">The version of this route.</param>
|
||||
public TreeRouter(
|
||||
UrlMatchingTree[] trees,
|
||||
IEnumerable<OutboundRouteEntry> linkGenerationEntries,
|
||||
UrlEncoder urlEncoder,
|
||||
ObjectPool<UriBuildingContext> 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
|
|||
|
||||
// This is a temporary layering issue, don't worry about it :)
|
||||
services.AddRouting();
|
||||
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||
services.AddSingleton<IDefaultMatcherFactory, TreeMatcherFactory>();
|
||||
|
||||
services.Configure<DispatcherOptions>(ConfigureDispatcher);
|
||||
|
|
|
|||
|
|
@ -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<Template>();
|
||||
var factory = new DefaultTemplateFactory(new ITemplateFactoryComponent[]
|
||||
{
|
||||
Mock.Of<TemplateFactory<string>>(f => f.GetTemplate("foo") == expected),
|
||||
});
|
||||
|
||||
// Act
|
||||
var template = factory.GetTemplateFromKey("foo");
|
||||
|
||||
// Assert
|
||||
Assert.Same(expected, template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTemplateFromKey_UsesMatchingComponent_IgnoresOtherComponents()
|
||||
{
|
||||
// Arrange
|
||||
var expected = Mock.Of<Template>();
|
||||
var factory = new DefaultTemplateFactory(new ITemplateFactoryComponent[]
|
||||
{
|
||||
Mock.Of<TemplateFactory<int>>(f => f.GetTemplate(17) == Mock.Of<Template>()),
|
||||
Mock.Of<TemplateFactory<string>>(f => f.GetTemplate("foo") == expected),
|
||||
});
|
||||
|
||||
// Act
|
||||
var template = factory.GetTemplateFromKey("foo");
|
||||
|
||||
// Assert
|
||||
Assert.Same(expected, template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTemplateFromKey_UsesMatchingComponent_ReturnsFirstMatch()
|
||||
{
|
||||
// Arrange
|
||||
var expected = Mock.Of<Template>();
|
||||
var factory = new DefaultTemplateFactory(new ITemplateFactoryComponent[]
|
||||
{
|
||||
Mock.Of<TemplateFactory<string>>(), // Will return null
|
||||
Mock.Of<TemplateFactory<string>>(f => f.GetTemplate("foo") == expected),
|
||||
});
|
||||
|
||||
// Act
|
||||
var template = factory.GetTemplateFromKey("foo");
|
||||
|
||||
// Assert
|
||||
Assert.Same(expected, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,16 +6,21 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternBinderTests
|
||||
public class RoutePatternBinderTest
|
||||
{
|
||||
public RoutePatternBinderTest()
|
||||
{
|
||||
BinderFactory = new RoutePatternBinderFactory(new UrlTestEncoder(), new DefaultObjectPoolProvider());
|
||||
}
|
||||
|
||||
public RoutePatternBinderFactory BinderFactory { get; }
|
||||
|
||||
public static TheoryData EmptyAndNullDefaultValues =>
|
||||
new TheoryData<string, DispatcherValueCollection, DispatcherValueCollection, string>
|
||||
{
|
||||
|
|
@ -114,12 +119,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new RoutePatternBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
RoutePattern.Parse(pattern),
|
||||
defaults);
|
||||
var binder = BinderFactory.Create(pattern, defaults);
|
||||
|
||||
// Act & Assert
|
||||
(var acceptedValues, var combinedValues) = binder.GetValues(ambientValues: null, values: values);
|
||||
|
|
@ -264,12 +264,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new RoutePatternBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
RoutePattern.Parse(pattern),
|
||||
defaults);
|
||||
var binder = BinderFactory.Create(pattern, defaults);
|
||||
|
||||
// Act & Assert
|
||||
(var acceptedValues, var combinedValues) = binder.GetValues(ambientValues: ambientValues, values: values);
|
||||
|
|
@ -695,11 +690,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
// Arrange
|
||||
var pattern = "{area?}/{controller=Home}/{action=Index}/{id?}";
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new RoutePatternBinder(
|
||||
new UrlTestEncoder(),
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
RoutePattern.Parse(pattern),
|
||||
defaults: null);
|
||||
var binder = BinderFactory.Create(pattern);
|
||||
var ambientValues = new DispatcherValueCollection();
|
||||
var routeValues = new DispatcherValueCollection(new { controller = "Test", action = "Index" });
|
||||
|
||||
|
|
@ -1116,7 +1107,41 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
#endif
|
||||
|
||||
private static void RunTest(
|
||||
[Theory]
|
||||
[InlineData(null, null, true)]
|
||||
[InlineData("blog", null, false)]
|
||||
[InlineData(null, "store", false)]
|
||||
[InlineData("Cool", "cool", true)]
|
||||
[InlineData("Co0l", "cool", false)]
|
||||
public void RoutePartsEqualTest(object left, object right, bool expected)
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
if (expected)
|
||||
{
|
||||
Assert.True(RoutePatternBinder.RoutePartsEqual(left, right));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(RoutePatternBinder.RoutePartsEqual(left, right));
|
||||
}
|
||||
}
|
||||
|
||||
private void RunTest(
|
||||
string pattern,
|
||||
object defaults,
|
||||
object ambientValues,
|
||||
object values,
|
||||
string expected)
|
||||
{
|
||||
RunTest(
|
||||
pattern,
|
||||
new DispatcherValueCollection(defaults),
|
||||
new DispatcherValueCollection(ambientValues),
|
||||
new DispatcherValueCollection(values),
|
||||
expected);
|
||||
}
|
||||
|
||||
private void RunTest(
|
||||
string pattern,
|
||||
DispatcherValueCollection defaults,
|
||||
DispatcherValueCollection ambientValues,
|
||||
|
|
@ -1125,16 +1150,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
UrlEncoder encoder = null)
|
||||
{
|
||||
// Arrange
|
||||
encoder = encoder ?? new UrlTestEncoder();
|
||||
|
||||
var binder = new RoutePatternBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
RoutePattern.Parse(pattern),
|
||||
defaults);
|
||||
var binderFactory = encoder == null ? BinderFactory : new RoutePatternBinderFactory(encoder, new DefaultObjectPoolProvider());
|
||||
var binder = binderFactory.Create(pattern, defaults ?? new DispatcherValueCollection());
|
||||
|
||||
// Act & Assert
|
||||
(var acceptedValues, var combinedValues) = binder.GetValues(ambientValues, values);
|
||||
(var acceptedValues, var combinedValues) = binder.GetValues(ambientValues, values);
|
||||
if (acceptedValues == null)
|
||||
{
|
||||
if (expected == null)
|
||||
|
|
@ -1180,40 +1200,6 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
}
|
||||
}
|
||||
|
||||
private static void RunTest(
|
||||
string pattern,
|
||||
object defaults,
|
||||
object ambientValues,
|
||||
object values,
|
||||
string expected)
|
||||
{
|
||||
RunTest(
|
||||
pattern,
|
||||
new DispatcherValueCollection(defaults),
|
||||
new DispatcherValueCollection(ambientValues),
|
||||
new DispatcherValueCollection(values),
|
||||
expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, null, true)]
|
||||
[InlineData("blog", null, false)]
|
||||
[InlineData(null, "store", false)]
|
||||
[InlineData("Cool", "cool", true)]
|
||||
[InlineData("Co0l", "cool", false)]
|
||||
public void RoutePartsEqualTest(object left, object right, bool expected)
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
if (expected)
|
||||
{
|
||||
Assert.True(RoutePatternBinder.RoutePartsEqual(left, right));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(RoutePatternBinder.RoutePartsEqual(left, right));
|
||||
}
|
||||
}
|
||||
|
||||
private class PathAndQuery
|
||||
{
|
||||
public PathAndQuery(string uri)
|
||||
|
|
@ -8,7 +8,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternMatcherTests
|
||||
public class RoutePatternMatcherTest
|
||||
{
|
||||
[Fact]
|
||||
public void TryMatch_Success()
|
||||
|
|
@ -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.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
// Not getting too in-depth with the tests here, the core URL generation is already tested elsewhere
|
||||
public class RoutePatternTemplateTest
|
||||
{
|
||||
public RoutePatternTemplateTest()
|
||||
{
|
||||
BinderFactory = new RoutePatternBinderFactory(UrlEncoder.Default, new DefaultObjectPoolProvider());
|
||||
}
|
||||
|
||||
public RoutePatternBinderFactory BinderFactory { get; }
|
||||
|
||||
[Fact]
|
||||
public void GetUrl_WithAllRequiredValues_GeneratesUrl()
|
||||
{
|
||||
// Arrange
|
||||
var template = new RoutePatternTemplate(BinderFactory.Create("api/products/{id}"));
|
||||
|
||||
// Act
|
||||
var url = template.GetUrl(new DispatcherValueCollection(new { id = 17 }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/api/products/17", url);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUrl_WithoutAllRequiredValues_GeneratesUrl()
|
||||
{
|
||||
// Arrange
|
||||
var template = new RoutePatternTemplate(BinderFactory.Create("api/products/{id}"));
|
||||
|
||||
// Act
|
||||
var url = template.GetUrl(new DispatcherValueCollection(new { name = "billy" }));
|
||||
|
||||
// Assert
|
||||
Assert.Null(url);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUrl_WithAmbientValues_GeneratesUrl()
|
||||
{
|
||||
// Arrange
|
||||
var template = new RoutePatternTemplate(BinderFactory.Create("api/products/{id}/{name}"));
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Features.Set<IDispatcherFeature>(new DispatcherFeature()
|
||||
{
|
||||
Values = new DispatcherValueCollection(new { id = 17 }),
|
||||
});
|
||||
|
||||
// Act
|
||||
var url = template.GetUrl(httpContext, new DispatcherValueCollection(new { name = "billy" }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/api/products/17/billy", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -10,18 +9,6 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
{
|
||||
public class DateTimeRouteConstraintTests
|
||||
{
|
||||
public static IEnumerable<object[]> GetDateTimeObject
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
DateTime.Now,
|
||||
true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("12/25/2009", true)]
|
||||
[InlineData("25/12/2009 11:45:00 PM", false)]
|
||||
|
|
@ -36,9 +23,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
[InlineData("12/25/2009 11:45:00 PM", true)]
|
||||
[InlineData("2009-05-12T11:45:00Z", true)]
|
||||
[InlineData("not-parseable-as-date", false)]
|
||||
[InlineData(false, false)]
|
||||
[MemberData(nameof(GetDateTimeObject))]
|
||||
public void DateTimeRouteConstraint(object parameterValue, bool expected)
|
||||
public void DateTimeRouteConstraint_ParsesStrings(string parameterValue, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new DateTimeRouteConstraint();
|
||||
|
|
@ -49,5 +34,31 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
// Assert
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeRouteConstraint_AcceptsDateTimeObjects_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new DateTimeRouteConstraint();
|
||||
|
||||
// Act
|
||||
var actual = ConstraintsTestHelper.TestConstraint(constraint, DateTime.Now);
|
||||
|
||||
// Assert
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeRouteConstraint_IgnoresOtherTypes_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new DateTimeRouteConstraint();
|
||||
|
||||
// Act
|
||||
var actual = ConstraintsTestHelper.TestConstraint(constraint, false);
|
||||
|
||||
// Assert
|
||||
Assert.False(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -17,7 +16,12 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
{
|
||||
public class TemplateBinderTests
|
||||
{
|
||||
private readonly IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
|
||||
public TemplateBinderTests()
|
||||
{
|
||||
BinderFactory = new RoutePatternBinderFactory(new UrlTestEncoder(), new DefaultObjectPoolProvider());
|
||||
}
|
||||
|
||||
public RoutePatternBinderFactory BinderFactory { get; }
|
||||
|
||||
public static TheoryData EmptyAndNullDefaultValues =>
|
||||
new TheoryData<string, RouteValueDictionary, RouteValueDictionary, string>
|
||||
|
|
@ -117,12 +121,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
var binder = new TemplateBinder(BinderFactory.Create(template, defaults));
|
||||
|
||||
// Act & Assert
|
||||
var result = binder.GetValues(ambientValues: null, values: values);
|
||||
|
|
@ -267,12 +266,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
var binder = new TemplateBinder(BinderFactory.Create(template, defaults));
|
||||
|
||||
// Act & Assert
|
||||
var result = binder.GetValues(ambientValues: ambientValues, values: values);
|
||||
|
|
@ -697,12 +691,8 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
{
|
||||
// Arrange
|
||||
var template = "{area?}/{controller=Home}/{action=Index}/{id?}";
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
new UrlTestEncoder(),
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults: null);
|
||||
var binder = new TemplateBinder(BinderFactory.Create(template));
|
||||
|
||||
var ambientValues = new RouteValueDictionary();
|
||||
var routeValues = new RouteValueDictionary(new { controller = "Test", action = "Index" });
|
||||
|
||||
|
|
@ -1119,7 +1109,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
|
||||
#endif
|
||||
|
||||
private static void RunTest(
|
||||
private void RunTest(
|
||||
string template,
|
||||
RouteValueDictionary defaults,
|
||||
RouteValueDictionary ambientValues,
|
||||
|
|
@ -1128,13 +1118,8 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
UrlEncoder encoder = null)
|
||||
{
|
||||
// Arrange
|
||||
encoder = encoder ?? new UrlTestEncoder();
|
||||
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
var binderFactory = encoder == null ? BinderFactory : new RoutePatternBinderFactory(encoder, new DefaultObjectPoolProvider());
|
||||
var binder = new TemplateBinder(binderFactory.Create(template, defaults ?? new RouteValueDictionary()));
|
||||
|
||||
// Act & Assert
|
||||
var result = binder.GetValues(ambientValues, values);
|
||||
|
|
@ -1183,7 +1168,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private static void RunTest(
|
||||
private void RunTest(
|
||||
string template,
|
||||
object defaults,
|
||||
object ambientValues,
|
||||
|
|
|
|||
|
|
@ -243,15 +243,10 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
|||
|
||||
private static TreeRouteBuilder CreateBuilder()
|
||||
{
|
||||
var objectPoolProvider = new DefaultObjectPoolProvider();
|
||||
var objectPolicy = new UriBuilderContextPooledObjectPolicy();
|
||||
var objectPool = objectPoolProvider.Create(objectPolicy);
|
||||
|
||||
var constraintResolver = GetInlineConstraintResolver();
|
||||
var builder = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
UrlEncoder.Default,
|
||||
objectPool,
|
||||
new RoutePatternBinderFactory(UrlEncoder.Default, new DefaultObjectPoolProvider()),
|
||||
constraintResolver);
|
||||
return builder;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,12 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
|||
{
|
||||
private static readonly RequestDelegate NullHandler = (c) => Task.FromResult(0);
|
||||
|
||||
private static UrlEncoder Encoder = UrlTestEncoder.Default;
|
||||
private static ObjectPool<UriBuildingContext> Pool = new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy());
|
||||
public TreeRouterTest()
|
||||
{
|
||||
BinderFactory = new RoutePatternBinderFactory(new UrlTestEncoder(), new DefaultObjectPoolProvider());
|
||||
}
|
||||
|
||||
public RoutePatternBinderFactory BinderFactory { get; }
|
||||
|
||||
[Theory]
|
||||
[InlineData("template/5", "template/{parameter:int}")]
|
||||
|
|
@ -1988,15 +1992,10 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
|||
|
||||
private static TreeRouteBuilder CreateBuilder()
|
||||
{
|
||||
var objectPoolProvider = new DefaultObjectPoolProvider();
|
||||
var objectPolicy = new UriBuilderContextPooledObjectPolicy();
|
||||
var objectPool = objectPoolProvider.Create<UriBuildingContext>(objectPolicy);
|
||||
|
||||
var constraintResolver = CreateConstraintResolver();
|
||||
var builder = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
UrlEncoder.Default,
|
||||
objectPool,
|
||||
new RoutePatternBinderFactory(UrlTestEncoder.Default, new DefaultObjectPoolProvider()),
|
||||
constraintResolver);
|
||||
return builder;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue