绑定到动态创建的输入的viewmodel集合

时间:2018-07-11 08:16:08

标签: collections binding asp.net-mvc-5 ef-code-first

具有这个复杂的模型:

public class Food
        {
        public int FoodID { get; set; }
        public string FoodNameEN { get; set; }
        public string FoodDescriptionEN { get; set; }
        public int CategoryID { get; set; }
        public bool Availability { get; set; }
        public bool DailyMenu { get; set; }

        public virtual Diet Diet { get; set; }
        public virtual Category Category { get; set; }
        public virtual ICollection<FoodAttributeValue> FoodAttributeValues { get; set; } = new HashSet<FoodAttributeValue>();

 public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryEN { get; set; }
        public bool Availability { get; set; }

        public virtual ICollection<Food> Foods { get; set; } = new HashSet<Food>();
    }

 public class Attribute
    {
        public int AttributeID { get; set; }
        public string AttributeNameEN { get; set; }

        public virtual ICollection<AttributeValue> AttributeValues { get; set; } = new HashSet<AttributeValue>();
    }

 public class AttributeValue
    {
        public int AttributeValueID { get; set; }
        public int AttributeID { get; set; }    
        public string AttributeValueEN { get; set; }

        public virtual Attribute Attribute { get; set; }
        public virtual ICollection<FoodAttributeValue> FoodAttributeValues { get; set; } = new HashSet<FoodAttributeValue>();
    }

public class FoodAttributeValue
    {
        public int FoodAttributeValueID { get; set; }
        public int FoodID { get; set; }    
        public int AttributeValueID { get; set; }    
        public decimal Price { get; set; }

        public virtual Food Food { get; set; }
        public virtual AttributeValue AttributeValue { get; set; }
    }

使用 FoodIndexViewModel 如下所示,我可以列出食物属性及其相关的所有内容,例如其属性(大小或体积),其 AttributeValues (正常大小,大号...),最后是其价格,正如您在 FoodAttributeValue 中看到的那样,它取决于 FoodID 和< em> AttributeValueID 。

public class FoodIndexViewModel
{
    public int FoodID { get; set; }
    public string FoodNameEN { get; set; }
    public string FoodDescriptionEN { get; set; }
    public int CategoryID { get; set; }        
    public bool Availability { get; set; }
    public bool DailyMenu { get; set; }

    public Diet Diet { get; set; }
    public ICollection<FoodAttributeValue> FoodAttributeValues { get; set; } = new List<FoodAttributeValue>();
}

我正在尝试对食品实施CRUD。 对于“创建/编辑”,我需要这样的内容:

Create / Edit Food

使用Jquery填充 Attributes 下拉列表,并使用 onchange 事件对其进行调整,以适应所有 Attribute Values 的列表。 属性已选择。

如何将选中的 AttributeValues 绑定到 FoodAttributeValues 集合以进行编辑和创建表单?

更新:即使不是很满意,我还是设法实现了目标

我创建了 AttributeValuesViewModel

public class AttributeValuesViewModel
    {
        public int? AttributeID { get; set; }
        public int AttributeValueID { get; set; }
        public string AttributeValueName { get; set; }

        public bool IsChecked { get; set; }

        [RegularExpression("^[0-9]*$", ErrorMessage = "Price must be a number 0 or higher")]
        [Required(ErrorMessage = "Please specify the Price")]
        public decimal Price { get; set; }
    } 

编辑GET:

//GET: Admin/Foods/Edit/5
public ActionResult Edit(int? id, int? attrID)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Food food = _db.Foods
        .Include(f => f.Category)
        .Include(f => f.Diet)
        .Include(f => f.FoodAttributeValues)
        .SingleOrDefault(f => f.FoodID == id);

    if (food == null)
    {
        return HttpNotFound();
    }

    ViewBag.CategoryID = new SelectList(_db.Categories, "CategoryID", "CategoryEN");

    // if "attrID" null, get the selected AttributeID from the attribute values the food has, else set it to the value provided.
    // In this case dropdown was changed on edit food page
    var selectedFoodAttrId = attrID == null ? food.FoodAttributeValues.FirstOrDefault().AttributeValue.AttributeID : attrID.Value;

    ViewBag.AttributeID = new SelectList(_db.Attributes, "AttributeID", "AttributeNameEN", selectedFoodAttrId);

    PopulateCheckedAttributeValues(food, selectedFoodAttrId);

    return View(food);                   

}

