Question

In my project I create a survey, I link this survey to a company and select users to participate. I select survey template, add extra questions and sets information about the survey such as start and end date.

All in all the result is a complex view with data from many parts of the domain. I created a ViewModel for this view that would include things such as a list for all companies (because i need to select company in a drop down list).

Now the annoyance is that when i submit to save, if i have a modelstate error then i want to redisplay the view but since all the data i fetched such as list of all companies was not send in from the client (even tho the viewmodel supports it) I have to again populate all those lists/variables before again displaying the form.

I suppose I could have created a view that created a drop down, but since the drop down needs to insert it's value into my survey under CompanyId thats not obvious either.

I think a custom modelbinder can also do the trick. I am asking here to get a feeling for the best solution out there. If I should rather rethink the whole strategy.

Was it helpful?

Solution 2

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.

OTHER TIPS

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
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top