在 View 中从 ViewModel 迭代 ICollection

Iterating ICollection from ViewModel within View

我有两个自动生成的数据库模型(ProductProductDetails),我将它们合并到一个 ViewModel 中,这样我就可以一次编辑所有数据.

让我感到困惑的是我应该在视图中循环访问 Product_ProductCategoryAttributes 的 ICollection(在 ProductDetail 模型中)以允许 .NET 自动将属性绑定到 ViewModel 的部分。我已经尝试使用 for 以及 foreach 循环但没有任何成功,因为正在创建的控件名称错误(自动绑定需要)。

产品型号

public partial class Product
{
    public Product()
    {
        this.ProductDetail = new HashSet<ProductDetail>();
    }

    public int idProduct { get; set; }
    public int idProductCategory { get; set; }
    public string EAN { get; set; }
    public string UID { get; set; }
    public bool Active { get; set; }

    public virtual ProductCategory ProductCategory { get; set; }
    public virtual ICollection<ProductDetail> ProductDetail { get; set; }
}

ProductDetail 型号

public partial class ProductDetail
{
    public ProductDetail()
    {
        this.Product_ProductCategoryAttribute = new HashSet<Product_ProductCategoryAttribute>();
    }

    public int idProductDetail { get; set; }
    public int idProductCategory { get; set; }
    public int idMeta { get; set; }
    public int idProduct { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }

    public virtual Meta Meta { get; set; }
    public virtual Product Product { get; set; }
    public virtual ICollection<Product_ProductCategoryAttribute> Product_ProductCategoryAttribute { get; set; }
    public virtual ProductCategory ProductCategory { get; set; }
}

ProductViewModel - 一个产品可以有多个 ProductDetails

public class ProductViewModel
{
    public Product Product { get; set; }
    public List<ProductDetail> ProductDetails { get; set; }

}

查看(部分代码有意省略)

@for (int i = 0; i < Model.ProductDetails.Count(); i++)
{
    @Html.TextAreaFor(model => model.ProductDetails[i].Description, new { @class = "form-control", @rows = "3" })

    @for (int j = 0; j < Model.ProductDetails[i].Product_ProductCategoryAttribute.Count(); j++)
    {
       @Html.HiddenFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute.ElementAt(j).idProductCategoryAttribute)
       @Html.TextBoxFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute.ElementAt(j).Value, new { @class = "form-control" })
    }
 }

第二个 for 循环 之外的所有控件都已正确命名,例如。 ProductDetails[0].Description,但是在第二个 for 循环 中生成的控件通过 属性 值获取它们的名称,在本例中是 ValueidProductCategoryAttribute。如果我没记错的话,一种解决方案是将 ICollection 转换为 IList,但自动生成模型我认为这不是最佳选择。

您不能在 HTML 助手的 lambda 中使用 ElementAt()。将生成的名称将只是没有索引的字段名称,它允许填充发布的值。

您应该使用索引遍历整个视图模型,以便生成的名称实际匹配。

所以这个:

 @Html.HiddenFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute.ElementAt(j).idProductCategoryAttribute)

应该是这个,或者类似的:

@Html.HiddenFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute[j].idProductCategoryAttribute)

至于将模型从 ICollection 更改为 IList,这很好,因为 IList 继承自 ICollection。但是正如你所说它是自动生成的,如果你先使用代码 entity framework 或类似的东西可能没问题。

真正的解决方案是将传入模型(视图模型)映射到自动生成的 ICollection<> 列表,然后再返回,这取决于您是 posting 还是 get婷.

在下面的示例中,我们获取发布的值并将它们映射到自动生成的 Product 对象并对其进行操作。

    ///
    /// ProductViewModel incoming model contains IList<> fields, and could be used as the view model for your page
    ///
    [HttpPost]
    public ActionResult Index(ProductViewModel requestModel)
    {
        // Create instance of the auto generated model (with ICollections)
        var product = new Product();

        // Map your incoming model to your auto generated model
        foreach (var productDetailViewModel in requestModel)
        {
             product.ProductDetail.Add(new ProductDetail()
             {
                 Product_ProductCategoryAttribute = productDetailViewModel.Product_ProductCategoryAttribute;

                 // Map other fields here
             }
        }

        // Do something with your product
        this.MyService.SaveProducts(product);

        // Posted values will be retained and passed to view
        // Or map the values back to your valid view model with `List<>` fields
        // Or pass back the requestModel back to the view
        return View();
    }

ProductViewModel.cs

public class ProductViewModel
{
    // This shouldn't be here, only fields that you need from Product should be here and mapped within your controller action
    //public Product Product { get; set; }

    // This should be a view model, used for the view only and not used as a database model too!
    public List<ProductDetailViewModel> ProductDetails { get; set; }
}

如果您的模型是 ICollection<T>(并且不能更改为 IList<T> 或在 for 循环中使用),那么您需要使用自定义 EditorTemplate 对于 typeof T

/Views/Shared/EditorTemplates/Product_ProductCategoryAttribute.cshtml

@model yourAssembly.Product_ProductCategoryAttribute
@Html.HiddenFor(m => m.idProductCategoryAttribute)
@Html.TextBoxFor(m => m.Value, new { @class = "form-control" })

/Views/Shared/EditorTemplates/ProductDetail.cshtml

@model yourAssembly.ProductDetail
@Html.TextAreaFor(m => m.Description, new { @class = "form-control", @rows = "3" })
@Html.EditorFor(m => m.Product_ProductCategoryAttribute)

在主视图中

@model yourAssembly.ProductViewModel
@using (Html.BeginForm())
{
  ...
  @Html.EditorFor(m => m.ProductDetails)
  ...

EditorFor() 方法将识别集合 (IEnumerable<T>) 并使用相应的 EditorTemplate 呈现集合中的每个项目,包括在控件中添加索引器 name 属性,以便在 post.

时绑定集合

自定义 EditorTemplate 复杂类型的另一个优点是它们可以在其他视图中重复使用。您还可以通过将它们放置在与控制器关联的视图文件夹中来为一个类型创建多个 EditorTemplate,例如 /Views/YourControllerName/EditorTemplates/ProductDetail.cshtml

旁注。在任何情况下,您都应该为每种类型使用视图模型,只包含您希望在视图中 edit/display 的那些属性。