private List<AttributeValuesViewModel> PopulateCheckedAttributeValues(Food food, int attributeId)
        {
            // All AttributeValues in DB
            var allAttributeValues = _db.AttributeValues.Where(av => av.AttributeID == attributeId);

            // The AttributeValueIDs of food checked AttributeValues
            var foodCheckedAttributeValues = new HashSet<int>(food.FoodAttributeValues.Select(f => f.AttributeValueID));

            var viewModel = new List<AttributeValuesViewModel>();

            foreach (var attrValue in allAttributeValues)
            {
                viewModel.Add(new AttributeValuesViewModel
                {
                    AttributeID = attributeId,
                    AttributeValueID = attrValue.AttributeValueID,
                    AttributeValueName = attrValue.AttributeValueEN,
                    Price = food.FoodAttributeValues.Where(fa => fa.AttributeValueID == attrValue.AttributeValueID).Select(fa => fa.Price).SingleOrDefault(),
                    IsChecked = foodCheckedAttributeValues.Contains(attrValue.AttributeValueID)
                });
            }

            //ViewBag.AttributeValues = viewModel;
            TempData["AttributeValues"] = viewModel;
            return viewModel;
        }

编辑POST:

// POST: Admin/Foods/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        [ActionName("Edit")]
        public ActionResult EditPost(int? id, string[] foodCheckedAttributeValues, decimal[] price, int AttributeID)
        {
            Food foodToUpdate = _db.Foods
                .Include(f => f.Category)
                .Include(f => f.Diet)
                .Include(f => f.FoodAttributeValues)
                .Single(f => f.FoodID == id);

            // Should not happen, but if for some reason form is posted with no price
            if (price == null)
            {
                TempData["Error"] = $"Food edit Failed! Fill at least one Price!";
                return RedirectToAction("Edit", new { id, foodCheckedAttributeValues});
            }

            if (TryUpdateModel(foodToUpdate))
            {
                UpdateAttributeValues(foodCheckedAttributeValues, price, foodToUpdate);
                try
                {
                    _db.SaveChanges();
                    TempData["message"] = $"Food with ID: {id} modified successfully!";
                    return RedirectToAction("Index");
                }
                catch (DbUpdateException updEx)
                {
                    if (updEx.InnerException != null && updEx.InnerException.ToString().Contains("Violation of UNIQUE KEY constraint"))
                    {
                        TempData["error"] = $"Food edit failed! Food Names must be uniqe, and this one already exists in the database!";
                    }
                }
                catch (Exception e)
                {
                    TempData["Error"] = $"Food edit Failed!!";
                }
            }

            //PopulateCheckedAttributeValues(foodToUpdate, AttributeID);
            //ViewBag.AttributeID = new SelectList(_db.Attributes, "AttributeID", "AttributeNameEN", AttributeID);
            //ViewBag.CategoryID = new SelectList(_db.Categories, "CategoryID", "CategoryEN");
            //return View(foodToUpdate);

            return RedirectToAction("Edit", new {id = foodToUpdate.FoodID});

        }

        // Loads only the partial view, no refresh of the page
        public PartialViewResult EditFoodAttributesPartial(int? id, int attrId)
        {
            Food food = _db.Foods
                .Include(f => f.Category)
                .Include(f => f.Diet)
                .Include(f => f.FoodAttributeValues)
                .SingleOrDefault(f => f.FoodID == id);

            var model = PopulateCheckedAttributeValues(food, attrId);

            return PartialView("_EditFoodAttributesPartial", model);
        }

        private void UpdateAttributeValues(string[] foodCheckedAttributeValues, decimal[] price, Food foodToUpdate)
        {
            // Has Attribute Selected
            if (foodCheckedAttributeValues != null && foodCheckedAttributeValues.Length > 0)
            {
                for(int i = 0; i < foodCheckedAttributeValues.Length; i++ )
                {
                    FoodAttributeValue fa = new FoodAttributeValue
                    {
                        AttributeValueID = Convert.ToInt32(foodCheckedAttributeValues[i]),
                        FoodID = foodToUpdate.FoodID,
                        Price = price[i]
                    };
                    _db.FoodAttributeValues.Add(fa);
                }
            }
            // No Attributes
            else
            {
                FoodAttributeValue fa = new FoodAttributeValue
                {
                    AttributeValueID = 0,
                    FoodID = foodToUpdate.FoodID,
                    Price = price[0]
                };
                _db.FoodAttributeValues.Add(fa);
            }

            var favToDelete = _db.FoodAttributeValues.Where(fa => fa.FoodID == foodToUpdate.FoodID);
            foreach (var fa in favToDelete)
            {
                _db.FoodAttributeValues.Remove(fa);
            }
        }        

