Object null reference execption in PropertyRule->InvokePropertyValidator
See original GitHub issueFluentValidation version
10.3.6
ASP.NET version
.NET 6 (6.0.201) and Microsoft.AspNetCore.App 6.0.3
Summary
This method gives a “Object null reference execption” in component.Validate(context, accessor.Value)
if accessor.Value is null.
This can be observed when making a POST request in e.g. Swagger where you send an invalid value for e.g. a DateTimeOffset in a request object and a Custom validator has been reqistered for this POST call, with some rules in it.
The normal response would be a BadRequest (with some validation detals), but currently an InternalServerError with the NullReferenceExceptuion for “Value” is thrown.
The error relates to the method below:
Existing code
private protected void InvokePropertyValidator(ValidationContext<T> context, Lazy<TProperty> accessor, string propertyName, RuleComponent<T, TProperty> component) {
bool valid = component.Validate(context, accessor.Value);
if (!valid) {
PrepareMessageFormatterForValidationError(context, accessor.Value);
var failure = CreateValidationError(context, accessor.Value, component);
component.OnFailure?.Invoke(context.InstanceToValidate, context, accessor.Value, failure.ErrorMessage);
context.Failures.Add(failure);
}
}
When adding the check as below: access.IsValueCreated, then the POST call suceeds fine, and returns a BadRequest as expected.
Suggestion with fix
private protected void InvokePropertyValidator(ValidationContext<T> context, Lazy<TProperty> accessor, string propertyName, RuleComponent<T, TProperty> component) {
if (!accessor.IsValueCreated) {
return;
}
bool valid = component.Validate(context, accessor.Value);
if (!valid) {
PrepareMessageFormatterForValidationError(context, accessor.Value);
var failure = CreateValidationError(context, accessor.Value, component);
component.OnFailure?.Invoke(context.InstanceToValidate, context, accessor.Value, failure.ErrorMessage);
context.Failures.Add(failure);
}
}
Note: have not hit InvokePropertyValidatorAsync yet - but same check/fix should properly also be implemented here.
Steps to Reproduce
My integration test - Post against an API:
UnitTest
[Theory]
[InlineData("/api/v1/users")]
public async Task PostUser_BadRequest_InBody_MyDateTime(string relativeRef)
{
// Arrange
var sb = new StringBuilder();
sb.AppendLine("{");
sb.AppendLine(" \"FirstName\": \"Hallo1\",");
sb.AppendLine(" \"LastName\": \"Hallo2\",");
sb.AppendLine(" \"MyDateTime\": \"x2020-10-12T21:22:23\",");
sb.AppendLine(" }");
sb.AppendLine("}");
var postUserParameters = sb.ToString();
// Act
var response = await HttpClient.PostAsync(relativeRef, Json(postUserParameters));
// Assert
response.Should().NotBeNull();
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
Endpoint / Controller
public class UsersController : ControllerBase
{
[HttpPost]
[ProducesResponseType(typeof(string), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
public Task<ActionResult> PostUserAsync(PostUserParameters parameters, [FromServices] IPostUserHandler handler, CancellationToken cancellationToken)
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return InvokePostUserAsync(parameters, handler, cancellationToken);
}
}
Post - Body
public class PostUserParameters
{
/// <summary>
/// Request to create a user.
/// </summary>
[FromBody]
[Required]
public CreateUserRequest Request { get; set; }
/// <summary>
/// Converts to string.
/// </summary>
public override string ToString()
{
return $"{nameof(Request)}: ({Request})";
}
}
public class CreateUserRequest
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public DateTimeOffset MyDateTime { get; set; }
}
}
public class PostUserParametersValidator : AbstractValidator<PostUserParameters>
{
public PostUserParametersValidator()
{
RuleFor(x => x.Request.FirstName)
.NotNull().WithMessage("->Request.FirstName")
.Length(2, 10)
.Matches(@"^[A-Z]").WithMessage(x => $"{nameof(x.Request.FirstName)} has to start with an uppercase letter");
RuleFor(x => x.Request.LastName)
.NotNull().WithMessage("->Request.LastName")
.Length(2, 30)
.Matches(@"^[A-Z]").WithMessage(x => $"{nameof(x.Request.LastName)} has to start with an uppercase letter")
.NotEqual(x => x.Request.FirstName);
}
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
To Append on this:
Here is the code for the handler implementation:
and the PostUserResult class:
Hi Jeremy,
Ok, aggree “breaking change” we do not like. So I will just do as you suggested 😃
I will go with the 2. approach since it is more readable - Thx again.