【问题标题】:Iterating ICollection from ViewModel within View在 View 中从 ViewModel 迭代 ICollection
【发布时间】:2015-04-15 14:20:22
【问题描述】:

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

让我感到困惑的是我应该在视图中迭代 Product_ProductCategoryAttributes(在 ProductDetail 模型中)的 ICollection 以允许 .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,但自动生成模型我认为这不是最佳选择。

【问题讨论】:

    标签: c# asp.net-mvc entity-framework viewmodel asp.net-mvc-viewmodel


    【解决方案1】:

    您不能在 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。但是正如你所说的它是自动生成的,如果你使用代码优先实体框架或类似的东西可能会没问题。

    真正的解决方案是将传入模型(视图模型)映射到自动生成的 ICollection&lt;&gt; 列表并再次映射回来,具体取决于您是 posting 还是 getting。

    在下面的示例中,我们将发布的值映射到自动生成的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; }
    }
    

    【讨论】:

    • 索引不能与 ICollections 一起使用,所以很遗憾这不是答案。
    • 那就不可能了。 ICollection 没有索引器,它需要一个索引来匹配它们。当列表没有顺序时,它无法匹配特定字段......
    • IList 继承自 ICollection,因此更改模型可能会很好
    • 不会在模型自动更新的情况下将 ICollection 更改为 IList(例如,在数据库表发生更改的情况下)将 IList 更改回 ICollection。我的意思是手动更改它不会是一个问题,但是每次更新时我都必须提醒自己这样做。
    • 在 ViewModel 中设置 IList 是不可能的,因为我将现有的数据库模型传递给 ViewModel。总之,有问题的ICollection可以在ProductDetail模型中找到。
    【解决方案2】:

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

    /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&lt;T&gt;) 并使用相应的EditorTemplate 呈现集合中的每个项目,包括在控件name 属性中添加索引器,以便在何时绑定集合你发帖。

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

    旁注。在任何情况下,您都应该为每种类型使用视图模型,其中仅包含您要在视图中编辑/显示的那些属性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多