Итерация ICollection из ViewModel в представлении

У меня есть две автоматически сгенерированные модели базы данных (Product и ProductDetails), которые я объединил в ViewModel, чтобы я мог редактировать все данные одновременно.

Что меня смущает, так это часть, где я должен перебирать ICollection Product_ProductCategoryAttributes (в модели 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. У одного продукта может быть много сведений о продукте.

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, получают свое имя по значению свойства, которым в данном случае является Value и АтрибутКатегорииПродукта. Если я не ошибаюсь, одним из решений было бы преобразование ICollection в IList, но с автогенерацией модели я не думаю, что это был бы лучший вариант.


person wegelagerer    schedule 15.04.2015    source источник


Ответы (2)


Вы не можете использовать ElementAt() в лямбда-выражении в помощниках HTML. Имя, которое будет сгенерировано, будет просто именем поля без индексов, которое позволяет заполнять опубликованные значения.

Вы должны использовать индексы для прохождения всей модели представления, чтобы сгенерированные имена действительно совпадали.

Итак, это:

 @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<> и обратно, в зависимости от того, 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; }
}
person Luke    schedule 15.04.2015
comment
Индексы нельзя использовать с ICollections, поэтому, к сожалению, это не ответ. - person wegelagerer; 15.04.2015
comment
Тогда это невозможно. ICollection не имеет индексатора, и для их сопоставления требуется индекс. Он не может сопоставить определенные поля, если в списке нет порядка.... - person Luke; 15.04.2015
comment
IList наследуется от ICollection, так что смена модели, скорее всего, подойдет - person Luke; 15.04.2015
comment
Не будет ли изменение ICollection на IList в случае автоматического обновления модели (например, в случае изменений в таблицах базы данных) изменить IList обратно на ICollection. Я имею в виду, что изменить его вручную не составит труда, но мне придется напоминать себе делать это каждый раз, когда он обновляется. - person wegelagerer; 15.04.2015
comment
Установка IList в ViewModel невозможна, так как я передаю существующую модель базы данных в ViewModel. Короче говоря, проблемную ICollection можно найти в модели ProductDetail. - person wegelagerer; 15.04.2015
comment
Вы не должны передавать модель базы данных в модель представления, если только ее нельзя изменить для использования IList‹› или индексированного массива. Ваш ProductViewModel должен содержать объект, который используется только как модель представления. Не модель представления, а объект базы данных. - person Luke; 15.04.2015
comment
Давайте продолжим обсуждение в чате. - person wegelagerer; 15.04.2015

Если ваша модель ICollection<T> (и ее нельзя изменить на IList<T> или использовать в цикле for), вам нужно использовать пользовательский EditorTemplate для typeof T

In /Views/Shared/EditorTemplates/Product_ProductCategoryAttribute.cshtml

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

In /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 для типа, разместив их в папке представления, связанной с контроллером, например /Views/YourControllerName/EditorTemplates/ProductDetail.cshtml.

Примечание. В любом случае вы должны использовать модели представления для каждого типа, которые включают только те свойства, которые вы хотите редактировать/отображать в представлении.

person Community    schedule 15.04.2015