Updates to Parameter Transformer
- Rename -> IOutboundParameterTransformer - Make it operate on object - Implementing caching for constraints/tranformers for link generation (cached as part of TemplateBinder)
This commit is contained in:
parent
df27b3ec10
commit
a657c3bdf2
|
|
@ -4,15 +4,16 @@
|
|||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <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>
|
||||
public interface IParameterTransformer : IParameterPolicy
|
||||
public interface IOutboundParameterTransformer : IParameterPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Transforms the specified parameter value.
|
||||
/// Transforms the specified route value to a string for inclusion in a URI.
|
||||
/// </summary>
|
||||
/// <param name="value">The parameter value to transform.</param>
|
||||
/// <param name="value">The route value to transform.</param>
|
||||
/// <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)
|
||||
{
|
||||
// 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(
|
||||
UrlEncoder.Default,
|
||||
_uriBuildingContextPool,
|
||||
endpoint.RoutePattern,
|
||||
new RouteValueDictionary(endpoint.RoutePattern.Defaults),
|
||||
endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>()?.RequiredValues.Keys,
|
||||
_parameterPolicyFactory);
|
||||
policies);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
|
|
@ -312,11 +339,10 @@ namespace Microsoft.AspNetCore.Routing
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!MatchesConstraints(httpContext, endpoint, templateValuesResult.CombinedValues))
|
||||
if (!templateBinder.TryProcessConstraints(httpContext, templateValuesResult.CombinedValues, out var parameterName, out var constraint))
|
||||
{
|
||||
result = default;
|
||||
|
||||
// MatchesConstraints does its own logging, so we're not logging here.
|
||||
Log.TemplateFailedConstraint(_logger, endpoint, parameterName, constraint, templateValuesResult.CombinedValues);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -330,36 +356,6 @@ namespace Microsoft.AspNetCore.Routing
|
|||
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
|
||||
public static RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
|
||||
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))
|
||||
{
|
||||
|
|
@ -80,31 +80,31 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
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
|
||||
if (LowercaseUrls)
|
||||
{
|
||||
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
|
||||
{
|
||||
_path.Append(_buffer[i].Value);
|
||||
_path.Append(bufferValue);
|
||||
}
|
||||
}
|
||||
_buffer.Clear();
|
||||
buffer.Clear();
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
{
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
private readonly ObjectPool<UriBuildingContext> _pool;
|
||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
||||
|
||||
private readonly (string parameterName, IRouteConstraint constraint)[] _constraints;
|
||||
private readonly RouteValueDictionary _defaults;
|
||||
private readonly KeyValuePair<string, object>[] _filters;
|
||||
private readonly (string parameterName, IOutboundParameterTransformer transformer)[] _parameterTransformers;
|
||||
private readonly RoutePattern _pattern;
|
||||
private readonly string[] _requiredKeys;
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
ObjectPool<UriBuildingContext> pool,
|
||||
RouteTemplate template,
|
||||
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="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="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(
|
||||
UrlEncoder urlEncoder,
|
||||
ObjectPool<UriBuildingContext> pool,
|
||||
RoutePattern pattern,
|
||||
RouteValueDictionary defaults,
|
||||
IEnumerable<string> requiredKeys,
|
||||
ParameterPolicyFactory parameterPolicyFactory)
|
||||
IEnumerable<(string parameterName, IParameterPolicy policy)> parameterPolicies)
|
||||
{
|
||||
if (urlEncoder == null)
|
||||
{
|
||||
|
|
@ -84,7 +87,6 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
_pool = pool;
|
||||
_pattern = pattern;
|
||||
_defaults = defaults;
|
||||
_parameterPolicyFactory = parameterPolicyFactory;
|
||||
_requiredKeys = requiredKeys?.ToArray() ?? Array.Empty<string>();
|
||||
|
||||
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
|
||||
// is provided for that 'filter' it must match the value in 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();
|
||||
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
public string BindValues(RouteValueDictionary acceptedValues)
|
||||
{
|
||||
|
|
@ -398,6 +431,18 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
|
||||
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++)
|
||||
{
|
||||
Debug.Assert(context.BufferState == SegmentState.Beginning);
|
||||
|
|
@ -460,7 +505,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
// 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, parameterPart.EncodeSlashes, GetParameterTransformer(parameterPart)))
|
||||
if (!context.Accept(converted, parameterPart.EncodeSlashes))
|
||||
{
|
||||
if (j != 0 && parameterPart.IsOptional && (separatorPart = segment.Parts[j - 1] as RoutePatternSeparatorPart) != null)
|
||||
{
|
||||
|
|
@ -505,26 +550,6 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
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)
|
||||
{
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
|
|
|
|||
|
|
@ -277,6 +277,54 @@ namespace Microsoft.AspNetCore.Routing
|
|||
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
|
||||
//
|
||||
// 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(
|
||||
"{p1}/{p2}",
|
||||
defaults: new { p2 = "catchall" },
|
||||
constraints: new { p2 = "\\d{4}" });
|
||||
policies: new { p2 = "\\d{4}" });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -555,7 +603,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
"{p1}/{p2}",
|
||||
defaults: new { p2 = "catchall" },
|
||||
constraints: new { p2 = new RegexRouteConstraint("\\d{4}"), });
|
||||
policies: new { p2 = new RegexRouteConstraint("\\d{4}"), });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -581,7 +629,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
"{p1}/{*p2}",
|
||||
defaults: new { p2 = "catchall" },
|
||||
constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
||||
policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -605,7 +653,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
"{p1}/{*p2}",
|
||||
defaults: new { p2 = "catchall" },
|
||||
constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
||||
policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -642,7 +690,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
"{p1}/{p2}",
|
||||
defaults: new { p2 = "catchall" },
|
||||
constraints: new { p2 = target.Object });
|
||||
policies: new { p2 = target.Object });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -674,7 +722,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "slug/Home/Store",
|
||||
defaults: new { controller = "Home", action = "Store" },
|
||||
constraints: new { c = constraint });
|
||||
policies: new { c = constraint });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(
|
||||
ambientValues: new { controller = "Home", action = "Blog", extra = "42" });
|
||||
|
|
@ -708,7 +756,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "slug/Home/Store",
|
||||
defaults: new { controller = "Home", action = "Store", otherthing = "17" },
|
||||
constraints: new { c = constraint });
|
||||
policies: new { c = constraint });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
|
||||
var expectedValues = new RouteValueDictionary(
|
||||
|
|
@ -740,7 +788,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "slug/{controller}/{action}",
|
||||
defaults: new { action = "Index" },
|
||||
constraints: new { c = constraint, });
|
||||
policies: new { c = constraint, });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
|
||||
var expectedValues = new RouteValueDictionary(
|
||||
|
|
@ -772,7 +820,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "slug/Home/Store",
|
||||
defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
|
||||
constraints: new { c = constraint, });
|
||||
policies: new { c = constraint, });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(
|
||||
ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" });
|
||||
|
|
@ -806,7 +854,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{id:int}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { });
|
||||
policies: new { });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||
|
||||
|
|
@ -832,7 +880,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{id}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { id = "int" });
|
||||
policies: new { id = "int" });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -858,7 +906,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{id:int?}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { });
|
||||
policies: new { });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||
|
||||
|
|
@ -884,7 +932,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{id?}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { id = "int" });
|
||||
policies: new { id = "int" });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -910,7 +958,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{id?}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { id = "int" });
|
||||
policies: new { id = "int" });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { });
|
||||
|
||||
|
|
@ -936,7 +984,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{id:int:range(1,20)}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { });
|
||||
policies: new { });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||
|
||||
|
|
@ -964,7 +1012,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{id:int:range(1,20)}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { });
|
||||
policies: new { });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
|
||||
|
||||
|
|
@ -989,7 +1037,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||
template: "Home/Index/{name}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { name = constraint });
|
||||
policies: new { name = constraint });
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[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
|
||||
[Fact]
|
||||
public void GetPathByAddress_WithHttpContext_WithPathBaseAndFragment()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
public static RouteEndpoint CreateRouteEndpoint(
|
||||
string template,
|
||||
object defaults = null,
|
||||
object constraints = null,
|
||||
object policies = null,
|
||||
object requiredValues = null,
|
||||
int order = 0,
|
||||
string displayName = null,
|
||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
return new RouteEndpoint(
|
||||
TestConstants.EmptyRequestDelegate,
|
||||
RoutePatternFactory.Parse(template, defaults, constraints),
|
||||
RoutePatternFactory.Parse(template, defaults, policies),
|
||||
order,
|
||||
new EndpointMetadataCollection(d),
|
||||
displayName);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||
|
|
@ -1294,12 +1295,8 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
public void BindValues_ParameterTransformer()
|
||||
{
|
||||
// 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 template = "ConventionalTransformerRoute/conventional-transformer/Param/{param:length(500):slugify?}";
|
||||
var defaults = 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),
|
||||
defaults,
|
||||
requiredKeys: defaults.Keys,
|
||||
parameterPolicyFactory);
|
||||
parameterPolicies: new (string, IParameterPolicy)[] { ("param", new LengthRouteConstraint(500)), ("param", new SlugifyParameterTransformer()), });
|
||||
|
||||
// Act
|
||||
var result = binder.GetValues(ambientValues, explicitValues);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
// 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.RegularExpressions;
|
||||
|
||||
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
|
||||
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