2

Please can someone help me, because I'm having real trouble getting on with validating my objects that have bound in my controller actions in one swoop.

I thought that IValidatableObjects Validate() method would fire every time the binding takes place, but this isn't true... if there is a general model error it won't fire.

This leads me to wonder, how am I supposed to perform a full complex validation on my object and return the full set of validation errors? No one wants to fix all of the reported errors on a web form, then submit it to have more returned to them.

I thought I might just be able to perform all of my validation in the Validate() method, but this isn't true because seemingly there is no way of getting away from the general validation of models. For example if you were to attempt to bind a string to an int, it doesn't silently fail, it adds a model validation error and then doesn't fire Validate to perform further validation on the object.

So I can't perform all of my validation just using one method:


  1. General model validation using validation attributes
    • Reasons
    • Can't perform advanced validation, such as conditional validation based on other values within the model
    • It isn't designed to perform anything more than individual field validation

  1. Implement IValidatableObject and perform full validation in Validate() method
    • Reasons
    • There is no getting away or 'switching off' the general model validation so that it fails silently so that I can perform full validation on the object
    • IValidatebleObject isn't always fired if general model validation fails in any way, such as a failed binding

How can I perform full validation of my object in one go, no matter whether the binding was successful or not?

Why doesn't IValidatableObject fire the Validation() method regardless of the binding success?

tereško
  • 58,060
  • 25
  • 98
  • 150
Luke
  • 22,826
  • 31
  • 110
  • 193
  • You can clear binding validation errors before `Validate()` method with `ModelState.Clear()` – Max Brodin Dec 07 '14 at 18:17
  • Also you can take a look at the [FluentValidation framework](https://fluentvalidation.codeplex.com/) – Max Brodin Dec 07 '14 at 18:19
  • Another way is probably to use custom model binder – Max Brodin Dec 07 '14 at 18:24
  • I [found this](http://www.haneycodes.net/trigger-ivalidatableobject-validate-when-modelstate-isvalid-is-false/) but it performs the IValidatableObject Validate() twice. I'm wondering whether just to perform all of the additional validation within the controller? – Luke Dec 07 '14 at 18:50
  • @Coulton - it only calls Validate twice if there are no data attribute errors. My link below offers a suggested workaround. – Erik Funkenbusch Dec 08 '14 at 10:10

2 Answers2

2

The problem you seem to be running into is that validations occur in different parts of the framework for different reasons.

First, client validation occurs. If all your fields have unobtrusive client validation, then all validation will occur at once on the client.

Second comes model binding. If an error occurs in trying to bind an item to it's model entry, then those items will first fail. For instance, if you try to bind the string "xxx" to a DateTime, it's going to throw a validation error because it cannot convert "xxx" to a DateTime. And, since DateTime is non-nullable, it can't simply put null there.

Third comes your actual server-side data attribute validations. If you have only partial client-side validation (meaning not all fields have client-side validation) then you can get the odd situation that it throws validation errors for some items client-side, then after the user has fixed those problems and submits, the server-side validation finds errors and you get more errors thrown.

Finally, IValidatableObject is called. Unfortunately, IValidatableObject has no client-side validation, so you would either have to create client-side validation for these fields, or completely disable client-side validation in order to get server-side validation to occur all at once.

IValidatableObject is a rather general purpose interface, and is used in more places than just MVC. MVC uses it because it's a convenient already existing interface. And, it was present before client-side validation was added to MVC. A better approach is to create a ValidationAttribute derived attribute that implements client-side validation, then supplying the proper javascript plug-ins for unobtrusive-validation.

If validation fails in any of the steps, it does not go on to the next step. ie., if client-side validation fails, it doesn't call server-side. If Data attributes fail, it doesn't call IValidatableObject.

One way around this is described in this post:

How to force MVC to Validate IValidatableObject

Community
  • 1
  • 1
Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • Thanks for clarifying how it works. It still leaves me wondering why it isn't possible to perform further validation in the Validate method of the object. Even if it couldn't bind a `DateTime`, the action method still continues to run with the value set to null in the model... – Luke Dec 08 '14 at 10:08
  • @Coulton - That's impossible, a DateTime object cannot be null – Erik Funkenbusch Dec 08 '14 at 10:10
  • Sorry, yes but it would still set a default value. Even if it can't a non nullable DateTime, there's no reason why you shouldn't be able to perform further validation on the object, because the application still continues to run, it won't fail with a fatal error – Luke Dec 08 '14 at 20:22
1

A few different options come to mind. If you need complex validation to be done on the client use of a javascript framework such as Angular or Knockout would be beneficial, or failing that adding a jQuery validation plugin such as: http://jqueryvalidation.org/

It may also be worthwhile reviewing the ModelStateDictonary methods and seeing if these could be of help - e.g. manually calling ModelState.AddModelError for complex validation scenarios in your controller.

http://msdn.microsoft.com/en-us/library/system.web.mvc.modelstatedictionary_methods(v=vs.118).aspx

example:

 bool valid = true;

 if (string.IsNullOrEmpty(model.JobNumber))
 {
     ModelState.AddModelError("jobNumber", "Please enter job number");
     valid = false;
 }

 return View(model);
Robert Anderson
  • 1,246
  • 8
  • 11
  • Thanks for this answer Robert. I thought I might be able to validate it within my controller like that. Maybe I should create a validation helper method for my object and pass the ModelState by `ref` to it. – Luke Dec 08 '14 at 07:53