Интересный вопрос. Давайте сначала проиллюстрируем проблему на простом примере, потому что, судя по другим ответам, я не уверен, что все поняли, в чем здесь проблема.
Предположим следующую модель:
public class MyViewModel
{
public int Id { get; set; }
public bool Delete { get; set; }
}
следующий контроллер:
public class HomeController : Controller
{
public ActionResult Index()
{
// Initially we have 2 items in the database
var model = new[]
{
new MyViewModel { Id = 2 },
new MyViewModel { Id = 1 }
};
return View(model);
}
[HttpDelete]
public ActionResult Index(MyViewModel[] model)
{
// simulate a validation error
ModelState.AddModelError("", "some error occured");
if (!ModelState.IsValid)
{
// We refetch the items from the database except that
// a new item was added in the beginning by some other user
// in between
var newModel = new[]
{
new MyViewModel { Id = 3 },
new MyViewModel { Id = 2 },
new MyViewModel { Id = 1 }
};
return View(newModel);
}
// TODO: here we do the actual delete
return RedirectToAction("Index");
}
}
и вид:
@model MyViewModel[]
@Html.ValidationSummary()
@using (Html.BeginForm())
{
@Html.HttpMethodOverride(HttpVerbs.Delete)
for (int i = 0; i < Model.Length; i++)
{
<div>
@Html.HiddenFor(m => m[i].Id)
@Html.CheckBoxFor(m => m[i].Delete)
@Model[i].Id
</div>
}
<button type="submit">Delete</button>
}
Вот что произойдет:
Пользователь переходит к действию Index
, выбирает первый элемент для удаления и нажимает кнопку «Удалить». Вот как выглядит представление перед отправкой формы:
Вызывается действие «Удалить», и когда представление отображается еще раз (из-за ошибки проверки), пользователю предоставляется следующее:
Видите, как предварительно выбран неправильный элемент?
Почему это происходит? Поскольку помощники HTML используют значение ModelState в приоритете при привязке вместо значения модели, и это предусмотрено дизайном.
Итак, как решить эту проблему? Прочитав следующий пост в блоге Фила Хаака: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
В своем блоге он рассказывает о непоследовательных индексах и приводит следующий пример:
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
Видите, как мы больше не используем инкрементные индексы для имен кнопок ввода?
Как мы применим это к нашему примеру?
Как это:
@model MyViewModel[]
@Html.ValidationSummary()
@using (Html.BeginForm())
{
@Html.HttpMethodOverride(HttpVerbs.Delete)
for (int i = 0; i < Model.Length; i++)
{
<div>
@Html.Hidden("index", Model[i].Id)
@Html.Hidden("[" + Model[i].Id + "].Id", Model[i].Id)
@Html.CheckBox("[" + Model[i].Id + "].Delete", Model[i].Delete)
@Model[i].Id
</div>
}
<button type="submit">Delete</button>
}
Теперь проблема устранена. Или это? Вы видели тот ужасный беспорядок, который сейчас представляет вид? Мы исправили одну проблему, но добавили в представление нечто совершенно отвратительное. Не знаю, как вас, а когда я смотрю на это, меня тошнит.
Так что же можно было сделать? Мы должны прочитать сообщение в блоге Стивена Сандерсона: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/, в котором он представляет очень интересный пользовательский помощник Html.BeginCollectionItem
, который используется следующим образом:
<div class="editorRow">
<% using(Html.BeginCollectionItem("gifts")) { %>
Item: <%= Html.TextBoxFor(x => x.Name) %>
Value: $<%= Html.TextBoxFor(x => x.Price, new { size = 4 }) %>
<% } %>
</div>
Заметили, как элементы формы упакованы в этот помощник?
Что делает этот помощник? Он заменяет последовательные индексы, сгенерированные строго типизированными помощниками, на Guids и использует дополнительное скрытое поле для установки этого индекса на каждой итерации.
При этом проблема проявляется только в том случае, если вам нужно получить свежие данные из вашей базы данных в действии «Удалить». Если вы полагаетесь на связыватель модели для регидратации, вообще не будет никаких проблем (за исключением того, что если есть ошибка модели, вы покажете представление со старыми данными -> что, вероятно, не так уж проблематично):
[HttpDelete]
public ActionResult Index(MyViewModel[] model)
{
// simulate a validation error
ModelState.AddModelError("", "some error occured");
if (!ModelState.IsValid)
{
return View(model);
}
// TODO: here we do the actual delete
return RedirectToAction("Index");
}
person
Darin Dimitrov
schedule
19.06.2012