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 bra
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.
Junto,
the Html.EditorForModel()
method isn't smart enough to match BrandId
with the Brands
select list.
First, you can't use the shortcut EditorForModel()
method.
You have to create your own HTML template like this.
<% using (Html.BeginForm()) { %>
<div style="display:none"><%= Html.AntiForgeryToken() %></div>
<table>
<tr>
<td><%= Html.LabelFor(m => m.Name) %></td>
<td><%= Html.EditorFor(m => m.Name) %></td>
</tr>
<tr>
<td><%= Html.LabelFor(m => m.Description) %></td>
<td><%= Html.EditorFor(m => m.Description) %></td>
</tr>
<tr>
<td><%= Html.LabelFor(m => m.BrandId) %></td>
<td><%= Html.EditorFor(m => m.BrandId) %></td>
</tr>
</table>
<% } %>
Second, you need to change your Action method.
[ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
BrandRepository br = new BrandRepository();
Product p = _ProductRepository.Get(id);
ViewData["BrandId"] = br.GetAll().ToList().ToSelectListItems(p.BrandId);
EditProductViewModel model = new EditProductViewModel(p);
return View("Edit", model);
}
Third, you need to update your EditProductViewModel
class.
public class EditProductViewModel
{
[Required]
[StringLength(200)]
public string Name { get; set; }
[Required()]
[DataType(DataType.Html)]
public string Description { get; set; }
[Required] // this foreign key *should* be required
public int BrandId { get; set; }
public EditProductViewModel(Product product)
{
this.Name = product.Name;
this.Description = product.Description;
this.BrandId = product.BrandId;
}
}
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.
<form action="/Product/Edit/123" method="post">
...
</form>
This is your HTTP POST action method and it accepts 2 parameters.
The "id" comes from the <form> tag's action attribute.
[HttpPost, ValidateAntiForgeryToken, ExportModelStateToTempData]
public ActionResult Edit(int id, EditProductViewModel model)
{
Product p = _ProductRepository.Get(id);
// make sure the product exists
// otherwise **redirect** to [NotFound] view because this is a HTTP POST method
if (p == null)
return RedirectToAction("NotFound", new { id = id });
if (ModelState.IsValid)
{
TryUpdateModel<Product>(p);
_ProductRepository.UpdateProduct( p );
}
return RedirectToAction("Edit", new { id = id });
}
The ExportModelStateToTempData
and ImportModelStateFromTempData
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.
TryUpdateModel<Product>( p );
My favorite way of doing it is to have a separate interface
for pure data binding.
public interface IProductModel
{
public string Name {get; set;}
public string Description {get; set;}
public int BrandId {get; set;}
}
public partial class Product : IProductModel
{
}
public partial class EditProductViewModel : IProductModel
{
}
And this is how I will update my data binding code.
TryUpdateModel<IProductModel>( p );
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.
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.