Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IValidatableObject.Validate Only Called If Attribute-Defined Validation Rules Pass? #1899

Closed
bgribaudo opened this issue Jan 10, 2017 · 3 comments

Comments

@bgribaudo
Copy link

bgribaudo commented Jan 10, 2017

Hello,

I have an object with a [Required]-decorated property that implements IValidatableObject. During model binding, if the required property is null, the object's Validate() method is never called. Is Validate() only called if attribute-configured validation passes?

In my case, if a user submits a form with validation errors, they first see a message about the missing field. Then, once that error has been resolved and the form resubmitted, they are presented with an additional list of errors—this time, produced by Validate(). Is there a way to trigger all validation to occur at the same time so that the user is presented with a list of all errors needing their attention when they first submit the form?

Thanks,
Ben

Versions: ASP.Net Core 1.1.0/.Net 4.6.1

Example Code

public class Import : IValidatableObject
{
  [Required]
  public IFormFile File { get; set; }

  public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { /* ... */ }
  //...
}

From the controller:

[HttpPost, ActionName("Index")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> IndexPost()
{
  var model = new ViewModels.Import();

  var bindingResult = await TryUpdateModelAsync(model, "", i => i.StartDate, i => i.EndDate, i => i.ImportTypeId, i => i.File);
  if (!bindingResult || ModelState.ValidationState != ModelValidationState.Valid) {
    return View(model);
  }
  
  // ...
}

If the user fails to populate the required field, they'll see an error message about it but they won't see the other error messages that Validate() might add.

@aspnet-hello
Copy link

This issue is being closed because it has not been updated in 3 months.

We apologize if this causes any inconvenience. We ask that if you are still encountering this issue, please log a new issue with updated information and we will investigate.

@crowcoder
Copy link

I am experiencing this behavior as well in Asp.Net Core 2.0, with a referenced library containing an IValidatableObject targeting .Net Standard 2.0.
It is not until all [Required] validations are satisfied that the custom validations in Validate() are executed. In other words, I have to post an object to my controller with all required fields before anything else will be reported in modelstate.
The modelstate seems to care about required first, then everything else. I haven't tried other standard validation attributes like [StringLength()].

@glyder
Copy link

glyder commented Mar 22, 2019

This is really annoying bug. Why close it?

Its hard to architect things and tell people dataannotations work then you find IValidatableObject.Validate runs as per above. It's an issue and this SHOULD be reopened.

Meanwhile I have to deal with programmers who think this is good programming:
public static List ValidateUpdateDates(ICommand command)
{
var newErrors = new List();

        var validator = new DataAnnotationsValidator();
        var results = new List<ValidationResult>();
        var isValid = validator.TryValidate(command, out results);

        if (!(command is UpdatePromotionCommand cpc))
            return newErrors;

        newErrors.AddRange(ValidateDatesAreValidFormat(cpc));

        if (
          !string.IsNullOrEmpty(cpc.PurchasePeriodStart)
          && !string.IsNullOrEmpty(cpc.PurchasePeriodEnd)
          && DateTime.TryParse(cpc.PurchasePeriodStart, out DateTime purchasePeriodStartDate)
          && DateTime.TryParse(cpc.PurchasePeriodEnd, out DateTime purchasePeriodEndDate)
          && purchasePeriodStartDate > purchasePeriodEndDate
        )
        {
            newErrors.Add(new ECError("Purchase Period End cannot be before Purchase Period Start."));
        }

        if (
          !string.IsNullOrEmpty(cpc.ClaimPeriodStart)
          && !string.IsNullOrEmpty(cpc.ClaimPeriodEnd)
          && DateTime.TryParse(cpc.ClaimPeriodStart, out DateTime claimPeriodStartDate)
          && DateTime.TryParse(cpc.ClaimPeriodEnd, out DateTime claimPeriodEndDate)
          && claimPeriodStartDate > claimPeriodEndDate
        )
        {
            newErrors.Add(new ECError("Claim Period End cannot be before Claim Period Start."));
        }

@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants