Skip to content

Commit e69a60d

Browse files
committed
Make ValidationContext a required property
1 parent 2da8f2d commit e69a60d

8 files changed

+98
-207
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ 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
2626
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidateContext() -> void
27-
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationContext.get -> System.ComponentModel.DataAnnotations.ValidationContext?
27+
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationContext.get -> System.ComponentModel.DataAnnotations.ValidationContext!
2828
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationContext.set -> void
2929
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationErrors.get -> System.Collections.Generic.Dictionary<string!, string![]!>?
3030
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationErrors.set -> void

src/Http/Http.Abstractions/src/Validation/ValidatableParameterInfo.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,6 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
6363
return;
6464
}
6565

66-
// ValidationContext requires a non-null value although the invocation pattern that we use
67-
// calls `GetValidationResult` and passes the value there. `GetValidationResult` tolerates
68-
// null values so we only need to set a non-null value to the ValidationContext here.
69-
context.ValidationContext ??= new ValidationContext(value ?? new object(), displayName: DisplayName, serviceProvider: null, items: null);
70-
7166
context.ValidationContext.DisplayName = DisplayName;
7267
context.ValidationContext.MemberName = Name;
7368

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
7474
context.CurrentValidationPath = $"{originalPrefix}.{Name}";
7575
}
7676

77-
context.ValidationContext ??= new ValidationContext(value ?? new object(), displayName: DisplayName, serviceProvider: null, items: null);
78-
7977
context.ValidationContext.DisplayName = DisplayName;
8078
context.ValidationContext.MemberName = Name;
8179

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
4848
return;
4949
}
5050

51-
// Although classes can be annotated with [DisplayName], we only process display names when producing
52-
// errors for properties so we can pass the `Type.Name` as the display name for the type here.
53-
context.ValidationContext ??= new ValidationContext(value, displayName: Type.Name, serviceProvider: null, items: null);
54-
5551
// Check if we've exceeded the maximum depth
5652
if (context.CurrentDepth >= context.ValidationOptions.MaxDepth)
5753
{

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,24 @@ public sealed class ValidateContext
1414
/// Gets or sets the validation context used for validating objects that implement <see cref="IValidatableObject"/> or have <see cref="ValidationAttribute"/>.
1515
/// This context provides access to service provider and other validation metadata.
1616
/// </summary>
17-
public ValidationContext? ValidationContext { get; set; }
17+
/// <remarks>
18+
/// This property should be set by the consumer of the <see cref="IValidatableInfo"/>
19+
/// interface to provide the necessary context for validation. The object should be initialized
20+
/// with the current object being validated, the display name, and the service provider to support
21+
/// the complete set of validation scenarios.
22+
/// </remarks>
23+
/// <example>
24+
/// <code>
25+
/// var validationContext = new ValidationContext(objectToValidate, serviceProvider, items);
26+
/// var validationOptions = serviceProvider.GetService&lt;IOptions&lt;ValidationOptions&gt;&gt;()?.Value;
27+
/// var validateContext = new ValidateContext
28+
/// {
29+
/// ValidationContext = validationContext,
30+
/// ValidationOptions = validationOptions
31+
/// };
32+
/// </code>
33+
/// </example>
34+
public required ValidationContext ValidationContext { get; set; }
1835

1936
/// <summary>
2037
/// Gets or sets the prefix used to identify the current object being validated in a complex object graph.

src/Http/Http.Abstractions/test/Validation/ValidatableParameterInfoTests.cs

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -285,60 +285,6 @@ public async Task Validate_ExceptionDuringValidation_CapturesExceptionAsError()
285285
Assert.Equal("Test exception", error.Value.First());
286286
}
287287

288-
[Fact]
289-
public async Task Validate_WithoutValidationContext_RequiredParameter_AddsErrorAndInitializesValidationContext()
290-
{
291-
// Arrange
292-
var paramInfo = CreateTestParameterInfo(
293-
parameterType: typeof(string),
294-
name: "testParam",
295-
displayName: "Test Parameter",
296-
validationAttributes: [new RequiredAttribute()]);
297-
298-
// Create a ValidateContext without a pre-populated ValidationContext
299-
var context = new ValidateContext
300-
{
301-
ValidationOptions = new TestValidationOptions(new Dictionary<Type, ValidatableTypeInfo>())
302-
};
303-
304-
// Sanity check
305-
Assert.Null(context.ValidationContext);
306-
307-
// Act
308-
await paramInfo.ValidateAsync(null, context, default);
309-
310-
// Assert – a ValidationContext should have been created and the error recorded
311-
Assert.NotNull(context.ValidationContext);
312-
var errors = context.ValidationErrors;
313-
Assert.NotNull(errors);
314-
var error = Assert.Single(errors);
315-
Assert.Equal("testParam", error.Key);
316-
Assert.Equal("The Test Parameter field is required.", error.Value.Single());
317-
}
318-
319-
[Fact]
320-
public async Task Validate_WithoutValidationContext_ValidValue_NoErrors()
321-
{
322-
// Arrange
323-
var paramInfo = CreateTestParameterInfo(
324-
parameterType: typeof(int),
325-
name: "testParam",
326-
displayName: "Test Parameter",
327-
validationAttributes: [new RangeAttribute(10, 100)]);
328-
329-
var context = new ValidateContext
330-
{
331-
ValidationOptions = new TestValidationOptions(new Dictionary<Type, ValidatableTypeInfo>())
332-
};
333-
334-
// Act
335-
await paramInfo.ValidateAsync(50, context, default);
336-
337-
// Assert – ValidationContext initialized, but no errors added
338-
Assert.NotNull(context.ValidationContext);
339-
Assert.Null(context.ValidationErrors);
340-
}
341-
342288
private TestValidatableParameterInfo CreateTestParameterInfo(
343289
Type parameterType,
344290
string name,

0 commit comments

Comments
 (0)