views:

552

answers:

1

I am currently using the EntityFramework to bind my ASP.NET MVC project to a MySQL database and one of my entities, Product, has an Images property containing a collection of ProductImages. I have built a form to allow the user to modify a given Product and this form includes fields for editing all of the images associated to that Product as well. After reading Phil Haack's and Dan Miser's posts on the matter I have a decent idea of what needs to happen, but I can't seem to make it work for some reason...

Here is my Product form:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<KryptonCMS.Models.Product>" %>
<%@ Import Namespace="KryptonCMS.Core" %>
<%@ Import Namespace="KryptonCMS.Models.ViewModels" %>

<% using (Html.BeginForm())
   {%>

  <ul class="gallery">
   <%
    var index = 0;
    foreach (var image in Model.ImageList.OrderBy(p => p.Order))
    {
   %>
   <li>
    <% Html.RenderPartial("ProductImageForm", image, new ViewDataDictionary(ViewData) { { "index", index } }); %>
   </li>
   <%
    index++;
          }
   %>
  </ul>

 <p>
  <input type="submit" name="btnSave" value="Save" />
  <input type="submit" name="btnCancel" value="Cancel" />
 </p>
<% } %>

And here is the definition for ProductImageForm:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<KryptonCMS.Models.ProductImage>" %>
<%@ Import Namespace="KryptonCMS.Core" %>
<div>
 <%
  var fieldPrefix = string.Format("images[{0}]", ViewData["index"]); %>
 <%=Html.Hidden(fieldPrefix + "ID", Model.ID) %>
 <img src="<%=UtilityManager.GetProductImagePath(Model.Product.ID, Model.FileName, true) %>"
  alt="" /><br />
 <label for="Description">
  Description:</label>
 <%=Html.TextBox(fieldPrefix + "Description", Model.Description) %><br />
 <label for="Order">
  Order:</label>
 <%=Html.TextBox(fieldPrefix + "Order", Model.Order)%><br />
</div>

And finally my ProductsController actions:

    public ActionResult Edit(int id)
    {
        var product = productsRepository.GetProduct(id);

        if (product == null)
            return View("NotFound", new MasterViewModel());

        // else
        return View(ContentViewModel.Create(product));
    }

    [AcceptVerbs(HttpVerbs.Post), ValidateInput(false)]
    public ActionResult Edit(int id, FormCollection formCollection)
    {
        var product = productsRepository.GetProduct(id);

        if (formCollection["btnSave"] != null)
        {
            if (TryUpdateModel(product) && TryUpdateModel(product.Images, "images"))
            {
                productsRepository.Save();

                return RedirectToAction("Details", new { id = product.ID });
            }
            return View(ContentViewModel.Create(product));
        }

        // else
        return RedirectToAction("Details", new { id = product.ID });
    }

The HTML output for a single ProductImageForm looks like this:

<div>
 <input id="images[0]ID" name="images[0]ID" type="hidden" value="1" />
 <img src="/Content/ProductGallery/3/thumbs/car1.jpg"
  alt="" /><br />
 <label for="Description">
  Description:</label>
 <input id="images[0]Description" name="images[0]Description" type="text" value="FAST CAR" /><br />
 <label for="Order">

  Order:</label>
 <input id="images[0]Order" name="images[0]Order" type="text" value="1" /><br />
</div>

I have tried all sorts of methods of reorganizing my form including taking the Image collection out of the Product form and placing it in its own (which I really don't want to do), but nothing is working. Is there something blatatently wrong with my approach here?

+3  A: 

You are missing dots in inputs' names:

<%= Html.Hidden(fieldPrefix + ".ID", Model.ID) %>
<%= Html.TextBox(fieldPrefix + ".Description", Model.Description) %>
<%= Html.TextBox(fieldPrefix + ".Order", Model.Order) %>

Check this blog post: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

eu-ge-ne
I just found that myself. Been ripping my hair out for days over a stinkin period. :(
Nathan Taylor
If I try to UpdateModel on the product.Images it doesn't automatically use the ProductImage collection returned by the form, what would be the cleanest way to replace the Image collection on my product with the one returned in the form?
Nathan Taylor
Try to change the field prefix to `product.images[{0}]`
eu-ge-ne
Have you tested that? Calling TryUpdateModel(product.Images) after changing the prefix has no effect.
Nathan Taylor
I did not tested that. I mean calling only `TryUpdateModel(product)`
eu-ge-ne
Another problem I just noticed: <%=Html.Hidden(fieldPrefix + ".ID", Model.ID) %>Is not mapping to Image.ID for some reason...
Nathan Taylor
Ya I had both TryUpdateModel(product) and TryUpdateModel(product.Images) but these may be failing because of the ID problem perhaps?
Nathan Taylor
Post your Product model, please
eu-ge-ne
Product model is generated by the EntityFramework. In a partial class I added [Bind(Include = "Name,Description,Discontinued,Images")] to the class definition.
Nathan Taylor
Did you check your edmx? (Image.Id -> Properties -> Setter == Public?)
eu-ge-ne
It is indeed Public. At this point I'm starting to think editing items one at a time and using some AJAX to stream the content progressively might be a less troublesome approach...
Nathan Taylor
Did you ever find a solution to this as Im having the same problem?http://stackoverflow.com/questions/1565132/asp-net-mvc-updating-a-list-of-objects-on-one-form-model-binding-to-a-list
Sergio