Skip to content

Commit 653a12c

Browse files
committed
Add ValidationErrorContext, remove unused code variant
1 parent 4548c61 commit 653a12c

File tree

6 files changed

+59
-86
lines changed

6 files changed

+59
-86
lines changed

src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs

Lines changed: 3 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections;
54
using System.Collections.Concurrent;
65
using System.ComponentModel.DataAnnotations;
76
using System.Diagnostics.CodeAnalysis;
8-
using System.Linq;
97
using System.Reflection;
108
using System.Reflection.Metadata;
119
using System.Runtime.InteropServices;
12-
using System.Text.RegularExpressions;
1310
using Microsoft.AspNetCore.Http.Validation;
1411
using Microsoft.Extensions.DependencyInjection;
1512
using Microsoft.Extensions.Options;
@@ -160,9 +157,9 @@ private void ValidateWithDefaultValidator(ValidationContext validationContext)
160157
#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
161158
private bool TryValidateTypeInfo(ValidationContext validationContext)
162159
{
163-
var options = _serviceProvider?.GetService<IOptions<ValidationOptions>>()?.Value;
160+
var options = _serviceProvider?.GetRequiredService<IOptions<ValidationOptions>>().Value!;
164161

165-
if (options == null || !options.TryGetValidatableTypeInfo(_editContext.Model.GetType(), out var typeInfo))
162+
if (!options.TryGetValidatableTypeInfo(_editContext.Model.GetType(), out var typeInfo))
166163
{
167164
return false;
168165
}
@@ -175,7 +172,7 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
175172

176173
var containerMapping = new Dictionary<string, object?>();
177174

178-
validateContext.OnValidationError += (key, _, container) => containerMapping[key] = container;
175+
validateContext.OnValidationError += context => containerMapping[context.Key] = context.Container;
179176

180177
var validationTask = typeInfo.ValidateAsync(_editContext.Model, validateContext, CancellationToken.None);
181178

@@ -198,9 +195,6 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
198195
// directly to ValidationMessageStore in the OnValidationError handler.
199196
var fieldContainer = containerMapping[fieldKey] ?? _editContext.Model;
200197

201-
// Alternative: Reverse mapping based on object graph walk.
202-
//var fieldContainer = GetFieldContainer(_editContext.Model, fieldKey);
203-
204198
var lastDotIndex = fieldKey.LastIndexOf('.');
205199
var fieldName = lastDotIndex >= 0 ? fieldKey[(lastDotIndex + 1)..] : fieldKey;
206200

@@ -212,70 +206,6 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
212206
}
213207
#pragma warning restore ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
214208

215-
// TODO(OR): Replace this with a more robust implementation or a different approach. E.g. collect references during the validation process itself.
216-
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Model types are expected to be defined in assemblies that do not get trimmed.")]
217-
private static object GetFieldContainer(object obj, string fieldKey)
218-
{
219-
// The method does not check all possible null access and index bound errors as the path is constructed internally and assumed to be correct.
220-
var dotSegments = fieldKey.Split('.')[..^1];
221-
var currentObject = obj;
222-
223-
for (int i = 0; i < dotSegments.Length; i++)
224-
{
225-
string segment = dotSegments[i];
226-
227-
if (currentObject == null)
228-
{
229-
string traversedPath = string.Join(".", dotSegments.Take(i));
230-
throw new ArgumentException($"Cannot access segment '{segment}' because the path '{traversedPath}' resolved to null.");
231-
}
232-
233-
Match match = _pathSegmentRegex.Match(segment);
234-
if (!match.Success)
235-
{
236-
throw new ArgumentException($"Invalid path segment: '{segment}'.");
237-
}
238-
239-
string propertyName = match.Groups[1].Value;
240-
string? indexStr = match.Groups[2].Success ? match.Groups[2].Value : null;
241-
242-
Type currentType = currentObject.GetType();
243-
PropertyInfo propertyInfo = currentType!.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)!;
244-
object propertyValue = propertyInfo!.GetValue(currentObject)!;
245-
246-
if (indexStr == null) // Simple property access
247-
{
248-
currentObject = propertyValue;
249-
}
250-
else // Indexed access
251-
{
252-
if (!int.TryParse(indexStr, out int index))
253-
{
254-
throw new ArgumentException($"Invalid index '{indexStr}' in segment '{segment}'.");
255-
}
256-
257-
if (propertyValue is Array array)
258-
{
259-
currentObject = array.GetValue(index)!;
260-
}
261-
else if (propertyValue is IList list)
262-
{
263-
currentObject = list[index]!;
264-
}
265-
else if (propertyValue is IEnumerable enumerable)
266-
{
267-
currentObject = enumerable.Cast<object>().ElementAt(index);
268-
}
269-
else
270-
{
271-
throw new ArgumentException($"Property '{propertyName}' is not an array, list, or enumerable. Cannot access by index in segment '{segment}'.");
272-
}
273-
}
274-
275-
}
276-
return currentObject!;
277-
}
278-
279209
public void Dispose()
280210
{
281211
_messages.Clear();
@@ -310,11 +240,5 @@ internal void ClearCache()
310240
{
311241
_propertyInfoCache.Clear();
312242
}
313-
314-
private static readonly Regex _pathSegmentRegex = PathSegmentRegexGen();
315-
316-
// Regex to parse "PropertyName" or "PropertyName[index]"
317-
[GeneratedRegex(@"^([a-zA-Z_]\w*)(?:\[(\d+)\])?$", RegexOptions.Compiled)]
318-
private static partial Regex PathSegmentRegexGen();
319243
}
320244
}

