Skip to content

Commit 409bb34

Browse files
Copilotcaptainsafia
andcommitted
Cache HasDisplayAttribute and optimize FormatComplexKey performance
Co-authored-by: captainsafia <[email protected]>
1 parent d0e29c8 commit 409bb34

File tree

2 files changed

+45
-16
lines changed

2 files changed

+45
-16
lines changed

src/Http/Http.Abstractions/src/Validation/ValidatablePropertyInfo.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Http.Validation;
1616
public abstract class ValidatablePropertyInfo : IValidatableInfo
1717
{
1818
private RequiredAttribute? _requiredAttribute;
19+
private readonly bool _hasDisplayAttribute;
1920

2021
/// <summary>
2122
/// Creates a new instance of <see cref="ValidatablePropertyInfo"/>.
@@ -31,6 +32,10 @@ protected ValidatablePropertyInfo(
3132
PropertyType = propertyType;
3233
Name = name;
3334
DisplayName = displayName;
35+
36+
// Cache the HasDisplayAttribute result to avoid repeated reflection calls
37+
var property = DeclaringType.GetProperty(Name);
38+
_hasDisplayAttribute = property is not null && HasDisplayAttribute(property);
3439
}
3540

3641
/// <summary>
@@ -81,8 +86,7 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
8186

8287
// Format the display name and member name according to JsonPropertyName attribute first, then naming policy
8388
// If the property has a [Display] attribute (either on property or record parameter), use DisplayName directly without formatting
84-
var hasDisplayAttribute = HasDisplayAttribute(property);
85-
context.ValidationContext.DisplayName = hasDisplayAttribute
89+
context.ValidationContext.DisplayName = _hasDisplayAttribute
8690
? DisplayName
8791
: GetJsonPropertyName(DisplayName, property, context.SerializerOptions?.PropertyNamingPolicy);
8892
context.ValidationContext.MemberName = memberName;

src/Http/Http.Abstractions/src/Validation/ValidateContext.cs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Http.Validation;
1414
[Experimental("ASP0029", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")]
1515
public sealed class ValidateContext
1616
{
17+
private JsonNamingPolicy? _cachedNamingPolicy;
18+
private bool _namingPolicyCached;
1719
/// <summary>
1820
/// Gets or sets the validation context used for validating objects that implement <see cref="IValidatableObject"/> or have <see cref="ValidationAttribute"/>.
1921
/// This context provides access to service provider and other validation metadata.
@@ -67,7 +69,34 @@ public sealed class ValidateContext
6769
/// When available, property names in validation errors will be formatted according to the
6870
/// PropertyNamingPolicy and JsonPropertyName attributes.
6971
/// </summary>
70-
public JsonSerializerOptions? SerializerOptions { get; set; }
72+
public JsonSerializerOptions? SerializerOptions
73+
{
74+
get => _serializerOptions;
75+
set
76+
{
77+
_serializerOptions = value;
78+
// Invalidate cache when SerializerOptions changes
79+
_namingPolicyCached = false;
80+
_cachedNamingPolicy = null;
81+
}
82+
}
83+
private JsonSerializerOptions? _serializerOptions;
84+
85+
/// <summary>
86+
/// Gets the cached naming policy from SerializerOptions to avoid repeated property access.
87+
/// </summary>
88+
private JsonNamingPolicy? CachedNamingPolicy
89+
{
90+
get
91+
{
92+
if (!_namingPolicyCached)
93+
{
94+
_cachedNamingPolicy = _serializerOptions?.PropertyNamingPolicy;
95+
_namingPolicyCached = true;
96+
}
97+
return _cachedNamingPolicy;
98+
}
99+
}
71100

72101
internal void AddValidationError(string key, string[] errors)
73102
{
@@ -114,7 +143,8 @@ internal void AddOrExtendValidationError(string key, string error)
114143

115144
private string FormatKey(string key)
116145
{
117-
if (string.IsNullOrEmpty(key) || SerializerOptions?.PropertyNamingPolicy is null)
146+
var namingPolicy = CachedNamingPolicy;
147+
if (string.IsNullOrEmpty(key) || namingPolicy is null)
118148
{
119149
return key;
120150
}
@@ -123,21 +153,20 @@ private string FormatKey(string key)
123153
// apply the naming policy to each part of the path
124154
if (key.Contains('.') || key.Contains('['))
125155
{
126-
return FormatComplexKey(key);
156+
return FormatComplexKey(key, namingPolicy);
127157
}
128158

129159
// Apply the naming policy directly
130-
return SerializerOptions.PropertyNamingPolicy.ConvertName(key);
160+
return namingPolicy.ConvertName(key);
131161
}
132162

133-
private string FormatComplexKey(string key)
163+
private static string FormatComplexKey(string key, JsonNamingPolicy namingPolicy)
134164
{
135165
// Use a more direct approach for complex keys with dots and array indices
136166
var result = new System.Text.StringBuilder();
137167
int lastIndex = 0;
138168
int i = 0;
139169
bool inBracket = false;
140-
var propertyNamingPolicy = SerializerOptions?.PropertyNamingPolicy;
141170

142171
while (i < key.Length)
143172
{
@@ -149,9 +178,7 @@ private string FormatComplexKey(string key)
149178
if (i > lastIndex)
150179
{
151180
string segment = key.Substring(lastIndex, i - lastIndex);
152-
string formattedSegment = propertyNamingPolicy is not null
153-
? propertyNamingPolicy.ConvertName(segment)
154-
: segment;
181+
string formattedSegment = namingPolicy.ConvertName(segment);
155182
result.Append(formattedSegment);
156183
}
157184

@@ -178,9 +205,7 @@ private string FormatComplexKey(string key)
178205
if (i > lastIndex)
179206
{
180207
string segment = key.Substring(lastIndex, i - lastIndex);
181-
string formattedSegment = propertyNamingPolicy is not null
182-
? propertyNamingPolicy.ConvertName(segment)
183-
: segment;
208+
string formattedSegment = namingPolicy.ConvertName(segment);
184209
result.Append(formattedSegment);
185210
}
186211
result.Append(c);
@@ -194,9 +219,9 @@ private string FormatComplexKey(string key)
194219
if (lastIndex < key.Length)
195220
{
196221
string segment = key.Substring(lastIndex);
197-
if (!inBracket && propertyNamingPolicy is not null)
222+
if (!inBracket)
198223
{
199-
segment = propertyNamingPolicy.ConvertName(segment);
224+
segment = namingPolicy.ConvertName(segment);
200225
}
201226
result.Append(segment);
202227
}

0 commit comments

Comments
 (0)