Skip to content
37 changes: 21 additions & 16 deletions src/Http/Routing/src/ValidationEndpointFilterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ namespace Microsoft.AspNetCore.Http.Validation;

internal static class ValidationEndpointFilterFactory
{
// A small struct to hold the validatable parameter details to avoid allocating arrays for parameters that don't need validation
private readonly record struct ValidatableParameterEntry(int Index, IValidatableInfo Parameter, string DisplayName);

public static EndpointFilterDelegate Create(EndpointFilterFactoryContext context, EndpointFilterDelegate next)
{
var parameters = context.MethodInfo.GetParameters();
Expand All @@ -25,12 +28,10 @@ public static EndpointFilterDelegate Create(EndpointFilterFactoryContext context

var serviceProviderIsService = context.ApplicationServices.GetService<IServiceProviderIsService>();

var parameterCount = parameters.Length;
var validatableParameters = new IValidatableInfo[parameterCount];
var parameterDisplayNames = new string[parameterCount];
var hasValidatableParameters = false;
// Use a list to only store validatable parameters instead of arrays for all parameters
List<ValidatableParameterEntry>? validatableParameters = null;

for (var i = 0; i < parameterCount; i++)
for (var i = 0; i < parameters.Length; i++)
{
// Ignore parameters that are resolved from the DI container.
if (IsServiceParameter(parameters[i], serviceProviderIsService))
Expand All @@ -40,13 +41,15 @@ public static EndpointFilterDelegate Create(EndpointFilterFactoryContext context

if (options.TryGetValidatableParameterInfo(parameters[i], out var validatableParameter))
{
validatableParameters[i] = validatableParameter;
parameterDisplayNames[i] = GetDisplayName(parameters[i]);
hasValidatableParameters = true;
validatableParameters ??= [];
validatableParameters.Add(new ValidatableParameterEntry(
i,
validatableParameter,
GetDisplayName(parameters[i])));
}
}

if (!hasValidatableParameters)
if (validatableParameters is null || validatableParameters.Count == 0)
{
return next;
}
Expand All @@ -55,18 +58,20 @@ public static EndpointFilterDelegate Create(EndpointFilterFactoryContext context
{
ValidateContext? validateContext = null;

for (var i = 0; i < context.Arguments.Count; i++)
foreach (var entry in validatableParameters)
{
var validatableParameter = validatableParameters[i];
var displayName = parameterDisplayNames[i];
if (entry.Index >= context.Arguments.Count)
{
break;
}

var argument = context.Arguments[i];
if (argument is null || validatableParameter is null)
var argument = context.Arguments[entry.Index];
if (argument is null)
{
continue;
}

var validationContext = new ValidationContext(argument, displayName, context.HttpContext.RequestServices, items: null);
var validationContext = new ValidationContext(argument, entry.DisplayName, context.HttpContext.RequestServices, items: null);

if (validateContext == null)
{
Expand All @@ -81,7 +86,7 @@ public static EndpointFilterDelegate Create(EndpointFilterFactoryContext context
validateContext.ValidationContext = validationContext;
}

await validatableParameter.ValidateAsync(argument, validateContext, context.HttpContext.RequestAborted);
await entry.Parameter.ValidateAsync(argument, validateContext, context.HttpContext.RequestAborted);
}

if (validateContext is { ValidationErrors.Count: > 0 })
Expand Down
Loading