Связка модели и скрытые поля
-
07-09-2020 - |
Вопрос
У меня есть упрощенный тестовый сценарий, полезный для ответа на этот вопрос:Продукт может иметь множество Компонентов, Компонент может принадлежать многим Продуктам.EF сгенерировал классы, я сократил их следующим образом:
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Component> Components { get; set; }
}
public partial class Component
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Создание компонента осуществляется с помощью следующих действий контроллера:
public ActionResult Create(int ProductId)
{
Product p = db.Products.Find(ProductId);
Component c = new Component();
c.Products.Add(p);
return PartialView(c);
}
[HttpPost]
public ActionResult Create(Component model)
{
db.Components.Add(model);
db.SaveChanges();
}
и представление, возвращаемое методом GET, выглядит следующим образом:
@model Test.Models.Product
<fieldset>
<legend>Product</legend>
<div class="display-label">Name</div>
<div class="display-field">@Model.Name</div>
</fieldset>
@Html.Action("Create", "Component", new {ProductId = Model.Id})
<p>
@Html.ActionLink("Edit", "Edit", new { id=Model.Id }) |
@Html.ActionLink("Back to List", "Index")
</p>
Из чего видно, что создание компонента выполняется на той же странице с помощью приведенного выше Html.Action
- код для этого представления следующий:
@model Test.Models.Component
@using Test.Models
<script type="text/javascript">
function Success() {
alert('ok');
}
function Failure() {
alert('err');
}
</script>
@using (Ajax.BeginForm("Create", "Component", new AjaxOptions
{
HttpMethod = "Post",
OnSuccess = "Success",
OnFailure = "Failure"
}))
{
<fieldset>
<legend>Components</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
@Html.HiddenFor(x => x.Products.First().Id)
@Html.HiddenFor(x => x.Products)
@foreach (Product p in Model.Products)
{
@Html.Hidden("Products[0].Id", p.Id)
}
@foreach (Product p in Model.Products)
{
@Html.Hidden("[0].Id", p.Id)
}
</fieldset>
<input type="submit" value="go" />
}
хорошо.так вот с чем я борюсь:мне нужно model
параметр [HttpPost]back для правильного заполнения, т.е.он должен содержать продукт, поскольку я не могу создать новый компонент с нулевым продуктом.Чтобы получить продукт, мне нужно найти его по идентификатору продукта.Я ожидаю, что смогу сделать:
model.Products.Add(db.Products.Find(model.Products.First().Id));
или что-то в этом роде, которое зависит от model
получение идентификатора.Это означает, что представление должно поместить идентификатор туда, предположительно в скрытое поле, и, как видно из моего кода представления, я предпринял несколько попыток заполнить это поле, но все они потерпели неудачу.
Обычно я предпочитаю методы *For, поскольку они отвечают за создание правильной номенклатуры.Если бы .Products было в единственном числе (.Product), я мог бы ссылаться на него как x => x.Product.Id
и все бы ничего, но так как во множественном числе, я не могу сделать x => x.Products.Id
поэтому я попробовал x => x.Products.First().Id
который компилируется и выдает правильное значение, но получает имя Id
(что неверно, поскольку связыватель модели считает, что это Component.Id
и не Component.Products[0].Id
.
Моя вторая попытка заключалась в том, чтобы позволить HiddenFor
повторять (как я бы сделал с EditorFor
):
@Html.HiddenFor(x => x.Products)
но это ничего не дает - я читал, что этот помощник не выполняет итерации.Я пытался x => x.Products.First()
но это даже не компилируется.Наконец, я решил отказаться от *For и закодировать имя самостоятельно:
@foreach (Product p in Model.Products)
{
@Html.Hidden("Products[0].Id", p.Id)
и хотя это выглядит правильно, обратная передача не видит моей ценности (Products.Count
== 0).Я видел в некоторых сообщениях, что формат должен выглядеть так [0].Id
но это тоже не работает.грр...
Я так понимаю, я мог бы закодировать это так:
@Html.Hidden("ProductId", p.Id)
а затем переобъявите действие моего контроллера следующим образом:
[HttpPost] ActionResult Create(Component model, int ProductId)
но это кажется омерзительным.трудно поверить, что это так сложно.может кто-нибудь помочь?
- е
п.с.У меня есть проект, который я могу сделать доступным для скачивания, если кому-то интересно.
Решение
Вместо того, чтобы писать эти foreach
циклы попробуйте использовать шаблоны редактора:
<fieldset>
<legend>Components</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
@Html.EditorFor(x => x.Products)
</fieldset>
и внутри соответствующего шаблона редактора (~/Views/Shared/EditorTemplates/Product.cshtml
)
@model Product
@Html.HiddenFor(x => x.Id)