Merge pull request #803 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
0bb28edc63
|
|
@ -4,15 +4,16 @@
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the contract that a class must implement to transform parameter values.
|
/// Defines the contract that a class must implement to transform route values while building
|
||||||
|
/// a URI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IParameterTransformer : IParameterPolicy
|
public interface IOutboundParameterTransformer : IParameterPolicy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transforms the specified parameter value.
|
/// Transforms the specified route value to a string for inclusion in a URI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The parameter value to transform.</param>
|
/// <param name="value">The route value to transform.</param>
|
||||||
/// <returns>The transformed value.</returns>
|
/// <returns>The transformed value.</returns>
|
||||||
string Transform(string value);
|
string TransformOutbound(object value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -280,13 +280,40 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
private TemplateBinder CreateTemplateBinder(RouteEndpoint endpoint)
|
private TemplateBinder CreateTemplateBinder(RouteEndpoint endpoint)
|
||||||
{
|
{
|
||||||
|
// Now create the constraints and parameter transformers from the pattern
|
||||||
|
var policies = new List<(string parameterName, IParameterPolicy policy)>();
|
||||||
|
foreach (var kvp in endpoint.RoutePattern.ParameterPolicies)
|
||||||
|
{
|
||||||
|
var parameterName = kvp.Key;
|
||||||
|
|
||||||
|
// It's possible that we don't have an actual route parameter, we need to support that case.
|
||||||
|
var parameter = endpoint.RoutePattern.GetParameter(parameterName);
|
||||||
|
|
||||||
|
// Use the first parameter transformer per parameter
|
||||||
|
var foundTransformer = false;
|
||||||
|
for (var i = 0; i < kvp.Value.Count; i++)
|
||||||
|
{
|
||||||
|
var parameterPolicy = _parameterPolicyFactory.Create(parameter, kvp.Value[i]);
|
||||||
|
if (!foundTransformer && parameterPolicy is IOutboundParameterTransformer parameterTransformer)
|
||||||
|
{
|
||||||
|
policies.Add((parameterName, parameterTransformer));
|
||||||
|
foundTransformer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameterPolicy is IRouteConstraint constraint)
|
||||||
|
{
|
||||||
|
policies.Add((parameterName, constraint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new TemplateBinder(
|
return new TemplateBinder(
|
||||||
UrlEncoder.Default,
|
UrlEncoder.Default,
|
||||||
_uriBuildingContextPool,
|
_uriBuildingContextPool,
|
||||||
endpoint.RoutePattern,
|
endpoint.RoutePattern,
|
||||||
new RouteValueDictionary(endpoint.RoutePattern.Defaults),
|
new RouteValueDictionary(endpoint.RoutePattern.Defaults),
|
||||||
endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>()?.RequiredValues.Keys,
|
endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>()?.RequiredValues.Keys,
|
||||||
_parameterPolicyFactory);
|
policies);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
|
|
@ -312,11 +339,10 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
|
if (!templateBinder.TryProcessConstraints(httpContext, templateValuesResult.CombinedValues, out var parameterName, out var constraint))
|
||||||
{
|
{
|
||||||
result = default;
|
result = default;
|
||||||
|
Log.TemplateFailedConstraint(_logger, endpoint, parameterName, constraint, templateValuesResult.CombinedValues);
|
||||||
// MatchesConstraints does its own logging, so we're not logging here.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,36 +356,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MatchesConstraints(
|
|
||||||
HttpContext httpContext,
|
|
||||||
RouteEndpoint endpoint,
|
|
||||||
RouteValueDictionary routeValues)
|
|
||||||
{
|
|
||||||
if (routeValues == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(routeValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var kvp in endpoint.RoutePattern.ParameterPolicies)
|
|
||||||
{
|
|
||||||
var parameter = endpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok
|
|
||||||
var constraintReferences = kvp.Value;
|
|
||||||
for (var i = 0; i < constraintReferences.Count; i++)
|
|
||||||
{
|
|
||||||
var constraintReference = constraintReferences[i];
|
|
||||||
var parameterPolicy = _parameterPolicyFactory.Create(parameter, constraintReference);
|
|
||||||
if (parameterPolicy is IRouteConstraint routeConstraint
|
|
||||||
&& !routeConstraint.Match(httpContext, NullRouter.Instance, kvp.Key, routeValues, RouteDirection.UrlGeneration))
|
|
||||||
{
|
|
||||||
Log.TemplateFailedConstraint(_logger, endpoint, kvp.Key, routeConstraint, routeValues);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also called from DefaultLinkGenerationTemplate
|
// Also called from DefaultLinkGenerationTemplate
|
||||||
public static RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
public static RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,10 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
||||||
|
|
||||||
public bool Accept(string value)
|
public bool Accept(string value)
|
||||||
{
|
{
|
||||||
return Accept(value, encodeSlashes: true, parameterTransformer: null);
|
return Accept(value, encodeSlashes: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Accept(string value, bool encodeSlashes, IParameterTransformer parameterTransformer)
|
public bool Accept(string value, bool encodeSlashes)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
|
|
@ -80,31 +80,31 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: call the parameter transformer before changing the case
|
|
||||||
// A transformer might use the case, e.g. AllProducts -> all-products
|
|
||||||
if (parameterTransformer != null)
|
|
||||||
{
|
|
||||||
value = parameterTransformer.Transform(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this needs to be above all 'EncodeValue' and _path.Append calls
|
// NOTE: this needs to be above all 'EncodeValue' and _path.Append calls
|
||||||
if (LowercaseUrls)
|
if (LowercaseUrls)
|
||||||
{
|
{
|
||||||
value = value.ToLowerInvariant();
|
value = value.ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < _buffer.Count; i++)
|
var buffer = _buffer;
|
||||||
|
for (var i = 0; i < buffer.Count; i++)
|
||||||
{
|
{
|
||||||
if (_buffer[i].RequiresEncoding)
|
var bufferValue = buffer[i].Value;
|
||||||
|
if (LowercaseUrls)
|
||||||
{
|
{
|
||||||
EncodeValue(_buffer[i].Value);
|
bufferValue = bufferValue.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer[i].RequiresEncoding)
|
||||||
|
{
|
||||||
|
EncodeValue(bufferValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_path.Append(_buffer[i].Value);
|
_path.Append(bufferValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_buffer.Clear();
|
buffer.Clear();
|
||||||
|
|
||||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,11 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
private readonly UrlEncoder _urlEncoder;
|
private readonly UrlEncoder _urlEncoder;
|
||||||
private readonly ObjectPool<UriBuildingContext> _pool;
|
private readonly ObjectPool<UriBuildingContext> _pool;
|
||||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
|
||||||
|
|
||||||
|
private readonly (string parameterName, IRouteConstraint constraint)[] _constraints;
|
||||||
private readonly RouteValueDictionary _defaults;
|
private readonly RouteValueDictionary _defaults;
|
||||||
private readonly KeyValuePair<string, object>[] _filters;
|
private readonly KeyValuePair<string, object>[] _filters;
|
||||||
|
private readonly (string parameterName, IOutboundParameterTransformer transformer)[] _parameterTransformers;
|
||||||
private readonly RoutePattern _pattern;
|
private readonly RoutePattern _pattern;
|
||||||
private readonly string[] _requiredKeys;
|
private readonly string[] _requiredKeys;
|
||||||
|
|
||||||
|
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
ObjectPool<UriBuildingContext> pool,
|
ObjectPool<UriBuildingContext> pool,
|
||||||
RouteTemplate template,
|
RouteTemplate template,
|
||||||
RouteValueDictionary defaults)
|
RouteValueDictionary defaults)
|
||||||
: this(urlEncoder, pool, template?.ToRoutePattern(), defaults, requiredKeys: null, parameterPolicyFactory: null)
|
: this(urlEncoder, pool, template?.ToRoutePattern(), defaults, requiredKeys: null, parameterPolicies: null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,14 +57,16 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
/// <param name="pattern">The <see cref="RoutePattern"/> to bind values to.</param>
|
/// <param name="pattern">The <see cref="RoutePattern"/> to bind values to.</param>
|
||||||
/// <param name="defaults">The default values for <paramref name="pattern"/>. Optional.</param>
|
/// <param name="defaults">The default values for <paramref name="pattern"/>. Optional.</param>
|
||||||
/// <param name="requiredKeys">Keys used to determine if the ambient values apply. Optional.</param>
|
/// <param name="requiredKeys">Keys used to determine if the ambient values apply. Optional.</param>
|
||||||
/// <param name="parameterPolicyFactory">The <see cref="ParameterPolicyFactory"/>.</param>
|
/// <param name="parameterPolicies">
|
||||||
|
/// A list of (<see cref="string"/>, <see cref="IParameterPolicy"/>) pairs to evalute when producing a URI.
|
||||||
|
/// </param>
|
||||||
public TemplateBinder(
|
public TemplateBinder(
|
||||||
UrlEncoder urlEncoder,
|
UrlEncoder urlEncoder,
|
||||||
ObjectPool<UriBuildingContext> pool,
|
ObjectPool<UriBuildingContext> pool,
|
||||||
RoutePattern pattern,
|
RoutePattern pattern,
|
||||||
RouteValueDictionary defaults,
|
RouteValueDictionary defaults,
|
||||||
IEnumerable<string> requiredKeys,
|
IEnumerable<string> requiredKeys,
|
||||||
ParameterPolicyFactory parameterPolicyFactory)
|
IEnumerable<(string parameterName, IParameterPolicy policy)> parameterPolicies)
|
||||||
{
|
{
|
||||||
if (urlEncoder == null)
|
if (urlEncoder == null)
|
||||||
{
|
{
|
||||||
|
|
@ -84,7 +87,6 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
_pool = pool;
|
_pool = pool;
|
||||||
_pattern = pattern;
|
_pattern = pattern;
|
||||||
_defaults = defaults;
|
_defaults = defaults;
|
||||||
_parameterPolicyFactory = parameterPolicyFactory;
|
|
||||||
_requiredKeys = requiredKeys?.ToArray() ?? Array.Empty<string>();
|
_requiredKeys = requiredKeys?.ToArray() ?? Array.Empty<string>();
|
||||||
|
|
||||||
for (var i = 0; i < _requiredKeys.Length; i++)
|
for (var i = 0; i < _requiredKeys.Length; i++)
|
||||||
|
|
@ -101,13 +103,21 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
// Any default that doesn't have a corresponding parameter is a 'filter' and if a value
|
// 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.
|
// is provided for that 'filter' it must match the value in defaults.
|
||||||
var filters = new RouteValueDictionary(_defaults);
|
var filters = new RouteValueDictionary(_defaults);
|
||||||
foreach (var parameter in _pattern.Parameters)
|
for (var i = 0; i < pattern.Parameters.Count; i++)
|
||||||
{
|
{
|
||||||
filters.Remove(parameter.Name);
|
filters.Remove(pattern.Parameters[i].Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
_filters = filters.ToArray();
|
_filters = filters.ToArray();
|
||||||
|
|
||||||
|
_constraints = parameterPolicies
|
||||||
|
?.Where(p => p.policy is IRouteConstraint)
|
||||||
|
.Select(p => (p.parameterName, (IRouteConstraint)p.policy))
|
||||||
|
.ToArray() ?? Array.Empty<(string, IRouteConstraint)>();
|
||||||
|
_parameterTransformers = parameterPolicies
|
||||||
|
?.Where(p => p.policy is IOutboundParameterTransformer)
|
||||||
|
.Select(p => (p.parameterName, (IOutboundParameterTransformer)p.policy))
|
||||||
|
.ToArray() ?? Array.Empty<(string, IOutboundParameterTransformer)>();
|
||||||
|
|
||||||
_slots = AssignSlots(_pattern, _filters);
|
_slots = AssignSlots(_pattern, _filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,6 +361,29 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 1.5: Process constraints
|
||||||
|
//
|
||||||
|
// Processes the constraints **if** they were passed in to the TemplateBinder constructor.
|
||||||
|
// Returns true on success
|
||||||
|
// Returns false + sets the name/constraint for logging on failure.
|
||||||
|
public bool TryProcessConstraints(HttpContext httpContext, RouteValueDictionary combinedValues, out string parameterName, out IRouteConstraint constraint)
|
||||||
|
{
|
||||||
|
var constraints = _constraints;
|
||||||
|
for (var i = 0; i < constraints.Length; i++)
|
||||||
|
{
|
||||||
|
(parameterName, constraint) = constraints[i];
|
||||||
|
|
||||||
|
if (!constraint.Match(httpContext, NullRouter.Instance, parameterName, combinedValues, RouteDirection.UrlGeneration))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterName = null;
|
||||||
|
constraint = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 2: If the route is a match generate the appropriate URI
|
// Step 2: If the route is a match generate the appropriate URI
|
||||||
public string BindValues(RouteValueDictionary acceptedValues)
|
public string BindValues(RouteValueDictionary acceptedValues)
|
||||||
{
|
{
|
||||||
|
|
@ -398,6 +431,18 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
|
|
||||||
private bool TryBindValuesCore(UriBuildingContext context, RouteValueDictionary acceptedValues)
|
private bool TryBindValuesCore(UriBuildingContext context, RouteValueDictionary acceptedValues)
|
||||||
{
|
{
|
||||||
|
// If we have any output parameter transformers, allow them a chance to influence the parameter values
|
||||||
|
// before we build the URI.
|
||||||
|
var parameterTransformers = _parameterTransformers;
|
||||||
|
for (var i = 0; i < parameterTransformers.Length; i++)
|
||||||
|
{
|
||||||
|
(var parameterName, var transformer) = parameterTransformers[i];
|
||||||
|
if (acceptedValues.TryGetValue(parameterName, out var value))
|
||||||
|
{
|
||||||
|
acceptedValues[parameterName] = transformer.TransformOutbound(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < _pattern.PathSegments.Count; i++)
|
for (var i = 0; i < _pattern.PathSegments.Count; i++)
|
||||||
{
|
{
|
||||||
Debug.Assert(context.BufferState == SegmentState.Beginning);
|
Debug.Assert(context.BufferState == SegmentState.Beginning);
|
||||||
|
|
@ -460,7 +505,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
// Example: template = {id}.{format?}. parameters: id=5
|
// Example: template = {id}.{format?}. parameters: id=5
|
||||||
// In this case after we have generated "5.", we wont find any value
|
// In this case after we have generated "5.", we wont find any value
|
||||||
// for format, so we remove '.' and generate 5.
|
// for format, so we remove '.' and generate 5.
|
||||||
if (!context.Accept(converted, parameterPart.EncodeSlashes, GetParameterTransformer(parameterPart)))
|
if (!context.Accept(converted, parameterPart.EncodeSlashes))
|
||||||
{
|
{
|
||||||
if (j != 0 && parameterPart.IsOptional && (separatorPart = segment.Parts[j - 1] as RoutePatternSeparatorPart) != null)
|
if (j != 0 && parameterPart.IsOptional && (separatorPart = segment.Parts[j - 1] as RoutePatternSeparatorPart) != null)
|
||||||
{
|
{
|
||||||
|
|
@ -505,26 +550,6 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IParameterTransformer GetParameterTransformer(RoutePatternParameterPart parameterPart)
|
|
||||||
{
|
|
||||||
if (_parameterPolicyFactory == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < parameterPart.ParameterPolicies.Count; i++)
|
|
||||||
{
|
|
||||||
// Use the first parameter transformer
|
|
||||||
var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, parameterPart.ParameterPolicies[i]);
|
|
||||||
if (parameterPolicy is IParameterTransformer parameterTransformer)
|
|
||||||
{
|
|
||||||
return parameterTransformer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool AddQueryKeyValueToContext(UriBuildingContext context, string key, object value, bool wroteFirst)
|
private bool AddQueryKeyValueToContext(UriBuildingContext context, string key, object value, bool wroteFirst)
|
||||||
{
|
{
|
||||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,54 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Assert.Equal(string.Empty, result.query.ToUriComponent());
|
Assert.Equal(string.Empty, result.query.ToUriComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for https://github.com/aspnet/Routing/issues/802
|
||||||
|
[Fact]
|
||||||
|
public void TryProcessTemplate_GeneratesLowercaseUrl_Includes_BufferedValues_SetOnRouteOptions()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
|
||||||
|
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
||||||
|
var httpContext = CreateHttpContext();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var success = linkGenerator.TryProcessTemplate(
|
||||||
|
httpContext: httpContext,
|
||||||
|
endpoint: endpoint,
|
||||||
|
ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
|
||||||
|
explicitValues: new RouteValueDictionary(new { id = "18" }),
|
||||||
|
options: null,
|
||||||
|
out var result);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(success);
|
||||||
|
Assert.Equal("/foo/bar/18", result.path.ToUriComponent());
|
||||||
|
Assert.Equal(string.Empty, result.query.ToUriComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regression test for https://github.com/aspnet/Routing/issues/802
|
||||||
|
[Fact]
|
||||||
|
public void TryProcessTemplate_ParameterPolicy_Includes_BufferedValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}", policies: new { bar = new SlugifyParameterTransformer(), });
|
||||||
|
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
||||||
|
var httpContext = CreateHttpContext();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var success = linkGenerator.TryProcessTemplate(
|
||||||
|
httpContext: httpContext,
|
||||||
|
endpoint: endpoint,
|
||||||
|
ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
|
||||||
|
explicitValues: new RouteValueDictionary(new { id = "18" }),
|
||||||
|
options: null,
|
||||||
|
out var result);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(success);
|
||||||
|
Assert.Equal("/foo/bar/18", result.path.ToUriComponent());
|
||||||
|
Assert.Equal(string.Empty, result.query.ToUriComponent());
|
||||||
|
}
|
||||||
|
|
||||||
// Regression test for aspnet/Routing#435
|
// Regression test for aspnet/Routing#435
|
||||||
//
|
//
|
||||||
// In this issue we used to lowercase URLs after parameters were encoded, meaning that if a character needed
|
// In this issue we used to lowercase URLs after parameters were encoded, meaning that if a character needed
|
||||||
|
|
@ -531,7 +579,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
"{p1}/{p2}",
|
"{p1}/{p2}",
|
||||||
defaults: new { p2 = "catchall" },
|
defaults: new { p2 = "catchall" },
|
||||||
constraints: new { p2 = "\\d{4}" });
|
policies: new { p2 = "\\d{4}" });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -555,7 +603,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
"{p1}/{p2}",
|
"{p1}/{p2}",
|
||||||
defaults: new { p2 = "catchall" },
|
defaults: new { p2 = "catchall" },
|
||||||
constraints: new { p2 = new RegexRouteConstraint("\\d{4}"), });
|
policies: new { p2 = new RegexRouteConstraint("\\d{4}"), });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -581,7 +629,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
"{p1}/{*p2}",
|
"{p1}/{*p2}",
|
||||||
defaults: new { p2 = "catchall" },
|
defaults: new { p2 = "catchall" },
|
||||||
constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -605,7 +653,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
"{p1}/{*p2}",
|
"{p1}/{*p2}",
|
||||||
defaults: new { p2 = "catchall" },
|
defaults: new { p2 = "catchall" },
|
||||||
constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -642,7 +690,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
"{p1}/{p2}",
|
"{p1}/{p2}",
|
||||||
defaults: new { p2 = "catchall" },
|
defaults: new { p2 = "catchall" },
|
||||||
constraints: new { p2 = target.Object });
|
policies: new { p2 = target.Object });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -674,7 +722,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "slug/Home/Store",
|
template: "slug/Home/Store",
|
||||||
defaults: new { controller = "Home", action = "Store" },
|
defaults: new { controller = "Home", action = "Store" },
|
||||||
constraints: new { c = constraint });
|
policies: new { c = constraint });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(
|
var httpContext = CreateHttpContext(
|
||||||
ambientValues: new { controller = "Home", action = "Blog", extra = "42" });
|
ambientValues: new { controller = "Home", action = "Blog", extra = "42" });
|
||||||
|
|
@ -708,7 +756,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "slug/Home/Store",
|
template: "slug/Home/Store",
|
||||||
defaults: new { controller = "Home", action = "Store", otherthing = "17" },
|
defaults: new { controller = "Home", action = "Store", otherthing = "17" },
|
||||||
constraints: new { c = constraint });
|
policies: new { c = constraint });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
|
||||||
var expectedValues = new RouteValueDictionary(
|
var expectedValues = new RouteValueDictionary(
|
||||||
|
|
@ -740,7 +788,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "slug/{controller}/{action}",
|
template: "slug/{controller}/{action}",
|
||||||
defaults: new { action = "Index" },
|
defaults: new { action = "Index" },
|
||||||
constraints: new { c = constraint, });
|
policies: new { c = constraint, });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
|
||||||
var expectedValues = new RouteValueDictionary(
|
var expectedValues = new RouteValueDictionary(
|
||||||
|
|
@ -772,7 +820,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "slug/Home/Store",
|
template: "slug/Home/Store",
|
||||||
defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
|
defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
|
||||||
constraints: new { c = constraint, });
|
policies: new { c = constraint, });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(
|
var httpContext = CreateHttpContext(
|
||||||
ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" });
|
ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" });
|
||||||
|
|
@ -806,7 +854,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{id:int}",
|
template: "Home/Index/{id:int}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { });
|
policies: new { });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||||
|
|
||||||
|
|
@ -832,7 +880,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{id}",
|
template: "Home/Index/{id}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { id = "int" });
|
policies: new { id = "int" });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -858,7 +906,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{id:int?}",
|
template: "Home/Index/{id:int?}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { });
|
policies: new { });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||||
|
|
||||||
|
|
@ -884,7 +932,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{id?}",
|
template: "Home/Index/{id?}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { id = "int" });
|
policies: new { id = "int" });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -910,7 +958,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{id?}",
|
template: "Home/Index/{id?}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { id = "int" });
|
policies: new { id = "int" });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
@ -936,7 +984,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{id:int:range(1,20)}",
|
template: "Home/Index/{id:int:range(1,20)}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { });
|
policies: new { });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||||
|
|
||||||
|
|
@ -964,7 +1012,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{id:int:range(1,20)}",
|
template: "Home/Index/{id:int:range(1,20)}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { });
|
policies: new { });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||||
|
|
||||||
|
|
@ -989,7 +1037,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
template: "Home/Index/{name}",
|
template: "Home/Index/{name}",
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { name = constraint });
|
policies: new { name = constraint });
|
||||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -298,11 +298,11 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UpperCaseParameterTransform : IParameterTransformer
|
private class UpperCaseParameterTransform : IOutboundParameterTransformer
|
||||||
{
|
{
|
||||||
public string Transform(string value)
|
public string TransformOutbound(object value)
|
||||||
{
|
{
|
||||||
return value?.ToUpperInvariant();
|
return value?.ToString()?.ToUpperInvariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,6 +329,29 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Assert.Equal("/HOME/Test", link);
|
Assert.Equal("/HOME/Test", link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetLink_ParameterTransformer_ForQueryString()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", policies: new { c = new UpperCaseParameterTransform(), });
|
||||||
|
|
||||||
|
var routeOptions = new RouteOptions();
|
||||||
|
routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||||
|
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform());
|
||||||
|
};
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(routeOptions, configure, endpoint);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test", c = "hithere", });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/HOME/Test?c=HITHERE", link);
|
||||||
|
}
|
||||||
|
|
||||||
// Includes characters that need to be encoded
|
// Includes characters that need to be encoded
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetPathByAddress_WithHttpContext_WithPathBaseAndFragment()
|
public void GetPathByAddress_WithHttpContext_WithPathBaseAndFragment()
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public static RouteEndpoint CreateRouteEndpoint(
|
public static RouteEndpoint CreateRouteEndpoint(
|
||||||
string template,
|
string template,
|
||||||
object defaults = null,
|
object defaults = null,
|
||||||
object constraints = null,
|
object policies = null,
|
||||||
object requiredValues = null,
|
object requiredValues = null,
|
||||||
int order = 0,
|
int order = 0,
|
||||||
string displayName = null,
|
string displayName = null,
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
return new RouteEndpoint(
|
return new RouteEndpoint(
|
||||||
TestConstants.EmptyRequestDelegate,
|
TestConstants.EmptyRequestDelegate,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints),
|
RoutePatternFactory.Parse(template, defaults, policies),
|
||||||
order,
|
order,
|
||||||
new EndpointMetadataCollection(d),
|
new EndpointMetadataCollection(d),
|
||||||
displayName);
|
displayName);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
using Microsoft.AspNetCore.Routing.Constraints;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||||
|
|
@ -1294,12 +1295,8 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
||||||
public void BindValues_ParameterTransformer()
|
public void BindValues_ParameterTransformer()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var routeOptions = new RouteOptions();
|
|
||||||
routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
|
||||||
var parameterPolicyFactory = new DefaultParameterPolicyFactory(
|
|
||||||
Options.Create(routeOptions),
|
|
||||||
new ServiceCollection().BuildServiceProvider());
|
|
||||||
var expected = "/ConventionalTransformerRoute/conventional-transformer/Param/my-value";
|
var expected = "/ConventionalTransformerRoute/conventional-transformer/Param/my-value";
|
||||||
|
|
||||||
var template = "ConventionalTransformerRoute/conventional-transformer/Param/{param:length(500):slugify?}";
|
var template = "ConventionalTransformerRoute/conventional-transformer/Param/{param:length(500):slugify?}";
|
||||||
var defaults = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
|
var defaults = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
|
||||||
var ambientValues = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
|
var ambientValues = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
|
||||||
|
|
@ -1310,7 +1307,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
defaults,
|
defaults,
|
||||||
requiredKeys: defaults.Keys,
|
requiredKeys: defaults.Keys,
|
||||||
parameterPolicyFactory);
|
parameterPolicies: new (string, IParameterPolicy)[] { ("param", new LengthRouteConstraint(500)), ("param", new SlugifyParameterTransformer()), });
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.GetValues(ambientValues, explicitValues);
|
var result = binder.GetValues(ambientValues, explicitValues);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.TestObjects
|
namespace Microsoft.AspNetCore.Routing.TestObjects
|
||||||
{
|
{
|
||||||
public class SlugifyParameterTransformer : IParameterTransformer
|
public class SlugifyParameterTransformer : IOutboundParameterTransformer
|
||||||
{
|
{
|
||||||
public string Transform(string value)
|
public string TransformOutbound(object value)
|
||||||
{
|
{
|
||||||
// Slugify value
|
// Slugify value
|
||||||
return Regex.Replace(value, "([a-z])([A-Z])", "$1-$2").ToLower();
|
return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue