Skip to content

Small improvements to reduce allocations in validation filter logic #62056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ??= new();
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