Skip to content

Commit f035b35

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

File tree

3 files changed

+19
-354
lines changed

3 files changed

+19
-354
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 & 133 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,171 +61,49 @@ 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-
{
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-
}
70+
public JsonSerializerOptions? SerializerOptions { get; set; }
10071

10172
internal void AddValidationError(string key, string[] errors)
10273
{
10374
ValidationErrors ??= [];
10475

105-
var formattedKey = FormatKey(key);
106-
ValidationErrors[formattedKey] = errors;
76+
ValidationErrors[key] = errors;
10777
}
10878

10979
internal void AddOrExtendValidationErrors(string key, string[] errors)
11080
{
11181
ValidationErrors ??= [];
11282

113-
var formattedKey = FormatKey(key);
114-
115-
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors))
83+
if (ValidationErrors.TryGetValue(key, out var existingErrors))
11684
{
11785
var newErrors = new string[existingErrors.Length + errors.Length];
11886
existingErrors.CopyTo(newErrors, 0);
11987
errors.CopyTo(newErrors, existingErrors.Length);
120-
ValidationErrors[formattedKey] = newErrors;
88+
ValidationErrors[key] = newErrors;
12189
}
12290
else
12391
{
124-
ValidationErrors[formattedKey] = errors;
92+
ValidationErrors[key] = errors;
12593
}
12694
}
12795

12896
internal void AddOrExtendValidationError(string key, string error)
12997
{
13098
ValidationErrors ??= [];
13199

132-
var formattedKey = FormatKey(key);
133-
134-
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors) && !existingErrors.Contains(error))
100+
if (ValidationErrors.TryGetValue(key, out var existingErrors) && !existingErrors.Contains(error))
135101
{
136-
ValidationErrors[formattedKey] = [.. existingErrors, error];
102+
ValidationErrors[key] = [..existingErrors, error];
137103
}
138104
else
139105
{
140-
ValidationErrors[formattedKey] = [error];
141-
}
142-
}
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++;
106+
ValidationErrors[key] = [error];
216107
}
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();
230108
}
231-
232-
233109
}

0 commit comments

Comments
 (0)