question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Object null reference execption in PropertyRule->InvokePropertyValidator

See original GitHub issue

FluentValidation 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:closed
  • Created 2 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
perkopscommented, Mar 11, 2022

To Append on this:

Here is the code for the handler implementation:

    public class PostUserHandler : IPostUserHandler
    {
        public Task<PostUserResult> ExecuteAsync(PostUserParameters parameters, CancellationToken cancellationToken = default)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException(nameof(parameters));
            }

            return InvokeExecuteAsync(parameters, cancellationToken);
        }

        private async Task<PostUserResult> InvokeExecuteAsync(PostUserParameters parameters, CancellationToken cancellationToken)
        {
            return await Task.FromResult(PostUserResult.Created());
        }
    }

and the PostUserResult class:

    public class PostUserResult : ResultBase
    {
        private PostUserResult(ActionResult result) : base(result) { }

        /// <summary>
        /// 201 - Created response.
        /// </summary>
        public static PostUserResult Created() => new PostUserResult(ResultFactory.CreateContentResult(HttpStatusCode.Created, null));

        /// <summary>
        /// 400 - BadRequest response.
        /// </summary>
        public static PostUserResult BadRequest(string? message = null) => new PostUserResult(ResultFactory.CreateContentResultWithValidationProblemDetails(HttpStatusCode.BadRequest, message));

        /// <summary>
        /// 409 - Conflict response.
        /// </summary>
        public static PostUserResult Conflict(object? error = null) => new PostUserResult(ResultFactory.CreateContentResultWithProblemDetails(HttpStatusCode.Conflict, error));
    }
0reactions
davidkallesencommented, Mar 11, 2022

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

FluentValidation throws NullReferenceException when ...
FluentValidation throws NullReferenceException when the object is null Expected: ValidationResult with Message "Entity cannot be null" using ...
Read more >
FluentValidation NullReferenceException - Way to prevent ...
I'm new to FluentValidaton and C# in general. Is this the proper way to go about handling validations like this? Do I need...
Read more >
How to avoid NullReferenceException in C# | RIMdev Blog
This exception is thrown when you try to access any properties / methods/ indexes on a type of object which points to null....
Read more >
Object Reference Not Set to an Instance of an Object
This exception is thrown when you try to access a member—for instance, a method or a property—on a variable that currently holds a...
Read more >
c# - Determining the object that caused a null reference ...
Frequently in applications we encounter situations that could throw a NullReferenceException ; for example, assuming the following method's ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found