5

Form like this

enter image description here

ViewModel:

public class ProductViewModel
{
    public string Product { get; set; }
    public IEnumerable<SizeColorQuantityViewModel> SizeColorQuantities { get; set; }
}
public class SizeColorQuantityViewModel
{
    public string ColorId { get; set; }
    public List<SizeAndQuantity> SizeAndQuantities { get; set; }
}
public class SizeAndQuantity
{
    public int SizeId { get; set; }
    public int Quantity { get; set; }
}

View:

@model ProjectSem3.Areas.Admin.Models.ProductViewModel
@{
    ViewBag.Title = "Create";
    Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
    string[] ListColor = { "Red", "Blue" };
    string[] ListSize = { "S", "M", "L", "XL" };
}
    @for (var i = 0; i < ListColor.Length; i++)
    {
    <div class="form-group">
       <label class="col-md-2 control-label">Color:</label>
       <div class="col-md-2">
          @Html.TextBox("[" + i + "].ColorId", null, new { @Value = ListColor[i], @class = "form-control", @readonly = "readonly" })
       </div>
    </div>
    <div class="form-group">
       <label class="col-md-2 control-label">Size and Quantity:</label>
       @for (var j = 0; j < ListSize.Length; j++)
       {
       <div class="col-md-2">
          @Html.TextBox("[" + i + "][" + j + "].SizeAndQuantities.SizeId", null, new
          {
          @class = "form-control",
          @style = "margin-bottom: 15px",
          @Value = ListSize[j],
          @readonly = "readonly"
          })
          @Html.TextBox("[" + i + "][" + j + "].SizeAndQuantities.Quantity", null, new { @class = "form-control" })
       </div>
       }
    </div>
    }

Controller:

// GET: Admin/Product
public ActionResult Create()
{
    return View();
}
// POST: Admin/Product
[HttpPost]
public ActionResult Create(ProductViewModel product, IEnumerable
<SizeColorQuantityViewModel>
sizeColorQuantity, IEnumerable
<SizeAndQuantity>
sizeAndQuantity)
{ 
     return View();
}

I can get value which is passed from ViewModel IEnumerable<SizeColorQuantityViewModel> sizeColorQuantity to Controller. But with this model IEnumerable<SizeAndQuantity> sizeAndQuantity, I can't get any value. Cause this is 2-D Array so, I have no idea for this issues. Could you teach me how to bind value for IEnumerable<SizeAndQuantity> sizeAndQuantity.

Long Nguyen
  • 9,898
  • 5
  • 53
  • 52
  • 1
    Your generating inputs with name attributes that do not relate to your model, but not a lot of this code is making sense. Why do you have collections `ListColor` and `ListSize` in your view? Are you trying to edit the quantity for a product with Red/Small, and Red/Medium and Red/Large etc, and the same again for Blue/Small, Blue/Medium etc? –  Aug 21 '16 at 08:07
  • @StephenMuecke Yes, I also think a lot of ways to post a Product with Size, Color and Quantity. Have you any another ways? If you don't mind, could you explan me more? Thanks a lot :D – Long Nguyen Aug 21 '16 at 08:19
  • 1
    You need to make a lot of changes to make this work. Can you change `IEnumerable` to `List`? (it will make it easier) –  Aug 21 '16 at 08:20
  • @StephenMuecke I follow this tips: http://www.codeproject.com/Tips/855577/List-of-Model-Object-Post-to-Controller-in-ASP-NET Before, I also set List but It seems to not be working – Long Nguyen Aug 21 '16 at 08:24

1 Answers1

11

Your generating inputs with name attributes that have no relationship to your model, therefore cannot be bound by the DefaultModelBinder. You need to start by generating the data in the controller (not in the view) so that you can bind to your model.

The following assumes you change your SizeColorQuantities property to List<SizeColorQuantityViewModel>

public ActionResult Create()
{
    var colors = new List<string>(){ "Red", "Blue" };
    var sizes = new List<string>(){ "S", "M", "L", "XL" };

    var model = new ProductViewModel()
    {
        Product = "My product",
        SizeColorQuantities = new List<SizeColorQuantityViewModel>
    };
    foreach(var color in colors)
    {
        var child = new SizeColorQuantityViewModel()
        {
            ColorId = color,
            SizeAndQuantities = new List<SizeAndQuantity>
        };
        model.SizeColorQuantities.Add(child);
        foreach(var size in sizes)
        {
            child.SizeAndQuantities.Add(new SizeAndQuantity()
            {
                SizeId = size // assumes SizeId is changed to string, not int
            });
        }
    }
    return View(model);
}

You now have a correctly populated view model that will be passed to the view which you can bind to with strongly typed HtmlHelpers inside nested for loops

@model ProductViewModel
....
@using (Html.BeginForm())
{
    ....
    @for(int i = 0; i < Model.SizeColorQuantities.Count; i++)
    {
        @Html.TextBoxFor(m => m.SizeColorQuantities[i].ColorId, new { @class = "form-control", @readonly = "readonly" })
        for (int j = 0; j < Model.SizeColorQuantities[i].SizeAndQuantities .Count; j++)
        {
            @Html.TextBoxFor(m => m.SizeColorQuantities[i].SizeAndQuantities[j].SizeId, new { @class = "form-control", @readonly = "readonly" })
            @Html.TextBoxFor(m => m.SizeColorQuantities[i].SizeAndQuantities[j].Quantity, new { @class = "form-control" })
        }
    }
    <input type="submit" ... />
}

Note: Always use the strongly typed ***For() HtmlHelper methods and never attempt to set the value (or name) attribute when using a HtmlHelper method.

Your POST method now needs to be

[HttpPost]
public ActionResult Create(ProductViewModel model)
{
    ....
}

Note you can also use custom EditorTemplate's for your types as explained in HTML Table to ADO.NET DataTable, which also explains how your name attributes must be generated in order to bind to collections. In your case, for example your Quantity inputs need to be (compare that to what your currently generating)

<input name="SizeColorQuantities[0].SizeAndQuantities[0].Quantity" ... />
<input name="SizeColorQuantities[0].SizeAndQuantities[1].Quantity" ... />
....
<input name="SizeColorQuantities[1].SizeAndQuantities[0].Quantity" ... />
<input name="SizeColorQuantities[1].SizeAndQuantities[1].Quantity" ... />
....
Community
  • 1
  • 1
  • Could I ask you one more question? I release that model return from HttpPost is null. Do you have some idea? [HttpPost] public ActionResult Create(ProductViewModel model) { var test = product.Product; return View(); } – Long Nguyen Aug 21 '16 at 14:01
  • Sorry @Stephen Muecke. I find out how to resolve this problem. Cause I set params variable is "product", it's same name with properties name. Thanks you a lot. http://stackoverflow.com/a/35889181/4642316 – Long Nguyen Aug 21 '16 at 15:22