I am trying to implement an Edit ViewModel for my Linq2SQL entity called Product. It has a foreign key linked to a list of brands.
Currently I am populating the brand list via ViewData and using DropDownListFor, thus:
<div class="editor-field">
<%= Html.DropDownListFor(model => model.BrandId, (SelectList)ViewData["Brands"])%>
<%= Html.ValidationMessageFor(model => model.BrandId) %>
</div>
Now I want to refactor the view to use a strongly typed ViewModel and Html.EditorForModel():
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<%=Html.EditorForModel() %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
In my Edit ViewModel, I have the following:
public class EditProductViewModel
{
[HiddenInput]
public int ProductId { get; set; }
[Required()]
[StringLength(200)]
public string Name { get; set; }
[Required()]
[DataType(DataType.Html)]
public string Description { get; set; }
public IEnumerable<SelectListItem> Brands { get; set; }
public int BrandId { get; set; }
public EditProductViewModel(Product product, IEnumerable<SelectListItem> brands)
{
this.ProductId = product.ProductId;
this.Name = product.Name;
this.Description = product.Description;
this.Brands = brands;
this.BrandId = product.BrandId;
}
}
The controller is setup like so:
public ActionResult Edit(int id)
{
BrandRepository br = new BrandRepository();
Product p = _ProductRepository.Get(id);
IEnumerable<SelectListItem> brands = br.GetAll().ToList().ToSelectListItems(p.BrandId);
EditProductViewModel model = new EditProductViewModel(p, brands);
return View("Edit", model);
}
The ProductId, Name and Description display correctly in the generated view, but the select list does not. The brand list definitely contains data.
If I do the following in my view, the SelectList is visible:
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<%=Html.EditorForModel() %>
<div class="editor-label">
<%= Html.LabelFor(model => model.BrandId) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.BrandId, Model.Brands)%>
<%= Html.ValidationMessageFor(model => model.BrandId) %>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
What am I doing wrong? Does EditorForModel() not generically support the SelectList? Am I missing some kind of DataAnnotation?
I can't seem to find any examples of SelectList usage in ViewModels that help. I'm truly stumped. This answer seems to be close, but hasn't helped.
You should build that lookup INTO your ViewModel. Then create a Builder object that builds the ViewModel and populates that lookup.
After all, that's what your ViewModel is for: to provide a model specifically for your view.
Junto,
the
Html.EditorForModel()
method isn't smart enough to matchBrandId
with theBrands
select list.First, you can't use the shortcut
EditorForModel()
method.You have to create your own HTML template like this.
Second, you need to change your Action method.
Third, you need to update your
EditProductViewModel
class.By now, you are probably saying: Dude, where is my [ProductId] property?".
Short Answer: You don't need it!
The HTML rendered by your view already points to "Edit" action method with an appropriate "ProductId" as shown below.
This is your HTTP POST action method and it accepts 2 parameters.
The "id" comes from the <form> tag's action attribute.
The
ExportModelStateToTempData
andImportModelStateFromTempData
are very useful.Those attributes are used for PRG (Post Redirect Get) pattern.
Read this Use PRG Pattern for Data Modification section in this blog post by Kazi Manzur Rashid.
http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx
Okay, this data bind code is not my favorite way of doing things.
My favorite way of doing it is to have a separate
interface
for pure data binding.And this is how I will update my data binding code.
What this helps is it makes it simple for me to data bind my model objects from post back data. Additionally, it makes it more secure because you are only binding the data that you want to bind for. Nothing more, nothing less.
Let me know if you have any question.
A new attribute DropDownList for your property BrandId may be helpful. Have a look at the Extending ASP.NET MVC 2 Templates article. But this approach uses ViewData as selectlist's items source.