Pool state used to generate URL
This commit is contained in:
parent
a956fe53c1
commit
33f9bdadef
|
|
@ -4,7 +4,9 @@
|
|||
using System;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Routing.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
|
|
@ -25,6 +27,13 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.AddOptions();
|
||||
services.TryAddTransient<IInlineConstraintResolver, DefaultInlineConstraintResolver>();
|
||||
services.TryAddSingleton(UrlEncoder.Default);
|
||||
services.TryAddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
|
||||
services.TryAddSingleton<ObjectPool<UriBuildingContext>>(s =>
|
||||
{
|
||||
var provider = s.GetRequiredService<ObjectPoolProvider>();
|
||||
var encoder = s.GetRequiredService<UrlEncoder>();
|
||||
return provider.Create<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(encoder));
|
||||
});
|
||||
|
||||
if (configureOptions != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
// 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.AspNet.Routing.Internal
|
||||
{
|
||||
public struct BufferValue
|
||||
{
|
||||
public BufferValue(string value, bool requiresEncoding)
|
||||
{
|
||||
Value = value;
|
||||
RequiresEncoding = requiresEncoding;
|
||||
}
|
||||
|
||||
public bool RequiresEncoding { get; }
|
||||
|
||||
public string Value { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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.AspNet.Routing.Internal
|
||||
{
|
||||
// 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
|
||||
// the complete match. For example, if the subsegment is "{p1}-{p2}.xml" and we
|
||||
// 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
|
||||
{
|
||||
Beginning,
|
||||
Inside,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.AspNet.Routing.Internal
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public bool Return(UriBuildingContext obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Internal
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
public class UriBuildingContext
|
||||
{
|
||||
// Holds the 'accepted' parts of the uri.
|
||||
private readonly StringBuilder _uri;
|
||||
|
||||
// Holds the 'optional' parts of the uri. We need a secondary buffer to handle cases where an optional
|
||||
// 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)
|
||||
{
|
||||
_urlEncoder = urlEncoder;
|
||||
_uri = new StringBuilder();
|
||||
_buffer = new List<BufferValue>();
|
||||
Writer = new StringWriter(_uri);
|
||||
_lastValueOffset = -1;
|
||||
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public SegmentState BufferState { get; private set; }
|
||||
|
||||
public SegmentState UriState { get; private set; }
|
||||
|
||||
public TextWriter Writer { get; }
|
||||
|
||||
public bool Accept(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (UriState == SegmentState.Inside || BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _buffer.Count; i++)
|
||||
{
|
||||
if (_buffer[i].RequiresEncoding)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, _buffer[i].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_uri.Append(_buffer[i].Value);
|
||||
}
|
||||
}
|
||||
_buffer.Clear();
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0)
|
||||
{
|
||||
_uri.Append("/");
|
||||
}
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
UriState = SegmentState.Inside;
|
||||
|
||||
_lastValueOffset = _uri.Length;
|
||||
// Allow the first segment to have a leading slash.
|
||||
// This prevents the leading slash from PathString segments from being encoded.
|
||||
if (_uri.Length == 0 && value.Length > 0 && value[0] == '/')
|
||||
{
|
||||
_uri.Append("/");
|
||||
_urlEncoder.Encode(Writer, value, 1, value.Length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Remove(string literal)
|
||||
{
|
||||
Debug.Assert(_lastValueOffset != -1, "Cannot invoke Remove more than once.");
|
||||
_uri.Length = _lastValueOffset;
|
||||
_lastValueOffset = -1;
|
||||
}
|
||||
|
||||
public bool Buffer(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Inside)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// We've already checked the conditions that could result in a rejected part, so this should
|
||||
// always be true.
|
||||
Debug.Assert(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0 || _buffer.Count != 0)
|
||||
{
|
||||
_buffer.Add(new BufferValue("/", requiresEncoding: false));
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
}
|
||||
|
||||
_buffer.Add(new BufferValue(value, requiresEncoding: true));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EndSegment()
|
||||
{
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_uri.Clear();
|
||||
if (_uri.Capacity > 128)
|
||||
{
|
||||
// We don't want to retain too much memory if this is getting pooled.
|
||||
_uri.Capacity = 128;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
if (_buffer.Capacity > 8)
|
||||
{
|
||||
_buffer.Capacity = 8;
|
||||
}
|
||||
|
||||
_hasEmptySegment = false;
|
||||
_lastValueOffset = -1;
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// We can ignore any currently buffered segments - they are are guaranteed to be 'defaults'.
|
||||
return _uri.ToString();
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}' Buffered: '{1}'}}", _uri, string.Join("", _buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNet.Routing.Internal;
|
|||
using Microsoft.AspNet.Routing.Template;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
{
|
||||
|
|
@ -243,7 +244,8 @@ namespace Microsoft.AspNet.Routing
|
|||
if (_binder == null)
|
||||
{
|
||||
var urlEncoder = context.RequestServices.GetRequiredService<UrlEncoder>();
|
||||
_binder = new TemplateBinder(ParsedTemplate, urlEncoder, Defaults);
|
||||
var pool = context.RequestServices.GetRequiredService<ObjectPool<UriBuildingContext>>();
|
||||
_binder = new TemplateBinder(urlEncoder, pool, ParsedTemplate, Defaults);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,40 +2,47 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNet.Http.Extensions;
|
||||
using Microsoft.AspNet.Routing.Internal;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNet.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 UrlEncoder _urlEncoder;
|
||||
|
||||
public TemplateBinder(
|
||||
RouteTemplate template,
|
||||
UrlEncoder urlEncoder,
|
||||
ObjectPool<UriBuildingContext> pool,
|
||||
RouteTemplate template,
|
||||
RouteValueDictionary defaults)
|
||||
{
|
||||
if (template == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(template));
|
||||
}
|
||||
|
||||
if (urlEncoder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(urlEncoder));
|
||||
}
|
||||
|
||||
_template = template;
|
||||
if (pool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pool));
|
||||
}
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
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
|
||||
|
|
@ -189,8 +196,14 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
// Step 2: If the route is a match generate the appropriate URI
|
||||
public string BindValues(RouteValueDictionary acceptedValues)
|
||||
{
|
||||
var context = new UriBuildingContext(_urlEncoder);
|
||||
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);
|
||||
|
|
@ -268,7 +281,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
// Generate the query string from the remaining values
|
||||
var queryBuilder = new QueryBuilder();
|
||||
var wroteFirst = false;
|
||||
foreach (var kvp in acceptedValues)
|
||||
{
|
||||
if (_defaults != null && _defaults.ContainsKey(kvp.Key))
|
||||
|
|
@ -283,12 +296,22 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
continue;
|
||||
}
|
||||
|
||||
queryBuilder.Add(kvp.Key, converted);
|
||||
if (!wroteFirst)
|
||||
{
|
||||
context.Writer.Write('?');
|
||||
wroteFirst = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Writer.Write('&');
|
||||
}
|
||||
|
||||
_urlEncoder.Encode(context.Writer, kvp.Key);
|
||||
context.Writer.Write('=');
|
||||
_urlEncoder.Encode(context.Writer, converted);
|
||||
}
|
||||
|
||||
var uri = context.GetUri();
|
||||
uri.Append(queryBuilder);
|
||||
return uri.ToString();
|
||||
return context.ToString();
|
||||
}
|
||||
|
||||
private TemplatePart GetParameter(string name)
|
||||
|
|
@ -350,7 +373,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private class TemplateBindingContext
|
||||
private struct TemplateBindingContext
|
||||
{
|
||||
private readonly RouteValueDictionary _defaults;
|
||||
private readonly RouteValueDictionary _acceptedValues;
|
||||
|
|
@ -396,196 +419,5 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return string.Format("{{Accepted: '{0}'}}", string.Join(", ", _acceptedValues.Keys));
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private class UriBuildingContext
|
||||
{
|
||||
// Holds the 'accepted' parts of the uri.
|
||||
private readonly StringBuilder _uri;
|
||||
|
||||
// Holds the 'optional' parts of the uri. We need a secondary buffer to handle cases where an optional
|
||||
// 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 readonly StringWriter _uriWriter;
|
||||
|
||||
private bool _hasEmptySegment;
|
||||
private int _lastValueOffset;
|
||||
|
||||
public UriBuildingContext(UrlEncoder urlEncoder)
|
||||
{
|
||||
_urlEncoder = urlEncoder;
|
||||
_uri = new StringBuilder();
|
||||
_buffer = new List<BufferValue>();
|
||||
_uriWriter = new StringWriter(_uri);
|
||||
_lastValueOffset = -1;
|
||||
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public SegmentState BufferState { get; private set; }
|
||||
|
||||
public SegmentState UriState { get; private set; }
|
||||
|
||||
public bool Accept(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (UriState == SegmentState.Inside || BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _buffer.Count; i++)
|
||||
{
|
||||
if (_buffer[i].RequiresEncoding)
|
||||
{
|
||||
_urlEncoder.Encode(_uriWriter, _buffer[i].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_uri.Append(_buffer[i].Value);
|
||||
}
|
||||
}
|
||||
_buffer.Clear();
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0)
|
||||
{
|
||||
_uri.Append("/");
|
||||
}
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
UriState = SegmentState.Inside;
|
||||
|
||||
_lastValueOffset = _uri.Length;
|
||||
// Allow the first segment to have a leading slash.
|
||||
// This prevents the leading slash from PathString segments from being encoded.
|
||||
if (_uri.Length == 0 && value.Length > 0 && value[0] == '/')
|
||||
{
|
||||
_uri.Append("/");
|
||||
_urlEncoder.Encode(_uriWriter, value, 1, value.Length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_urlEncoder.Encode(_uriWriter, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Remove(string literal)
|
||||
{
|
||||
Debug.Assert(_lastValueOffset != -1, "Cannot invoke Remove more than once.");
|
||||
_uri.Length = _lastValueOffset;
|
||||
_lastValueOffset = -1;
|
||||
}
|
||||
|
||||
public bool Buffer(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Inside)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// We've already checked the conditions that could result in a rejected part, so this should
|
||||
// always be true.
|
||||
Debug.Assert(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0 || _buffer.Count != 0)
|
||||
{
|
||||
_buffer.Add(new BufferValue("/", requiresEncoding: false));
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
}
|
||||
|
||||
_buffer.Add(new BufferValue(value, requiresEncoding: true));
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void EndSegment()
|
||||
{
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
internal StringBuilder GetUri()
|
||||
{
|
||||
// We can ignore any currently buffered segments - they are are guaranteed to be 'defaults'.
|
||||
return _uri;
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}' Buffered: '{1}'}}", _uri, string.Join("", _buffer));
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// the complete match. For example, if the subsegment is "{p1}-{p2}.xml" and we
|
||||
// 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".
|
||||
private enum SegmentState
|
||||
{
|
||||
Beginning,
|
||||
Inside,
|
||||
}
|
||||
|
||||
private struct BufferValue
|
||||
{
|
||||
public BufferValue(string value, bool requiresEncoding)
|
||||
{
|
||||
Value = value;
|
||||
RequiresEncoding = requiresEncoding;
|
||||
}
|
||||
|
||||
public bool RequiresEncoding { get; }
|
||||
|
||||
public string Value { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"version": "1.0.0-*"
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
||||
"Microsoft.Extensions.ObjectPool": "1.0.0-*",
|
||||
"Microsoft.Extensions.Options": "1.0.0-*",
|
||||
"Microsoft.Extensions.PropertyHelper.Sources": {
|
||||
"type": "build",
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ namespace Microsoft.AspNet.Routing
|
|||
innerRouteCollection.Add(namedRoute);
|
||||
routeCollection.Add(innerRouteCollection);
|
||||
|
||||
var virtualPathContext = CreateVirtualPathContext("Ambiguous", options: new RouteOptions());
|
||||
var virtualPathContext = CreateVirtualPathContext("Ambiguous");
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => routeCollection.GetVirtualPath(virtualPathContext));
|
||||
|
|
@ -519,31 +519,24 @@ namespace Microsoft.AspNet.Routing
|
|||
private static VirtualPathContext CreateVirtualPathContext(
|
||||
string routeName = null,
|
||||
ILoggerFactory loggerFactory = null,
|
||||
RouteOptions options = null)
|
||||
Action<RouteOptions> options = null)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
loggerFactory = NullLoggerFactory.Instance;
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
options = new RouteOptions();
|
||||
}
|
||||
|
||||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||
|
||||
var optionsAccessor = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
|
||||
optionsAccessor.SetupGet(o => o.Value).Returns(options);
|
||||
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton(loggerFactory)
|
||||
.AddSingleton(UrlEncoder.Default)
|
||||
.AddSingleton(optionsAccessor.Object)
|
||||
.BuildServiceProvider();
|
||||
var services = new ServiceCollection();
|
||||
services.AddRouting();
|
||||
if (options != null)
|
||||
{
|
||||
services.Configure(options);
|
||||
}
|
||||
|
||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||
context.SetupGet(m => m.RequestServices).Returns(serviceProvider);
|
||||
context.SetupGet(m => m.RequestServices).Returns(services.BuildServiceProvider());
|
||||
context.SetupGet(c => c.Request).Returns(request.Object);
|
||||
|
||||
return new VirtualPathContext(context.Object, null, null, routeName);
|
||||
|
|
@ -551,21 +544,20 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
private static VirtualPathContext CreateVirtualPathContext(
|
||||
RouteValueDictionary values,
|
||||
RouteOptions options = null,
|
||||
Action<RouteOptions> options = null,
|
||||
string routeName = null)
|
||||
{
|
||||
var optionsAccessor = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
|
||||
optionsAccessor.SetupGet(o => o.Value).Returns(options);
|
||||
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
|
||||
.AddSingleton(UrlEncoder.Default)
|
||||
.AddSingleton(optionsAccessor.Object)
|
||||
.BuildServiceProvider();
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
|
||||
services.AddRouting();
|
||||
if (options != null)
|
||||
{
|
||||
services.Configure<RouteOptions>(options);
|
||||
}
|
||||
|
||||
var context = new DefaultHttpContext
|
||||
{
|
||||
RequestServices = serviceProvider
|
||||
RequestServices = services.BuildServiceProvider(),
|
||||
};
|
||||
|
||||
return new VirtualPathContext(
|
||||
|
|
@ -626,15 +618,15 @@ namespace Microsoft.AspNet.Routing
|
|||
return target;
|
||||
}
|
||||
|
||||
private static RouteOptions GetRouteOptions(
|
||||
private static Action<RouteOptions> GetRouteOptions(
|
||||
bool lowerCaseUrls = false,
|
||||
bool appendTrailingSlash = false)
|
||||
{
|
||||
var routeOptions = new RouteOptions();
|
||||
routeOptions.LowercaseUrls = lowerCaseUrls;
|
||||
routeOptions.AppendTrailingSlash = appendTrailingSlash;
|
||||
|
||||
return routeOptions;
|
||||
return (options) =>
|
||||
{
|
||||
options.LowercaseUrls = lowerCaseUrls;
|
||||
options.AppendTrailingSlash = appendTrailingSlash;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1298,13 +1298,13 @@ namespace Microsoft.AspNet.Routing
|
|||
RouteValueDictionary values,
|
||||
RouteValueDictionary ambientValues)
|
||||
{
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
|
||||
.AddSingleton(UrlEncoder.Default)
|
||||
.BuildServiceProvider();
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
|
||||
services.AddRouting();
|
||||
|
||||
var context = new DefaultHttpContext
|
||||
{
|
||||
RequestServices = serviceProvider
|
||||
RequestServices = services.BuildServiceProvider(),
|
||||
};
|
||||
|
||||
return new VirtualPathContext(context, ambientValues, values);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNet.Routing.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
using Xunit;
|
||||
|
|
@ -114,9 +116,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy(encoder)),
|
||||
TemplateParser.Parse(template),
|
||||
new UrlTestEncoder(),
|
||||
defaults);
|
||||
|
||||
// Act & Assert
|
||||
|
|
@ -247,7 +251,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
new RouteValueDictionary(new {val1 = "someval1", val2 = "someval2" }),
|
||||
new RouteValueDictionary(),
|
||||
new RouteValueDictionary(new {val3 = "someval3" }),
|
||||
"UrlEncode[[Test]]/UrlEncode[[someval1]]UrlEncode[[.]]UrlEncode[[someval2]]?val3=someval3"
|
||||
"UrlEncode[[Test]]/UrlEncode[[someval1]]UrlEncode[[.]]UrlEncode[[someval2]]?UrlEncode[[val3]]=UrlEncode[[someval3]]"
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -261,9 +265,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var encoder = new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy(encoder)),
|
||||
TemplateParser.Parse(template),
|
||||
new UrlTestEncoder(),
|
||||
defaults);
|
||||
|
||||
// Act & Assert
|
||||
|
|
@ -602,7 +608,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
new RouteValueDictionary(new { controller = "Home" }),
|
||||
new RouteValueDictionary(new { controller = "home", action = "Index", id = (string)null }),
|
||||
values,
|
||||
"UrlEncode[[products]]UrlEncode[[.mvc]]/UrlEncode[[showcategory]]/UrlEncode[[123]]?so%3Frt=de%3Fsc&maxPrice=100");
|
||||
"UrlEncode[[products]]UrlEncode[[.mvc]]/UrlEncode[[showcategory]]/UrlEncode[[123]]" +
|
||||
"?UrlEncode[[so?rt]]=UrlEncode[[de?sc]]&UrlEncode[[maxPrice]]=UrlEncode[[100]]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -622,7 +629,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
maxPrice = 100,
|
||||
custom = "customValue"
|
||||
}),
|
||||
"UrlEncode[[products]]UrlEncode[[.mvc]]/UrlEncode[[showcategory]]/UrlEncode[[123]]?sort=desc&maxPrice=100");
|
||||
"UrlEncode[[products]]UrlEncode[[.mvc]]/UrlEncode[[showcategory]]/UrlEncode[[123]]" +
|
||||
"?UrlEncode[[sort]]=UrlEncode[[desc]]&UrlEncode[[maxPrice]]=UrlEncode[[100]]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1091,7 +1099,12 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
// Arrange
|
||||
encoder = encoder ?? new UrlTestEncoder();
|
||||
var binder = new TemplateBinder(TemplateParser.Parse(template), encoder, defaults);
|
||||
|
||||
var binder = new TemplateBinder(
|
||||
encoder,
|
||||
new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy(encoder)),
|
||||
TemplateParser.Parse(template),
|
||||
defaults);
|
||||
|
||||
// Act & Assert
|
||||
var result = binder.GetValues(ambientValues, values);
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ using System.Linq;
|
|||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Internal;
|
||||
using Microsoft.AspNet.Routing.Template;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
using Moq;
|
||||
|
|
@ -21,6 +23,10 @@ namespace Microsoft.AspNet.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));
|
||||
|
||||
[Theory]
|
||||
[InlineData("template/5", "template/{parameter:int}")]
|
||||
[InlineData("template/5", "template/{parameter}")]
|
||||
|
|
@ -1601,7 +1607,7 @@ namespace Microsoft.AspNet.Routing.Tree
|
|||
|
||||
entry.Constraints = constraints;
|
||||
entry.Defaults = defaults;
|
||||
entry.Binder = new TemplateBinder(entry.Template, UrlEncoder.Default, defaults);
|
||||
entry.Binder = new TemplateBinder(Encoder, Pool, entry.Template, defaults);
|
||||
entry.Order = order;
|
||||
entry.GenerationPrecedence = RoutePrecedence.ComputeGenerated(entry.Template);
|
||||
entry.RequiredLinkValues = new RouteValueDictionary(requiredValues);
|
||||
|
|
|
|||
Loading…
Reference in New Issue