Revalidating a modified ViewModel within a controller method?
Asked Answered
I

2

63

EDIT - We're using MVC4 Dev Preview....

I'm implementing an edit page for a FishingTrip class. FishingTrip contains a child collection of simple Crew objects (i.e. FishingTripID, CrewID, CrewPosition).

I'm using Jarrett Meyer's approach to add, edit and delete from the Crew collection. I'm using unobtrusive validation to specify that the properties of Crew are all Required.

My problem: when I logically-delete an item from the list (as per Jarrett's method), I don't want that item to be validated.

I have successfully tweaked the "removeRow" method on the client-side to disable unobtrusive validation for the logically-deleted item, so that the form will post despite there being an item that contains blank fields.

In my controller method [HttpPost] Edit, ModelState.IsValid starts off as false (as expected - because of the logically-deleted item that contains blank fields.) So I remove that item from my ViewModel.... but ModelState.IsValid is still false.

In summary, I (think I) want to modify my ViewModel within the controller method to remove the offending item, then call some kind of "revalidate", and have ModelState.IsValid show up as true.

Any ideas?

Intrados answered 20/10, 2011 at 8:55 Comment(1)
Possible duplicate of Manually invoking ModelState validationIncensory
P
145

Once you have removed the offending item(s), clear the ModelState and validate again, like so:

ModelState.Clear();
TryValidateModel(crew);  // assumes the model being passed is named "crew"

Note: Be carefull when use TryValidateModel method because this method does not validate nested object of model (As mentioned by @Merenzo).

Ponder answered 20/10, 2011 at 14:8 Comment(10)
Thanks @councellorben - ModelState.Clear does indeed remove those errors (and the whole ModelState.Keys collection) but TryModelValidate doesn't seem to repopulate the ModelState. It doesnt seem to do anything? e.g. If I don't remove the offending item, then call .Clear, then TryValidateModel, I'm left with a blank ModelState, rather than one that matches the "pre-Clear()" version.Intrados
If you review the MVC source code (TryValidateModel in Controller.cs in System.Web.Mvc), TryValidateModel adds any errors to the ModelState, and I have used it for that purpose in seveal projects.Ponder
@councellorben - in your projects, does Clear() blow away all ModedlState.Keys, and does TryValidateModel then restore all these Keys and add the errors to the Keys? (Apologies - we're using MVC4 not MVC3 as I originally stated.)Intrados
In MVC 3, all keys are restored, and the errors are added. I will have to test in MVC 4 to see if this has changed. That would be a rather serious breaking change.Ponder
In my MVC4 project, I mistakenly thought TryValidateModel() was doing nothing... however it's just ignoring nested objects (as per stackoverflow.com/questions/4465432). In my MVC4 project, TryValidateModel() is restoring those keys that are (a) in the top level object and (b) only if they have validation errors. So it looks to me like this answer is right. Whether or not there's a breaking change in MVC4 is another story :)Intrados
Excellent catch. I missed it, because I used TryValidateModel in several circumstances while iterating through a collection of models.Ponder
So, how do you replicate the deep-validation that MVC does to a model on it's way in? I'm using FluentValidation for everything... but if I apply the validator directly it doesn't update modelstate.Redmer
It took me so long to find out how to properly do this. Thank you for saving me another day of grief.Atwitter
you can use TryValidateModel overload method TryValidateModel(model.NestedModel, "NestedModel")Sailor
Any ideas on how to avoid "Value cannot be null" error in unit test with this method?Picker
P
3

Late to the game, but still: I was also looking for a way to validate model after doing some tweaks to it (more precisely - to the items of its nested collection) - and TryValidateModel didn't work for me, as it doesn't process nested objects.

Finally, I settled with custom model binder:

public class MyItemModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(
        ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType == typeof(MyItemModel))
        {
            MyItemModel item = (MyItemModel)bindingContext.Model;
            //do required tweaks on model here 
            //(I needed to load some additional data from DB)
        }
        //validation code will be called here, in OnModelUpdated implementation
        base.OnModelUpdated(controllerContext, bindingContext);
    }
}

on the model class:

[ModelBinder(typeof(MyItemModelBinder))]
public class MyItemModel : IValidatableObject
{
    //...
}
Petrina answered 16/3, 2015 at 18:18 Comment(2)
I'm trying to get the DataAnnotation attributes to validate again, but they don't seem to after OnModelUpdated. Any ideas?Litalitany
I had created and applied a custom model binder to the parent view model. I needed to apply it to the child VM that directly contained the validation.Litalitany

© 2022 - 2024 — McMap. All rights reserved.