在属性值更改事件后如何加载部分:

编辑页面:

 // The div where it resides in the complete edit page
 <div id="attributesDiv" class="col-7">
                            @Html.Partial("_EditFoodAttributesPartial", (List<CodeFirstCosi.UI.Areas.Admin.ViewModels.AttributeValuesViewModel>)TempData["AttributeValues"])
                       </div>
@section scripts {
        @Scripts.Render("~/bundles/jqueryval")
       <script>
           $(document).ready(function () {
               $('#submitButton').prop("disabled", false);

               // For the first load, before any Attribute ID change event
               $("#attributesTable input[type=checkbox]").each(function () {

                   $(this).change(function () {

                       if ($(this).is(':checked')) {
                           $('#' + $(this).attr('id') + 'Price').prop('disabled', false);
                           // at least one is checked so remove the error on submit
                           $('#errorTr').remove();
                       } else {
                           $('#' + $(this).attr('id') + 'Price').prop('disabled', true);
                       }
                   });
               });

               // Attribute dropdown changed event
               $('#AttributeName').change(function () {
                  $("#attributesDiv").load("@Url.Action("EditFoodAttributesPartial", new { id = Model.FoodID})?&attrID=" + $('#AttributeName').val(), function () {                       
           });
        </script>
    }

部分视图:

@model List<CodeFirstCosi.UI.Areas.Admin.ViewModels.AttributeValuesViewModel>

@{
    var attrId = Model.FirstOrDefault().AttributeID;

    if (attrId != 0)
    {
        <table id="attributesTable" class="table table-borderless">
            <thead>
                <tr>
                    <td>Attribute Value</td>
                    <td>Price</td>
                </tr>
            </thead>
            <tbody>
                @foreach (var attrValue in Model)
                {
                    var AttributeID = attrValue.AttributeID;
                    var AttributeValueID = attrValue.AttributeValueID;
                    var AttributeValueName = attrValue.AttributeValueName;
                    var Price = attrValue.Price;
                    var IsChecked = attrValue.IsChecked;

                    var checkboxID = "AV" + AttributeValueID;
                    var priceID = checkboxID + "Price";

                    <tr>
                        <td>
                            <input type="checkbox"
                                   id="@checkboxID"
                                   name="foodCheckedAttributeValues"
                                   value="@AttributeValueID"
                                   @(Html.Raw(IsChecked ? "checked=\"checked\"" : ""))
                                   />
                            <label for="@checkboxID">@AttributeValueName</label>
                        </td>
                        <td>
                            <input type="number"
                                   step=".01"
                                   min="0"
                                   name="Price"
                                   id="@priceID"
                                   required="required"
                                   value="@Price"
                                   class="form-control form-control-sm"
                                   @(Html.Raw(!IsChecked ? "disabled" : "")) />
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    }
    else
    {
        foreach (var attrValue in Model)
        {
            var AttributeValueID = attrValue.AttributeValueID;
            var Price = attrValue.Price;

            var checkboxID = "AV" + AttributeValueID;
            var priceID = checkboxID + "Price";

            <div class="form-group">
                <label>Price</label>
                <input type="number"
                       step=".01"
                       min="0"
                       name="Price"
                       id="@priceID"
                       required="required"
                       value="@Price"
                       class="form-control form-control-sm" />
            </div>
        }
    }
}

在功能方面,它可以满足我的需求。但是我无法绑定视图模型AttributeValuesViewModel来使验证工作。在局部视图中,我希望这些输入是使用@Html帮助器生成的,例如:

  <tr>
                <td>

                    @Html.EditorFor(model => attrValue.IsChecked, new { htmlAttributes = new { @id = @checkboxID, @value = @AttributeValueID, @class = "form-control form-control-sm" } })
                    @Html.ValidationMessageFor(model => attrValue.IsChecked, "", new { @class = "text-danger" })

                    @Html.LabelFor(model => attrValue.AttributeValueName, new { htmlAttributes = new { @class = "form-control form-control-sm" } })
                </td>
                <td>           
                    @Html.EditorFor(model => attrValue.Price, new { htmlAttributes = new { @id = @priceID, @class = "form-control form-control-sm" } })
                    @Html.ValidationMessageFor(model => attrValue.Price, "", new { @class = "text-danger" })
              </td>
            </tr>

为了触发mvc验证,但是,如果执行此操作,我将获得不同的输入名称,并且无法接收绑定到相应操作的值。

有帮助吗?

0 个答案:

没有答案