在View中从ViewModel迭代ICollection

时间:2015-04-15 14:20:23

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

我有两个自动生成的数据库模型(产品 ProductDetails ),我将其合并到ViewModel中,以便我可以一次编辑所有数据。

让我感到困惑的是我应该在视图内迭代ProductDatactCategoryAttributes(在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循环中生成的控件的属性值由属性值获取,在本例中为,的 idProductCategoryAttribute 即可。如果我没有错,一个解决方案就是将ICollection转换为IList,但是自动生成模型我不认为这是最好的选择。

2 个答案:

答案 0 :(得分:2)

您无法在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<>列表,然后再返回,具体取决于您是post还是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; }
}

答案 1 :(得分:2)

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

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的另一个优点是它们可以在其他视图中重用。您还可以通过将其定位到与控制器关联的视图文件夹中为类型创建多个EditorTemplate,例如EditorTemplate

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