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
}
}