Having to repopulate viewmodel when modelstate is invalid due to not sending all data in such as drop down box list

半腔热情 提交于 2019-12-05 15:39:49
Tim Medora

A model binder may help with the process but I don't view it as a complete solution. For one thing, the model binder may not "know" your intentions when the model is instantiated. For example, how would it know that you would later in the action method deem the model to be invalid?

I usually have a separate method (or even a separate class) whose sole purpose is to manage the building of the view model's data appropriate to the situation.

The action method then tells this helper what it wants, allowing it to serve its purpose as a governor of the process. For example, the controller may decide:

  • A new view model needs to be prepared.
  • A new view model needs to be prepared, but initialize select properties with values from the query string.
  • A view model should be built to represent an existing domain model.
  • A view model's user-written data should be preserved, but other fields should be rebuilt (e.g. your dropdown list example).
  • Etc.

I call this building of the view model "composition". Essentially, you are pulling together the data that is needed to present a complete view model to the view. This data may come from a variety of places.

I describe the composition process in more detail here. I've written a whole framework to support a composition pattern for ASP.Net MVC (closed source, simply due to time). I found that it made supporting complex view models much easier, and improved my code reuse greatly.

Simple Example

This example keeps the process inside the controller (as opposed to a separate class) and focuses on specifying a few simple options.

[Flags]
public enum CompositionOptions
{
    PopulateFromDomainModel = 1,
    Hydrate = 2
}

[HttpGet]
public ActionResult Edit( int id)
{
    var model = new ViewModel();
    // the controller states exactly what it wants
    Compose( model, CompositionOptions.PopulateFromDomainModel | CompositionOptions.Hydrate, id );
}

[HttpPost]
public ActionResult Edit( ViewModel model )
{
    if( !ModelState.IsValid )
    {
        // Rebuild values which weren't contained in the POST. Again, the
        // controller states exactly what it needs.
        Compose( model, CompositionOptions.Hydrate );
        return View( model );
    }

    // Use POST-redirect-GET pattern, allowing the page to reload with the changes
    return RedirectToAction( "Edit", new { id = model.Id } );
}

private void Compose( ViewModel model, CompositionOptions options, int? id = null )
{
    // This logic can become quite complex, but you are generally checking for
    // existing data source (domain models) and what you should populate and
    // what fields you should preserve.

    if( id != null && options.HasFlag( CompositionOptions.PopulateFromDomainModel ) )
    {
        // get your domain model from a repository and populate the 
        // properties of the view model
    }

    if( options.HasFlag( CompositionOptions.Hydrate ) )
    {
        // set values on the view model which won't be included in 
        // a POST, and thus must be rebuilt with every roundtrip
    }
}

One possibility is certainly the one suggested by Tim with automated rehydration.

However the approach I went for in the end is the on suggested by Tim G Thomas here: http://timgthomas.com/2013/09/simplify-client-side-validation-by-adding-a-server/

It means that the data will never leave the client unless it is valid, meaning need for rehydration is removed. It does require javascript which is acceptable for me since you never need to rehydrate a view again.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!