I am working through Hanselman's tutorial on MVC, and attempting to use a view model in the Create and Edit views so as to fill in a pulldown menu. In this we are directed to create a table Dinner
, with columns including Title
and Country
and the related classes, and then a ViewModel containing a dinner
object and a SelectList
for a pull-down menu thus:-
public class DinnerFormViewModel {
public Dinner Dinner { get; private set; }
public SelectList Countries { get; private set; }
public DinnerFormViewModel (Dinner dinner) {
Dinner = dinner;
Countries = new SelectList(...code to create list of countries..., dinner.Country);
}
}
This I use to create Edit and Create methods in the controller. The Edit method looks like this:-
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerId });
}
catch { ...etc... }
}
and the code generated in the view looks like this:-
<div class="editor-field">
<%: Html.EditorFor(model => model.Dinner.Title) %>
<%: Html.ValidationMessageFor(model => model.Dinner.Title) %>
</div>
<div class="editor-field">
<%: Html.DropDownList("Country", Model.Countries) %>
<%: Html.ValidationMessage("Country", "*") %>
</div>
So far, so good. But when it comes to the Create method it starts to get a bit strange. While the code in the view is essentially identical, when I invoke the UpdateModel
method to fill in the dinner
object, the only field that is filled in is the Country
, from the pull-down menu; when I try it with the DinnerFormViewModel
, the Country
in the Dinner
is left null
, but all the other fields are filled in. So the Create method looks like this:-
public ActionResult Create(FormCollection formValues) {
Dinner dinner = new Dinner();
DinnerFormViewModel dinnerFormViewModel = new DinnerFormViewModel(new Dinner());
try {
UpdateModel(dinnerFormViewModel);
UpdateModel(dinner);
dinnerFormViewModel.Dinner.Country = dinner.Country;
dinnerRepository.Add(dinnerFormViewModel.Dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinnerFormViewModel.Dinner.DinnerId });
}
catch {...etc...}
}
While this works, it doesn't to me look like it is the way I should be writing this code. Is it right, or if it isn't, what should I be doing? (It is a thousand pities that the example given in the tutorial doesn't compile.)
Html.EditorFor(model => model.Dinner.Title)
This maps automatically to your view model because the HTML
name
generated will know how to map toDinner.Title
Html.DropDownList("Country", Model.Countries)
This will give the HTML element the name "Country". This means it expects a
model.Country
when posting to your view model, which doesn't exist on your ViewModel, but does exist in Dinner. HenceUpdateModel(dinnerFormViewModel);
does not work butUpdateModel(dinner);
does work.It's all based on the
name
that is generated for the input elements as far as how it decides assignment into the view model. I often inspect the generated HTML in the browser to sanity check what is being generated when there are problems mapping values on POST.You could use
DropDownListFor
so you could specify themodel.Dinner.Country
so that it would generate aname
that is fully qualified for the nested relationship.So now you understand why it didn't work the way you wanted it to. From here, how you solve it more practically has many variations that I've seen commonly used, but most will involve some sort of mapping of the view model to the entity, and often in another layer such as a business layer, data access layer, or leveraging a tool such as AutoMapper.
You could add a Country property to the root of your ViewModel.
Or I would prefer making your view model more flat where the properties are not nested in
Dinner
, and after it posts, map your view model to your entity. It looks likeDinner
is your entity. I never include my entity in my view model, and keeping entities out of controllers and views is a pretty common practice. Instead there is a Entity->ViewModel assignment at some point during GET, and then a ViewModel->Entity mapping on post. This will make more sense as you eventual move to a layered approach.By mapping I mean basic assignment. For example, when retrieving your entity, you could map it to your view model like so:
Of course I made up some properties because I don't know what your entity looks like.
This allows you to refactor your DB relationships, and you only need to change your mapping.