MVC 5 BeginCollectionItem с частичным CRUD
Ниже я внес изменения в вопрос, который все тот же, но, надеюсь, намного более понятный по моделям и в отношении того, чего я хочу достичь и где я столкнулся с проблемами.
Ниже показаны два класса, Компания и Сотрудник, Компания имеет список Сотрудников.
Это будет форма ввода, поэтому там не будет никаких данных для начала.
В конечном итоге я хочу, чтобы пользователь мог добавлять столько объектов Employee в объектную модель Company, сколько они хотят, и для объектов Employee, которые будут обновлены.
Я на правильном пути с использованием BeginCollectionItem, чтобы я мог добавить / удалить столько объектов Employee, сколько я хочу? Когда я нажимаю кнопку "Добавить", она переходит в частичное представление на другой странице (с AjaxActionLink), но не с JavaScript.
Обновление Удалил AjaxActionLink и использовал вместо него JavaScript.
Индекс
@model MvcTest.Models.Company
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Company</h2>
<div>
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)
</div>
<fieldset>
<legend>Employees</legend>
<div id="new-Employee">
@foreach (var Employee in Model.Employees)
{
Html.RenderPartial("_Employee", Employee);
}
</div>
<div>
<input type="button" id="addemployee" name="addemployee" value="Add Employee"/>
<br/>
</div>
<br/>
@section Scripts
{
<script type="text/javascript">
$('#addemployee').on('click', function () {
$.ajax({
async: false,
url: '/Company/AddNewEmployee'
}).success(function (partialView) {
$('#new-Employee').append(partialView);
});
});
</script>
}
</fieldset>
<div>
<input type="submit" value="Submit" />
</div>
_Employee PartialView
@model MvcTest.Models.Employee
@using (Html.BeginCollectionItem("Employees"))
{
<div class="employeeRow">
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)
@Html.LabelFor(m => m.Telephone)
@Html.EditorFor(m => m.Telephone)
@Html.LabelFor(m => m.Mobile)
@Html.EditorFor(m => m.Mobile)
@Html.LabelFor(m => m.JobTitle)
@Html.EditorFor(m => m.JobTitle)
<a href="#" class="deleteRow">Delete</a>
</div>
}
@section Scripts
{
$("a.deleteRow").live("click", function(){
$(this).parents("div.employeeRow:first").remove();
return false;
});
}
контроллер
public class CompanyController : Controller
{
// GET: Company
public ActionResult Index()
{
var newCompany = new Company();
return View(newCompany);
}
public ActionResult AddNewEmployee()
{
var employee = new Employee();
return PartialView("_Employee", employee);
}
}
модель
public class Company
{
[Key]
public int Id { get; set; }
[Display(Name = "Company")]
public string Name { get; set; }
public List<Employee> Employees { get; set; }
//public Company()
//{
// Employees = new List<Employee>
// {
// new Employee{ Name = "Enter name"}
// };
//}
}
public class Employee
{
[Key]
public int Id { get; set; }
[Display(Name="Employee")]
public string Name { get; set; }
public string Telephone { get; set; }
public string Mobile {get;set;}
[Display(Name="Job Title")]
public string JobTitle {get;set;}
}
4 ответа
Вам не нужно использовать BeginCollectionItem для достижения этой цели. Из-за того, что мне пришлось изучать его самостоятельно и пытаться использовать его для решения аналогичной проблемы, оказалось, что он был создан для подобных проблем с более ранними версиями MVC.
Используйте частичные представления для отображения и обновления списка. Один частичный вид для отображения и итерации по списку объектов, а другой для создания нового объекта, который после публикации для обновления списка покажет вновь созданный объект в частичном представлении со списком.
Я разместил подобный вопрос здесь, который должен решить вашу проблему, нажмите здесь
Надеюсь это поможет.
Обновление Причина, по которой ваше удаление не работает, заключается в том, что вы не можете вызвать JS из частичного просмотра, поместите его в главное представление (@section Script). Также я думаю, что вы немного запутались с вашими ключевыми словами class и id в ваших divs, посмотрите ниже.
Итак, вы должны иметь:
Частичный вид
@model MvcTest.Models.Employee
@using (Html.BeginCollectionItem("Employees"))
{
<div id="employeeRow" class="employeeRow">
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)
@Html.LabelFor(m => m.Telephone)
@Html.EditorFor(m => m.Telephone)
@Html.LabelFor(m => m.Mobile)
@Html.EditorFor(m => m.Mobile)
@Html.LabelFor(m => m.JobTitle)
@Html.EditorFor(m => m.JobTitle)
<a href="#" id="deleteRow" class="deleteRow" onclick="deleteFunction()">Delete</a>
</div>
}
Главный вид
@model MvcTest.Models.Company
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Company</h2>
<div>
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)
</div>
<fieldset>
<legend>Employees</legend>
<div id="new-Employee">
@foreach (var Employee in Model.Employees)
{
Html.RenderPartial("_Employee", Employee);
}
</div>
<div>
<input type="button" id="addemployee" name="addemployee" value="Add Employee"/>
<br/>
</div>
<br/>
@section Scripts
{
<script type="text/javascript">
$('#addemployee').on('click', function () {
$.ajax({
async: false,
url: '/Company/AddNewEmployee'
}).success(function (partialView) {
$('#new-Employee').append(partialView);
});
});
$("#deleteRow").live("click", function () {
$(this).parents("#employeeRow:first").remove();
return false;
});
</script>
}
</fieldset>
<div>
<input type="submit" value="Submit" />
</div>
Допустим ClassA
ваша ViewModel:
Вам нужно только одно частичное представление или представление, чтобы обновить оба:
например Edit.cshtml
@model ClassA
@Html.BeginForm(){
@Html.TextBoxFor(m => m.name)
for(int i = 0; i< Model.classB.Count; i++){
@Html.TextBoxFor(m => Model.classB[i].name)
<button type="button"> Add </button>
<button type="button"> Remove </button>
}
<input type="submit" value = "save"/>
}
Примечание. В частичном представлении вы используете цикл foreach. Для подшивки модели MVC поля ввода должны иметь формат:
list[0].prop1
list[0].prop2
list[0].prop3
list[1].prop1
list[1].prop2
list[1].prop3
Так что для этого мы используем для цикла
Тогда в контроллере:
[HttpPost]
public ActionResult Edit(ClassA model){
// HEre you will see model.ClassB list
// you can then save them one by one
foreach(var item in Model.classB){
save
}
return View(model);
}
Если вы хотите динамически добавлять или удалять элементы из списка:
Создать еще один частичный вид classBs
:
@model List<classB>
<div id="lists">
foreach (var contact in Model)
{
@Html.Partial("ClassBRow", contact)
}
</div>
<button data-action-url="@Url.Action("ClassBRow","CONTROLLER")" class="btn btn-primary pull-right" id="addItem">Add</button>
<script>
$("#addItem").click(function () {
var btn = $(this);
$.ajax({
url: btn.data('action-url'),
success: function (html) {
$("#lists").append(html);
}
});
return false;
});
</script>
Создайте еще один частичный вид: ClassBRow.cshtml
:
@model classB
using (Html.BeginCollectionItem("classB"))
{
@Html.HiddenFor(m => m.isDeleted, new { data_is_deleted = "false" })
@Html.TextBoxFor(m => Model.name, new { @class = "form-control" })
<span class="glyphicon glyphicon-trash" data-action="removeItem" title="remove" style="cursor:pointer"></span>
}
В вашем контроллере:
public ActionResult ClassBRow()
{
return PartialView(new classB());
}
И Edit.cshtml становится:
@model ClassA
@Html.BeginForm(){
@Html.TextBoxFor(m => m.name)
@Html.Partial("classBs", model.classB)
<input type="submit" value = "save"/>
}
Рассматривайте взгляды как независимые. Если бы ваш частичный просмотр был полным просмотром, вы бы передали его IEnumerable<ClassB>
так рендер с:
@Html.Partial("ClassBView", Model.ClassB)
Кроме того, вы не можете использовать foreach
с EditorFor
поскольку недостаточно информации для создания имени на основе индекса. Вместо этого используйте обычный цикл for. Парсер выражений может затем преобразовать его в name="name[1]"
и т. д., поскольку индекс доступен в выражении:
@model IEnumerable<Project.Models.ClassB>
@if(Model != null)
{
for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
@Html.EditorFor(modelItem => Model[i].Name)
<input type="button" value="Clear" />
<input type="submit" value="Create" />
</td>
</tr>
}
}
В вашем примере больше не хватает (что Clear
а также Create
подключиться к, например), поэтому, если вы можете предоставить оставшуюся часть кода вашего контроллера, я расширю это.
Вы можете использовать (EditorTemplates) для просмотра (ClassB) следующим образом:
1- Создайте папку с именем (EditorTemplates) в папке (Views/Home) (при условии, что имя вашего контроллера - Home):
2- В созданной папке (EditorTemplates) создайте представление с именем (ClassB)
и добавьте следующий шаблон для представления (ClassB):
@model Project.Models.ClassB
@if(Model != null)
{
<tr>
<td>
@Html.EditorFor(modelItem => Model.Name)
<input type="button" value="Clear" />
<input type="submit" value="Create" />
</td>
</tr>
}
и (ClassAView) должен быть следующим:
@model Project.Models.ClassA
<tr>
<td>
@Html.EditorFor(m => m.name)
</td>
<td>
@Html.EditorFor(m => m.classB);
</td>
</tr>
Редактор автоматически перебирает список объектов, отображающих представление для каждого из них.