Port TemplateBinder to dispatcher
This commit is contained in:
parent
eebc7db2ca
commit
2d661396df
|
|
@ -31,8 +31,8 @@ namespace Microsoft.AspNetCore.Dispatcher.Performance
|
|||
var treeBuilder = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
UrlEncoder.Default,
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default)),
|
||||
new DefaultInlineConstraintResolver(new OptionsManager<RouteOptions>(new OptionsFactory<RouteOptions>(Enumerable.Empty<IConfigureOptions<RouteOptions>>(), Enumerable.Empty<IPostConfigureOptions<RouteOptions>>()))));
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())));
|
||||
|
||||
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets/{id}"), "default", 0);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ using System.Linq;
|
|||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
|
@ -31,8 +31,8 @@ namespace Microsoft.AspNetCore.Routing.Performance
|
|||
var treeBuilder = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
UrlEncoder.Default,
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default)),
|
||||
new DefaultInlineConstraintResolver(new OptionsManager<RouteOptions>(new OptionsFactory<RouteOptions>(Enumerable.Empty<IConfigureOptions<RouteOptions>>(), Enumerable.Empty<IPostConfigureOptions<RouteOptions>>()))));
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())));
|
||||
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets/{id}"), "default", 0);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public struct BufferValue
|
||||
{
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
|
|
@ -18,7 +19,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
// Adds the EndpointMiddleare at the end of the pipeline if the DispatcherMiddleware is in use.
|
||||
// Adds the EndpointMiddleware at the end of the pipeline if the DispatcherMiddleware is in use.
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter, DispatcherEndpointStartupFilter>());
|
||||
|
||||
// Adds a default dispatcher which will collect all data sources and endpoint selectors from DI.
|
||||
|
|
@ -27,6 +28,15 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.AddSingleton<AddressTable, DefaultAddressTable>();
|
||||
services.AddSingleton<TemplateAddressSelector>();
|
||||
|
||||
//
|
||||
// Infrastructure
|
||||
//
|
||||
services.AddSingleton<ObjectPool<UriBuildingContext>>(s =>
|
||||
{
|
||||
var provider = s.GetRequiredService<ObjectPoolProvider>();
|
||||
return provider.Create<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy());
|
||||
});
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHandlerFactory, TemplateEndpointHandlerFactory>());
|
||||
|
||||
return services;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.HashCodeCombiner.Sources" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,441 @@
|
|||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternBinder
|
||||
{
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
private readonly ObjectPool<UriBuildingContext> _pool;
|
||||
|
||||
private readonly DispatcherValueCollection _defaults;
|
||||
private readonly DispatcherValueCollection _filters;
|
||||
private readonly RoutePattern _pattern;
|
||||
|
||||
public RoutePatternBinder(
|
||||
UrlEncoder urlEncoder,
|
||||
ObjectPool<UriBuildingContext> pool,
|
||||
RoutePattern template,
|
||||
DispatcherValueCollection defaults)
|
||||
{
|
||||
if (urlEncoder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(urlEncoder));
|
||||
}
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pool));
|
||||
}
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(template));
|
||||
}
|
||||
|
||||
_urlEncoder = urlEncoder;
|
||||
_pool = pool;
|
||||
_pattern = template;
|
||||
_defaults = defaults;
|
||||
|
||||
// Any default that doesn't have a corresponding parameter is a 'filter' and if a value
|
||||
// is provided for that 'filter' it must match the value in defaults.
|
||||
_filters = new DispatcherValueCollection(_defaults);
|
||||
foreach (var parameter in _pattern.Parameters)
|
||||
{
|
||||
_filters.Remove(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Get the list of values we're going to try to use to match and generate this URI
|
||||
public (DispatcherValueCollection acceptedValues, DispatcherValueCollection combinedValues) GetValues(DispatcherValueCollection ambientValues, DispatcherValueCollection values)
|
||||
{
|
||||
var context = new TemplateBindingContext(_defaults);
|
||||
|
||||
// Find out which entries in the URI are valid for the URI we want to generate.
|
||||
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
|
||||
// specified that b="9", then we need to invalidate everything after it. The new
|
||||
// values should then be a="1", b="9", c=<no value>.
|
||||
//
|
||||
// We also handle the case where a parameter is optional but has no value - we shouldn't
|
||||
// accept additional parameters that appear *after* that parameter.
|
||||
for (var i = 0; i < _pattern.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _pattern.Parameters[i];
|
||||
|
||||
// If it's a parameter subsegment, examine the current value to see if it matches the new value
|
||||
var parameterName = parameter.Name;
|
||||
|
||||
object newParameterValue;
|
||||
var hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue);
|
||||
|
||||
object currentParameterValue = null;
|
||||
var hasCurrentParameterValue = ambientValues != null &&
|
||||
ambientValues.TryGetValue(parameterName, out currentParameterValue);
|
||||
|
||||
if (hasNewParameterValue && hasCurrentParameterValue)
|
||||
{
|
||||
if (!RoutePartsEqual(currentParameterValue, newParameterValue))
|
||||
{
|
||||
// Stop copying current values when we find one that doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNewParameterValue &&
|
||||
!hasCurrentParameterValue &&
|
||||
_defaults?.ContainsKey(parameter.Name) != true)
|
||||
{
|
||||
// This is an unsatisfied parameter value and there are no defaults. We might still
|
||||
// be able to generate a URL but we should stop 'accepting' ambient values.
|
||||
//
|
||||
// This might be a case like:
|
||||
// template: a/{b?}/{c?}
|
||||
// ambient: { c = 17 }
|
||||
// values: { }
|
||||
//
|
||||
// We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
|
||||
// we can't use it.
|
||||
//
|
||||
// In the example above we should fall into this block for 'b'.
|
||||
break;
|
||||
}
|
||||
|
||||
// If the parameter is a match, add it to the list of values we will use for URI generation
|
||||
if (hasNewParameterValue)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(newParameterValue))
|
||||
{
|
||||
context.Accept(parameterName, newParameterValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasCurrentParameterValue)
|
||||
{
|
||||
context.Accept(parameterName, currentParameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all remaining new values to the list of values we will use for URI generation
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(kvp.Value))
|
||||
{
|
||||
context.Accept(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Accept all remaining default values if they match a required parameter
|
||||
for (var i = 0; i < _pattern.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _pattern.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.NeedsValue(parameter.Name))
|
||||
{
|
||||
// Add the default value only if there isn't already a new value for it and
|
||||
// only if it actually has a default value, which we determine based on whether
|
||||
// the parameter value is required.
|
||||
context.AcceptDefault(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all required parameters have a value.
|
||||
for (var i = 0; i < _pattern.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _pattern.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!context.AcceptedValues.ContainsKey(parameter.Name))
|
||||
{
|
||||
// We don't have a value for this parameter, so we can't generate a url.
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Any default values that don't appear as parameters are treated like filters. Any new values
|
||||
// provided must match these defaults.
|
||||
foreach (var filter in _filters)
|
||||
{
|
||||
var parameter = _pattern.GetParameter(filter.Key);
|
||||
if (parameter != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(filter.Key, out value))
|
||||
{
|
||||
if (!RoutePartsEqual(value, filter.Value))
|
||||
{
|
||||
// If there is a non-parameterized value in the route and there is a
|
||||
// new value for it and it doesn't match, this route won't match.
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any ambient values that don't match parameters - they need to be visible to constraints
|
||||
// but they will ignored by link generation.
|
||||
var combinedValues = new DispatcherValueCollection(context.AcceptedValues);
|
||||
if (ambientValues != null)
|
||||
{
|
||||
foreach (var kvp in ambientValues)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(kvp.Value))
|
||||
{
|
||||
var parameter = _pattern.GetParameter(kvp.Key);
|
||||
if (parameter == null && !context.AcceptedValues.ContainsKey(kvp.Key))
|
||||
{
|
||||
combinedValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (context.AcceptedValues, combinedValues);
|
||||
}
|
||||
|
||||
// Step 2: If the route is a match generate the appropriate URI
|
||||
public string BindValues(DispatcherValueCollection acceptedValues)
|
||||
{
|
||||
var context = _pool.Get();
|
||||
var result = BindValues(context, acceptedValues);
|
||||
_pool.Return(context);
|
||||
return result;
|
||||
}
|
||||
|
||||
private string BindValues(UriBuildingContext context, DispatcherValueCollection acceptedValues)
|
||||
{
|
||||
for (var i = 0; i < _pattern.PathSegments.Count; i++)
|
||||
{
|
||||
Debug.Assert(context.BufferState == SegmentState.Beginning);
|
||||
Debug.Assert(context.UriState == SegmentState.Beginning);
|
||||
|
||||
var segment = _pattern.PathSegments[i];
|
||||
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
|
||||
if (part.IsLiteral)
|
||||
{
|
||||
if (!context.Accept(_urlEncoder, ((RoutePatternLiteral)part).Content))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.IsSeparator)
|
||||
{
|
||||
if (!context.Accept(_urlEncoder, ((RoutePatternSeparator)part).Content))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.IsParameter && part is RoutePatternParameter parameter)
|
||||
{
|
||||
// If it's a parameter, get its value
|
||||
object value;
|
||||
var hasValue = acceptedValues.TryGetValue(parameter.Name, out value);
|
||||
if (hasValue)
|
||||
{
|
||||
acceptedValues.Remove(parameter.Name);
|
||||
}
|
||||
|
||||
var isSameAsDefault = false;
|
||||
object defaultValue;
|
||||
if (_defaults != null && _defaults.TryGetValue(parameter.Name, out defaultValue))
|
||||
{
|
||||
if (RoutePartsEqual(value, defaultValue))
|
||||
{
|
||||
isSameAsDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (isSameAsDefault)
|
||||
{
|
||||
// If the accepted value is the same as the default value buffer it since
|
||||
// we won't necessarily add it to the URI we generate.
|
||||
if (!context.Buffer(_urlEncoder, converted))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the value is not accepted, it is null or empty value in the
|
||||
// middle of the segment. We accept this if the parameter is an
|
||||
// optional parameter and it is preceded by an optional seperator.
|
||||
// I this case, we need to remove the optional seperator that we
|
||||
// have added to the URI
|
||||
// Example: template = {id}.{format?}. parameters: id=5
|
||||
// In this case after we have generated "5.", we wont find any value
|
||||
// for format, so we remove '.' and generate 5.
|
||||
if (!context.Accept(_urlEncoder, converted))
|
||||
{
|
||||
if (j != 0 && parameter.IsOptional && segment.Parts[j - 1].IsSeparator)
|
||||
{
|
||||
context.Remove(((RoutePatternSeparator)segment.Parts[j - 1]).Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.EndSegment();
|
||||
}
|
||||
|
||||
// Generate the query string from the remaining values
|
||||
var wroteFirst = false;
|
||||
foreach (var kvp in acceptedValues)
|
||||
{
|
||||
if (_defaults != null && _defaults.ContainsKey(kvp.Key))
|
||||
{
|
||||
// This value is a 'filter' we don't need to put it in the query string.
|
||||
continue;
|
||||
}
|
||||
|
||||
var values = kvp.Value as IEnumerable;
|
||||
if (values != null && !(values is string))
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, value, wroteFirst);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, kvp.Value, wroteFirst);
|
||||
}
|
||||
}
|
||||
return context.ToString();
|
||||
}
|
||||
|
||||
private bool AddParameterToContext(UriBuildingContext context, string key, object value, bool wroteFirst)
|
||||
{
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrEmpty(converted))
|
||||
{
|
||||
context.Writer.Write(wroteFirst ? '&' : '?');
|
||||
_urlEncoder.Encode(context.Writer, key);
|
||||
context.Writer.Write('=');
|
||||
_urlEncoder.Encode(context.Writer, converted);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for equality as parts of a case-insensitive path.
|
||||
/// </summary>
|
||||
/// <param name="a">An object to compare.</param>
|
||||
/// <param name="b">An object to compare.</param>
|
||||
/// <returns>True if the object are equal, otherwise false.</returns>
|
||||
public static bool RoutePartsEqual(object a, object b)
|
||||
{
|
||||
var sa = a as string;
|
||||
var sb = b as string;
|
||||
|
||||
if (sa != null && sb != null)
|
||||
{
|
||||
// For strings do a case-insensitive comparison
|
||||
return string.Equals(sa, sb, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a != null && b != null)
|
||||
{
|
||||
// Explicitly call .Equals() in case it is overridden in the type
|
||||
return a.Equals(b);
|
||||
}
|
||||
else
|
||||
{
|
||||
// At least one of them is null. Return true if they both are
|
||||
return a == b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRoutePartNonEmpty(object routePart)
|
||||
{
|
||||
var routePartString = routePart as string;
|
||||
if (routePartString == null)
|
||||
{
|
||||
return routePart != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return routePartString.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private struct TemplateBindingContext
|
||||
{
|
||||
private readonly DispatcherValueCollection _defaults;
|
||||
private readonly DispatcherValueCollection _acceptedValues;
|
||||
|
||||
public TemplateBindingContext(DispatcherValueCollection defaults)
|
||||
{
|
||||
_defaults = defaults;
|
||||
|
||||
_acceptedValues = new DispatcherValueCollection();
|
||||
}
|
||||
|
||||
public DispatcherValueCollection AcceptedValues
|
||||
{
|
||||
get { return _acceptedValues; }
|
||||
}
|
||||
|
||||
public void Accept(string key, object value)
|
||||
{
|
||||
if (!_acceptedValues.ContainsKey(key))
|
||||
{
|
||||
_acceptedValues.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptDefault(string key)
|
||||
{
|
||||
Debug.Assert(!_acceptedValues.ContainsKey(key));
|
||||
|
||||
object value;
|
||||
if (_defaults != null && _defaults.TryGetValue(key, out value))
|
||||
{
|
||||
_acceptedValues.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedsValue(string key)
|
||||
{
|
||||
return !_acceptedValues.ContainsKey(key);
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}'}}", string.Join(", ", _acceptedValues.Keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
// Segments are treated as all-or-none. We should never output a partial segment.
|
||||
// If we add any subsegment of this segment to the generated URI, we have to add
|
||||
|
|
@ -1,29 +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.
|
||||
|
||||
using System;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class UriBuilderContextPooledObjectPolicy : IPooledObjectPolicy<UriBuildingContext>
|
||||
{
|
||||
private readonly UrlEncoder _encoder;
|
||||
|
||||
public UriBuilderContextPooledObjectPolicy(UrlEncoder encoder)
|
||||
{
|
||||
if (encoder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoder));
|
||||
}
|
||||
|
||||
_encoder = encoder;
|
||||
}
|
||||
|
||||
public UriBuildingContext Create()
|
||||
{
|
||||
return new UriBuildingContext(_encoder);
|
||||
return new UriBuildingContext();
|
||||
}
|
||||
|
||||
public bool Return(UriBuildingContext obj)
|
||||
|
|
@ -7,7 +7,7 @@ using System.IO;
|
|||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
public class UriBuildingContext
|
||||
|
|
@ -19,14 +19,12 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
// segment is in the middle of the uri. We don't know if we need to write it out - if it's
|
||||
// followed by other optional segments than we will just throw it away.
|
||||
private readonly List<BufferValue> _buffer;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
|
||||
private bool _hasEmptySegment;
|
||||
private int _lastValueOffset;
|
||||
|
||||
public UriBuildingContext(UrlEncoder urlEncoder)
|
||||
public UriBuildingContext()
|
||||
{
|
||||
_urlEncoder = urlEncoder;
|
||||
_uri = new StringBuilder();
|
||||
_buffer = new List<BufferValue>();
|
||||
Writer = new StringWriter(_uri);
|
||||
|
|
@ -42,7 +40,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
|
||||
public TextWriter Writer { get; }
|
||||
|
||||
public bool Accept(string value)
|
||||
public bool Accept(UrlEncoder encoder, string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
|
|
@ -67,7 +65,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
{
|
||||
if (_buffer[i].RequiresEncoding)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, _buffer[i].Value);
|
||||
encoder.Encode(Writer, _buffer[i].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -93,11 +91,11 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
if (_uri.Length == 0 && value.Length > 0 && value[0] == '/')
|
||||
{
|
||||
_uri.Append("/");
|
||||
_urlEncoder.Encode(Writer, value, 1, value.Length - 1);
|
||||
encoder.Encode(Writer, value, 1, value.Length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value);
|
||||
encoder.Encode(Writer, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -110,7 +108,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
_lastValueOffset = -1;
|
||||
}
|
||||
|
||||
public bool Buffer(string value)
|
||||
public bool Buffer(UrlEncoder encoder, string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
|
|
@ -135,7 +133,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
{
|
||||
// We've already written part of this segment so there's no point in buffering, we need to
|
||||
// write out the rest or give up.
|
||||
var result = Accept(value);
|
||||
var result = Accept(encoder, value);
|
||||
|
||||
// We've already checked the conditions that could result in a rejected part, so this should
|
||||
// always be true.
|
||||
|
|
@ -28,14 +28,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
// Routing shares lots of infrastructure with the dispatcher.
|
||||
services.AddDispatcher();
|
||||
|
||||
services.TryAddTransient<IInlineConstraintResolver, DefaultInlineConstraintResolver>();
|
||||
services.TryAddSingleton(UrlEncoder.Default);
|
||||
services.TryAddSingleton<ObjectPool<UriBuildingContext>>(s =>
|
||||
{
|
||||
var provider = s.GetRequiredService<ObjectPoolProvider>();
|
||||
var encoder = s.GetRequiredService<UrlEncoder>();
|
||||
return provider.Create<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(encoder));
|
||||
});
|
||||
|
||||
// The TreeRouteBuilder is a builder for creating routes, it should stay transient because it's
|
||||
// stateful.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Logging;
|
||||
|
|
|
|||
|
|
@ -2,23 +2,15 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Template
|
||||
{
|
||||
public class TemplateBinder
|
||||
{
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
private readonly ObjectPool<UriBuildingContext> _pool;
|
||||
|
||||
private readonly RouteValueDictionary _defaults;
|
||||
private readonly RouteValueDictionary _filters;
|
||||
private readonly RouteTemplate _template;
|
||||
private readonly RoutePatternBinder _binder;
|
||||
|
||||
public TemplateBinder(
|
||||
UrlEncoder urlEncoder,
|
||||
|
|
@ -41,320 +33,29 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
throw new ArgumentNullException(nameof(template));
|
||||
}
|
||||
|
||||
_urlEncoder = urlEncoder;
|
||||
_pool = pool;
|
||||
_template = template;
|
||||
_defaults = defaults;
|
||||
|
||||
// Any default that doesn't have a corresponding parameter is a 'filter' and if a value
|
||||
// is provided for that 'filter' it must match the value in defaults.
|
||||
_filters = new RouteValueDictionary(_defaults);
|
||||
foreach (var parameter in _template.Parameters)
|
||||
{
|
||||
_filters.Remove(parameter.Name);
|
||||
}
|
||||
_binder = new RoutePatternBinder(urlEncoder, pool, template.ToRoutePattern(), defaults);
|
||||
}
|
||||
|
||||
// Step 1: Get the list of values we're going to try to use to match and generate this URI
|
||||
public TemplateValuesResult GetValues(RouteValueDictionary ambientValues, RouteValueDictionary values)
|
||||
{
|
||||
var context = new TemplateBindingContext(_defaults);
|
||||
|
||||
// Find out which entries in the URI are valid for the URI we want to generate.
|
||||
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
|
||||
// specified that b="9", then we need to invalidate everything after it. The new
|
||||
// values should then be a="1", b="9", c=<no value>.
|
||||
//
|
||||
// We also handle the case where a parameter is optional but has no value - we shouldn't
|
||||
// accept additional parameters that appear *after* that parameter.
|
||||
for (var i = 0; i < _template.Parameters.Count; i++)
|
||||
(var acceptedValues, var combinedValues) = _binder.GetValues(ambientValues, values);
|
||||
if (acceptedValues == null || combinedValues == null)
|
||||
{
|
||||
var parameter = _template.Parameters[i];
|
||||
|
||||
// If it's a parameter subsegment, examine the current value to see if it matches the new value
|
||||
var parameterName = parameter.Name;
|
||||
|
||||
object newParameterValue;
|
||||
var hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue);
|
||||
|
||||
object currentParameterValue = null;
|
||||
var hasCurrentParameterValue = ambientValues != null &&
|
||||
ambientValues.TryGetValue(parameterName, out currentParameterValue);
|
||||
|
||||
if (hasNewParameterValue && hasCurrentParameterValue)
|
||||
{
|
||||
if (!RoutePartsEqual(currentParameterValue, newParameterValue))
|
||||
{
|
||||
// Stop copying current values when we find one that doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNewParameterValue &&
|
||||
!hasCurrentParameterValue &&
|
||||
_defaults?.ContainsKey(parameter.Name) != true)
|
||||
{
|
||||
// This is an unsatisfied parameter value and there are no defaults. We might still
|
||||
// be able to generate a URL but we should stop 'accepting' ambient values.
|
||||
//
|
||||
// This might be a case like:
|
||||
// template: a/{b?}/{c?}
|
||||
// ambient: { c = 17 }
|
||||
// values: { }
|
||||
//
|
||||
// We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
|
||||
// we can't use it.
|
||||
//
|
||||
// In the example above we should fall into this block for 'b'.
|
||||
break;
|
||||
}
|
||||
|
||||
// If the parameter is a match, add it to the list of values we will use for URI generation
|
||||
if (hasNewParameterValue)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(newParameterValue))
|
||||
{
|
||||
context.Accept(parameterName, newParameterValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasCurrentParameterValue)
|
||||
{
|
||||
context.Accept(parameterName, currentParameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all remaining new values to the list of values we will use for URI generation
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(kvp.Value))
|
||||
{
|
||||
context.Accept(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Accept all remaining default values if they match a required parameter
|
||||
for (var i = 0; i < _template.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _template.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.NeedsValue(parameter.Name))
|
||||
{
|
||||
// Add the default value only if there isn't already a new value for it and
|
||||
// only if it actually has a default value, which we determine based on whether
|
||||
// the parameter value is required.
|
||||
context.AcceptDefault(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all required parameters have a value.
|
||||
for (var i = 0; i < _template.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _template.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!context.AcceptedValues.ContainsKey(parameter.Name))
|
||||
{
|
||||
// We don't have a value for this parameter, so we can't generate a url.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Any default values that don't appear as parameters are treated like filters. Any new values
|
||||
// provided must match these defaults.
|
||||
foreach (var filter in _filters)
|
||||
{
|
||||
var parameter = GetParameter(filter.Key);
|
||||
if (parameter != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(filter.Key, out value))
|
||||
{
|
||||
if (!RoutePartsEqual(value, filter.Value))
|
||||
{
|
||||
// If there is a non-parameterized value in the route and there is a
|
||||
// new value for it and it doesn't match, this route won't match.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any ambient values that don't match parameters - they need to be visible to constraints
|
||||
// but they will ignored by link generation.
|
||||
var combinedValues = new RouteValueDictionary(context.AcceptedValues);
|
||||
if (ambientValues != null)
|
||||
{
|
||||
foreach (var kvp in ambientValues)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(kvp.Value))
|
||||
{
|
||||
var parameter = GetParameter(kvp.Key);
|
||||
if (parameter == null && !context.AcceptedValues.ContainsKey(kvp.Key))
|
||||
{
|
||||
combinedValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TemplateValuesResult()
|
||||
{
|
||||
AcceptedValues = context.AcceptedValues,
|
||||
CombinedValues = combinedValues,
|
||||
AcceptedValues = acceptedValues.AsRouteValueDictionary(),
|
||||
CombinedValues = combinedValues.AsRouteValueDictionary(),
|
||||
};
|
||||
}
|
||||
|
||||
// Step 2: If the route is a match generate the appropriate URI
|
||||
public string BindValues(RouteValueDictionary acceptedValues)
|
||||
{
|
||||
var context = _pool.Get();
|
||||
var result = BindValues(context, acceptedValues);
|
||||
_pool.Return(context);
|
||||
return result;
|
||||
}
|
||||
|
||||
private string BindValues(UriBuildingContext context, RouteValueDictionary acceptedValues)
|
||||
{
|
||||
for (var i = 0; i < _template.Segments.Count; i++)
|
||||
{
|
||||
Debug.Assert(context.BufferState == SegmentState.Beginning);
|
||||
Debug.Assert(context.UriState == SegmentState.Beginning);
|
||||
|
||||
var segment = _template.Segments[i];
|
||||
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
|
||||
if (part.IsLiteral)
|
||||
{
|
||||
if (!context.Accept(part.Text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.IsParameter)
|
||||
{
|
||||
// If it's a parameter, get its value
|
||||
object value;
|
||||
var hasValue = acceptedValues.TryGetValue(part.Name, out value);
|
||||
if (hasValue)
|
||||
{
|
||||
acceptedValues.Remove(part.Name);
|
||||
}
|
||||
|
||||
var isSameAsDefault = false;
|
||||
object defaultValue;
|
||||
if (_defaults != null && _defaults.TryGetValue(part.Name, out defaultValue))
|
||||
{
|
||||
if (RoutePartsEqual(value, defaultValue))
|
||||
{
|
||||
isSameAsDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (isSameAsDefault)
|
||||
{
|
||||
// If the accepted value is the same as the default value buffer it since
|
||||
// we won't necessarily add it to the URI we generate.
|
||||
if (!context.Buffer(converted))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the value is not accepted, it is null or empty value in the
|
||||
// middle of the segment. We accept this if the parameter is an
|
||||
// optional parameter and it is preceded by an optional seperator.
|
||||
// I this case, we need to remove the optional seperator that we
|
||||
// have added to the URI
|
||||
// Example: template = {id}.{format?}. parameters: id=5
|
||||
// In this case after we have generated "5.", we wont find any value
|
||||
// for format, so we remove '.' and generate 5.
|
||||
if (!context.Accept(converted))
|
||||
{
|
||||
if (j != 0 && part.IsOptional && segment.Parts[j - 1].IsOptionalSeperator)
|
||||
{
|
||||
context.Remove(segment.Parts[j - 1].Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.EndSegment();
|
||||
}
|
||||
|
||||
// Generate the query string from the remaining values
|
||||
var wroteFirst = false;
|
||||
foreach (var kvp in acceptedValues)
|
||||
{
|
||||
if (_defaults != null && _defaults.ContainsKey(kvp.Key))
|
||||
{
|
||||
// This value is a 'filter' we don't need to put it in the query string.
|
||||
continue;
|
||||
}
|
||||
|
||||
var values = kvp.Value as IEnumerable;
|
||||
if (values != null && !(values is string))
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, value, wroteFirst);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, kvp.Value, wroteFirst);
|
||||
}
|
||||
}
|
||||
return context.ToString();
|
||||
}
|
||||
|
||||
private bool AddParameterToContext(UriBuildingContext context, string key, object value, bool wroteFirst)
|
||||
{
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrEmpty(converted))
|
||||
{
|
||||
context.Writer.Write(wroteFirst ? '&' : '?');
|
||||
_urlEncoder.Encode(context.Writer, key);
|
||||
context.Writer.Write('=');
|
||||
_urlEncoder.Encode(context.Writer, converted);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private TemplatePart GetParameter(string name)
|
||||
{
|
||||
for (var i = 0; i < _template.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _template.Parameters[i];
|
||||
if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return _binder.BindValues(acceptedValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -387,66 +88,5 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRoutePartNonEmpty(object routePart)
|
||||
{
|
||||
var routePartString = routePart as string;
|
||||
if (routePartString == null)
|
||||
{
|
||||
return routePart != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return routePartString.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private struct TemplateBindingContext
|
||||
{
|
||||
private readonly RouteValueDictionary _defaults;
|
||||
private readonly RouteValueDictionary _acceptedValues;
|
||||
|
||||
public TemplateBindingContext(RouteValueDictionary defaults)
|
||||
{
|
||||
_defaults = defaults;
|
||||
|
||||
_acceptedValues = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
public RouteValueDictionary AcceptedValues
|
||||
{
|
||||
get { return _acceptedValues; }
|
||||
}
|
||||
|
||||
public void Accept(string key, object value)
|
||||
{
|
||||
if (!_acceptedValues.ContainsKey(key))
|
||||
{
|
||||
_acceptedValues.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptDefault(string key)
|
||||
{
|
||||
Debug.Assert(!_acceptedValues.ContainsKey(key));
|
||||
|
||||
object value;
|
||||
if (_defaults != null && _defaults.TryGetValue(key, out value))
|
||||
{
|
||||
_acceptedValues.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedsValue(string key)
|
||||
{
|
||||
return !_acceptedValues.ContainsKey(key);
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}'}}", string.Join(", ", _acceptedValues.Keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Dispatcher.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Logging;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
[
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Routing.Tree.TreeRouter : Microsoft.AspNetCore.Routing.IRouter",
|
||||
"MemberId": "public .ctor(Microsoft.AspNetCore.Routing.Tree.UrlMatchingTree[] trees, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Routing.Tree.OutboundRouteEntry> linkGenerationEntries, System.Text.Encodings.Web.UrlEncoder urlEncoder, Microsoft.Extensions.ObjectPool.ObjectPool<Microsoft.AspNetCore.Routing.Internal.UriBuildingContext> objectPool, Microsoft.Extensions.Logging.ILogger routeLogger, Microsoft.Extensions.Logging.ILogger constraintLogger, System.Int32 version)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Routing.Tree.TreeRouteBuilder",
|
||||
"MemberId": "public .ctor(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Text.Encodings.Web.UrlEncoder urlEncoder, Microsoft.Extensions.ObjectPool.ObjectPool<Microsoft.AspNetCore.Routing.Internal.UriBuildingContext> objectPool, Microsoft.AspNetCore.Routing.IInlineConstraintResolver constraintResolver)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Routing.Template.TemplateBinder",
|
||||
"MemberId": "public .ctor(System.Text.Encodings.Web.UrlEncoder urlEncoder, Microsoft.Extensions.ObjectPool.ObjectPool<Microsoft.AspNetCore.Routing.Internal.UriBuildingContext> pool, Microsoft.AspNetCore.Routing.Template.RouteTemplate template, Microsoft.AspNetCore.Routing.RouteValueDictionary defaults)",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
||||
|
|
@ -13,6 +13,8 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Http" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Testing" />
|
||||
<PackageReference Include="Microsoft.Extensions.WebEncoders" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -591,6 +591,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||
services.AddOptions();
|
||||
services.AddRouting();
|
||||
services.AddDispatcher();
|
||||
if (options != null)
|
||||
{
|
||||
services.Configure(options);
|
||||
|
|
@ -613,6 +614,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||
services.AddOptions();
|
||||
services.AddRouting();
|
||||
services.AddDispatcher();
|
||||
if (options != null)
|
||||
{
|
||||
services.Configure<RouteOptions>(options);
|
||||
|
|
|
|||
|
|
@ -1487,6 +1487,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
|
||||
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||
services.AddRouting();
|
||||
services.AddDispatcher();
|
||||
|
||||
var context = new DefaultHttpContext
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
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;
|
||||
|
|
@ -119,7 +120,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy(encoder)),
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
|
||||
|
|
@ -269,7 +270,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy(encoder)),
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
|
||||
|
|
@ -699,7 +700,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
new UrlTestEncoder(),
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy(encoder)),
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults: null);
|
||||
var ambientValues = new RouteValueDictionary();
|
||||
|
|
@ -1131,7 +1132,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy(encoder)),
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// 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.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -244,7 +244,7 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
|||
private static TreeRouteBuilder CreateBuilder()
|
||||
{
|
||||
var objectPoolProvider = new DefaultObjectPoolProvider();
|
||||
var objectPolicy = new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default);
|
||||
var objectPolicy = new UriBuilderContextPooledObjectPolicy();
|
||||
var objectPool = objectPoolProvider.Create(objectPolicy);
|
||||
|
||||
var constraintResolver = GetInlineConstraintResolver();
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
|
@ -24,8 +24,7 @@ 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(Encoder));
|
||||
private static ObjectPool<UriBuildingContext> Pool = new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy());
|
||||
|
||||
[Theory]
|
||||
[InlineData("template/5", "template/{parameter:int}")]
|
||||
|
|
@ -1990,7 +1989,7 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
|||
private static TreeRouteBuilder CreateBuilder()
|
||||
{
|
||||
var objectPoolProvider = new DefaultObjectPoolProvider();
|
||||
var objectPolicy = new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default);
|
||||
var objectPolicy = new UriBuilderContextPooledObjectPolicy();
|
||||
var objectPool = objectPoolProvider.Create<UriBuildingContext>(objectPolicy);
|
||||
|
||||
var constraintResolver = CreateConstraintResolver();
|
||||
|
|
|
|||
Loading…
Reference in New Issue