Validation of nested ViewModels

2019-04-24 13:44发布

问题:

I have two ViewModels (simplified):

public class ParentViewModel
{
    public ParentViewModel
    {
        Content = new ChildViewModel();
    }

    public ChildViewModel Content { get; set, }
}

public class ChildViewModel
{
    [Required]
    public string Name1 { get; set, }
    [Required]
    public string Name2 { get; set, }
}

And the following controller post action:

[HttpPost]
public ActionResult Create(ParentViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        // process viewModel -> write something into database
        return RedirectToAction("Index");
    }
    return View(viewModel);
}

Now I am sending the following form values in a post request body to the URL corresponding to that action (manually in Fiddler Request Builder):

  • Content.Name1=X

    This works fine, the Name1 property is filled in viewModel.Content, Name2 is null and the model state is invalid because Name2 is required. So, validation fails as expected.

  • Xontent.Name1=X or Name1=X or whatever so that nothing gets bound to the viewModel

    Now viewModel.Content is not null (because I'm instantiating it in the constructor) but all properties Name1 and Name2 are null. This is expected. What I did not expect is that the model state is valid, so it passes the validation (leading to DB exceptions later because there are non-nullable columns).

How can I improve this code so that validation also works in the second case?

I did three experiments:

  • I have removed the instantiation of Content in the ParentViewModel constructor, then Content is null in the second example above, but validation still passes.

  • I have added a [Required] attribute to the Content property (but didn't remove the instantiation of Content in the ParentViewModel constructor). This has no effect at all, the described behaviour of the two tests above is the same.

  • I have added a [Required] attribute to the Content property and removed the instantiation of Content in the ParentViewModel constructor. This seems to work as I want: In the second test Content is null and validation fails due to the [Required] attribute. It would look like this:

    public class ParentViewModel
    {
        [Required]
        public ChildViewModel Content { get; set, }
    }
    
    public class ChildViewModel
    {
        [Required]
        public string Name1 { get; set, }
        [Required]
        public string Name2 { get; set, }
    }
    

I would conclude now that instantiating the Content child property in the ParentViewModel constructor is the source of the problem and that the model binder itself must instantiate the child properties (or not, if there are no matching form fields in the request) in order to have a properly working server side validation.

I have child property instantiation in several other view model constructors and didn't notice this problem until now. So, is this generally a bad practice? Are there other ways to solve the problem?

回答1:

ModelState.IsValid tells you if any model errors have been added to ModelState.

The default model binder will add some errors for basic type conversion issues such as passing a non-number for something which is an "int". You can populate ModelState more fully based on whatever validation system you're using. I would suggest looking into data annotations to validate the ViewModels since it works well.

This syntax may be wrong or old. ModelState.AddModelError("key", Exception)

paraphrased from What is ModelState.IsValid valid for in ASP.NET MVC in NerdDinner?



回答2:

The third solution is fine:

public class ParentViewModel
{
    [Required]
    public ChildViewModel Content { get; set, }
}

public class ChildViewModel
{
    [Required]
    public string Name1 { get; set, }
    [Required]
    public string Name2 { get; set, }
}

I'm using it now at several places and didn't notice any problems.