src/Components/Samples/BlazorUnitedApp/Validation/CustomerModel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
14
using System.ComponentModel.DataAnnotations;
25

36
namespace BlazorUnitedApp.Validation;

src/Components/Samples/BlazorUnitedApp/Validation/OrderModel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
14
using System.ComponentModel.DataAnnotations;
25
using Microsoft.AspNetCore.Http.Validation;
36

src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,19 @@ Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentDepth.get -> int
2323
Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentDepth.set -> void
2424
Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentValidationPath.get -> string!
2525
Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentValidationPath.set -> void
26-
Microsoft.AspNetCore.Http.Validation.ValidateContext.OnValidationError -> System.Action<string!, string![]!, object?>?
26+
Microsoft.AspNetCore.Http.Validation.ValidateContext.OnValidationError -> System.Action<Microsoft.AspNetCore.Http.Validation.ValidationErrorContext!>?
2727
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidateContext() -> void
2828
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationContext.get -> System.ComponentModel.DataAnnotations.ValidationContext!
2929
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationContext.set -> void
3030
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationErrors.get -> System.Collections.Generic.Dictionary<string!, string![]!>?
3131
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationErrors.set -> void
3232
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationOptions.get -> Microsoft.AspNetCore.Http.Validation.ValidationOptions!
3333
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationOptions.set -> void
34+
Microsoft.AspNetCore.Http.Validation.ValidationErrorContext
35+
Microsoft.AspNetCore.Http.Validation.ValidationErrorContext.Container.get -> object?
36+
Microsoft.AspNetCore.Http.Validation.ValidationErrorContext.Errors.get -> string![]!
37+
Microsoft.AspNetCore.Http.Validation.ValidationErrorContext.Key.get -> string!
38+
Microsoft.AspNetCore.Http.Validation.ValidationErrorContext.ValidationErrorContext(string! key, string![]! errors, object? container) -> void
3439
Microsoft.AspNetCore.Http.Validation.ValidationOptions
3540
Microsoft.AspNetCore.Http.Validation.ValidationOptions.MaxDepth.get -> int
3641
Microsoft.AspNetCore.Http.Validation.ValidationOptions.MaxDepth.set -> void

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ public sealed class ValidateContext
6363
/// <summary>
6464
/// Optional event raised when a validation error is reported.
6565
/// </summary>
66-
public event Action<string, string[], object?>? OnValidationError;
66+
public event Action<ValidationErrorContext>? OnValidationError;
6767

68-
internal void AddValidationError(string key, string[] error, object? container)
68+
internal void AddValidationError(string key, string[] errors, object? container)
6969
{
7070
ValidationErrors ??= [];
7171

72-
ValidationErrors[key] = error;
73-
OnValidationError?.Invoke(key, error, container);
72+
ValidationErrors[key] = errors;
73+
OnValidationError?.Invoke(new ValidationErrorContext(key, errors, container));
7474
}
7575

7676
internal void AddOrExtendValidationErrors(string key, string[] errors, object? container)
@@ -89,7 +89,7 @@ internal void AddOrExtendValidationErrors(string key, string[] errors, object? c
8989
ValidationErrors[key] = errors;
9090
}
9191

92-
OnValidationError?.Invoke(key, errors, container);
92+
OnValidationError?.Invoke(new ValidationErrorContext(key, errors, container));
9393
}
9494

9595
internal void AddOrExtendValidationError(string key, string error, object? container)
@@ -105,6 +105,6 @@ internal void AddOrExtendValidationError(string key, string error, object? conta
105105
ValidationErrors[key] = [error];
106106
}
107107

108-
OnValidationError?.Invoke(key, [error], container);
108+
OnValidationError?.Invoke(new ValidationErrorContext(key, [error], container));
109109
}
110110
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
namespace Microsoft.AspNetCore.Http.Validation;
7+
8+
/// <summary>
9+
/// Represents the context for a reported validation error.
10+
/// </summary>
11+
[Experimental("ASP0029", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")]
12+
public class ValidationErrorContext
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="ValidationErrorContext"/> class with the specified key, errors, and container.
16+
/// </summary>
17+
public ValidationErrorContext(string key, string[] errors, object? container)
18+
{
19+
Key = key;
20+
Errors = errors;
21+
Container = container;
22+
}
23+
24+
/// <summary>
25+
/// Gets the key associated with the validation error.
26+
/// </summary>
27+
public string Key { get; }
28+
29+
/// <summary>
30+
/// Gets the array of error messages associated with the validation error.
31+
/// </summary>
32+
public string[] Errors { get; }
33+
34+
/// <summary>
35+
/// Gets the container object associated with the validation error, if any.
36+
/// </summary>
37+
public object? Container { get; }
38+
}

0 commit comments

Comments
 (0)