Skip to content

Commit 4afa2bb

Browse files
committed
Move key formatting to ValidatableTypeInfo
1 parent 3cab762 commit 4afa2bb

File tree

3 files changed

+19
-344
lines changed

3 files changed

+19
-344
lines changed

src/Http/Http.Abstractions/src/Validation/ValidatableTypeInfo.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,17 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
106106
// Create a validation error for each member name that is provided
107107
foreach (var memberName in validationResult.MemberNames)
108108
{
109+
// Format the member name using JsonSerializerOptions naming policy if available
110+
// Note: we don't respect [JsonPropertyName] here because we have no context of the property being validated.
111+
var formattedMemberName = memberName;
112+
if (context.SerializerOptions?.PropertyNamingPolicy != null)
113+
{
114+
formattedMemberName = context.SerializerOptions.PropertyNamingPolicy.ConvertName(memberName);
115+
}
116+
109117
var key = string.IsNullOrEmpty(originalPrefix) ?
110-
memberName :
111-
$"{originalPrefix}.{memberName}";
118+
formattedMemberName :
119+
$"{originalPrefix}.{formattedMemberName}";
112120
context.AddOrExtendValidationError(key, validationResult.ErrorMessage);
113121
}
114122

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

Lines changed: 9 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ 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;
1917
/// <summary>
2018
/// Gets or sets the validation context used for validating objects that implement <see cref="IValidatableObject"/> or have <see cref="ValidationAttribute"/>.
2119
/// This context provides access to service provider and other validation metadata.
@@ -63,55 +61,33 @@ public sealed class ValidateContext
6361
/// This is used to prevent stack overflows from circular references.
6462
/// </summary>
6563
public int CurrentDepth { get; set; }
66-
64+
6765
/// <summary>
6866
/// Gets or sets the JSON serializer options to use for property name formatting.
6967
/// When available, property names in validation errors will be formatted according to the
7068
/// PropertyNamingPolicy and JsonPropertyName attributes.
7169
/// </summary>
72-
public JsonSerializerOptions? SerializerOptions
73-
{
70+
public JsonSerializerOptions? SerializerOptions
71+
{
7472
get => _serializerOptions;
75-
set
76-
{
77-
_serializerOptions = value;
78-
// Invalidate cache when SerializerOptions changes
79-
_namingPolicyCached = false;
80-
_cachedNamingPolicy = null;
81-
}
73+
set => _serializerOptions = value;
8274
}
8375
private JsonSerializerOptions? _serializerOptions;
8476

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-
}
100-
10177
internal void AddValidationError(string key, string[] errors)
10278
{
10379
ValidationErrors ??= [];
10480

105-
var formattedKey = FormatKey(key);
81+
var formattedKey = key;
10682
ValidationErrors[formattedKey] = errors;
10783
}
10884

10985
internal void AddOrExtendValidationErrors(string key, string[] errors)
11086
{
11187
ValidationErrors ??= [];
11288

113-
var formattedKey = FormatKey(key);
114-
89+
var formattedKey = key;
90+
11591
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors))
11692
{
11793
var newErrors = new string[existingErrors.Length + errors.Length];
@@ -129,8 +105,8 @@ internal void AddOrExtendValidationError(string key, string error)
129105
{
130106
ValidationErrors ??= [];
131107

132-
var formattedKey = FormatKey(key);
133-
108+
var formattedKey = key;
109+
134110
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors) && !existingErrors.Contains(error))
135111
{
136112
ValidationErrors[formattedKey] = [.. existingErrors, error];
@@ -140,94 +116,4 @@ internal void AddOrExtendValidationError(string key, string error)
140116
ValidationErrors[formattedKey] = [error];
141117
}
142118
}
143-
144-
private string FormatKey(string key)
145-
{
146-
var namingPolicy = CachedNamingPolicy;
147-
if (string.IsNullOrEmpty(key) || namingPolicy is null)
148-
{
149-
return key;
150-
}
151-
152-
// If the key contains a path (e.g., "Address.Street" or "Items[0].Name"),
153-
// apply the naming policy to each part of the path
154-
if (key.Contains('.') || key.Contains('['))
155-
{
156-
return FormatComplexKey(key, namingPolicy);
157-
}
158-
159-
// Apply the naming policy directly
160-
return namingPolicy.ConvertName(key);
161-
}
162-
163-
private static string FormatComplexKey(string key, JsonNamingPolicy namingPolicy)
164-
{
165-
// Use a more direct approach for complex keys with dots and array indices
166-
var result = new System.Text.StringBuilder();
167-
int lastIndex = 0;
168-
int i = 0;
169-
bool inBracket = false;
170-
171-
while (i < key.Length)
172-
{
173-
char c = key[i];
174-
175-
if (c == '[')
176-
{
177-
// Format the segment before the bracket
178-
if (i > lastIndex)
179-
{
180-
string segment = key.Substring(lastIndex, i - lastIndex);
181-
string formattedSegment = namingPolicy.ConvertName(segment);
182-
result.Append(formattedSegment);
183-
}
184-
185-
// Start collecting the bracket part
186-
inBracket = true;
187-
result.Append(c);
188-
lastIndex = i + 1;
189-
}
190-
else if (c == ']')
191-
{
192-
// Add the content inside the bracket as-is
193-
if (i > lastIndex)
194-
{
195-
string segment = key.Substring(lastIndex, i - lastIndex);
196-
result.Append(segment);
197-
}
198-
result.Append(c);
199-
inBracket = false;
200-
lastIndex = i + 1;
201-
}
202-
else if (c == '.' && !inBracket)
203-
{
204-
// Format the segment before the dot
205-
if (i > lastIndex)
206-
{
207-
string segment = key.Substring(lastIndex, i - lastIndex);
208-
string formattedSegment = namingPolicy.ConvertName(segment);
209-
result.Append(formattedSegment);
210-
}
211-
result.Append(c);
212-
lastIndex = i + 1;
213-
}
214-
215-
i++;
216-
}
217-
218-
// Format the last segment if there is one
219-
if (lastIndex < key.Length)
220-
{
221-
string segment = key.Substring(lastIndex);
222-
if (!inBracket)
223-
{
224-
segment = namingPolicy.ConvertName(segment);
225-
}
226-
result.Append(segment);
227-
}
228-
229-
return result.ToString();
230-
}
231-
232-
233119
}

0 commit comments

Comments
 (0)