Как я могу поддерживать ModelState с RedirectToAction?

Как я могу вернуть результат другого действия или переместить пользователя на другое действие, если в моем ModelState есть ошибка, не потеряв информацию о ModelState?

Сценарий; Действие "Удалить" принимает POST из формы DELETE, отображаемой моим указателем Action/View. Если в Delete есть ошибка, я хочу переместить пользователя обратно в действие/представление индекса и показать ошибки, которые сохраняются действием Delete в ViewData.ModelState. Как это можно сделать в ASP.NET MVC?

[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Delete)]
public ActionResult Delete([ModelBinder(typeof(RdfUriBinder))] RdfUri graphUri)
{
    if (!ModelState.IsValid)
        return Index(); //this needs to be replaced with something that works :)

    return RedirectToAction("Index");
}

Ответ 1

Сохраняйте данные своего вида в TempData и извлеките их оттуда в своем указателе, если оно существует.

   ...
   if (!ModelState.IsValid)
       TempData["ViewData"] = ViewData;

   RedirectToAction( "Index" );
}

 public ActionResult Index()
 {
     if (TempData["ViewData"] != null)
     {
         ViewData = (ViewDataDictionary)TempData["ViewData"];
     }

     ...
 }

[EDIT] Я проверил он-лайн источник для MVC, и оказалось, что ViewData в контроллере настраивается, поэтому, вероятно, проще всего передать все ViewData, включая ModelState, в действие индекса.

Ответ 2

Используйте Action Filters (шаблон PRG) (так же просто, как и с использованием атрибутов)

Упоминается здесь и здесь.

Ответ 3

Обратите внимание, что решение tvanfosson не всегда будет работать, хотя в большинстве случаев оно должно быть прекрасным.

Проблема с этим конкретным решением заключается в том, что если у вас уже есть какие-либо ViewData или ModelState, вы в конечном итоге переписываете все это с предыдущим состоянием запроса. Например, новый запрос может иметь некоторые ошибки состояния модели, связанные с недействительными параметрами, передаваемыми в действие, но они будут скрыты, поскольку они перезаписаны.

Другая ситуация, когда она может работать не так, как ожидалось, - если у вас есть фильтр действий, который инициализировал некоторые ошибки ViewData или ModelState. Опять же, они будут перезаписаны этим кодом.

Мы рассматриваем некоторые решения для ASP.NET MVC, которые позволят вам более легко слить состояние из двух запросов, поэтому следите за обновлениями.

Спасибо, Eilon

Ответ 4

В случае, если это полезно для всех, кого я использовал @bob, рекомендованное решение с использованием PRG:

см. пункт 13 → ссылка.

У меня была дополнительная проблема с сообщениями, передаваемыми в VeiwBag, на представление, которое записывается и проверяется/загружается вручную из TempData в действиях контроллера при выполнении RedirectToAction("Action"). В попытке упростить (а также сделать его поддерживаемым) я немного расширил этот подход, чтобы проверить и сохранить/загрузить другие данные. Мои методы действий выглядели примерно так:

 [AcceptVerbs(HttpVerbs.Post)]
 [ExportModelStateToTempData]
 public ActionResult ChangePassword(ProfileViewModel pVM) {
      bool result = MyChangePasswordCode(pVM.ChangePasswordViewModel);
      if (result) {
           ViewBag.Message = "Password change success";
      else {
           ModelState.AddModelError("ChangePassword", "Some password error");
      }
      return RedirectToAction("Index");
    }

И мое действие в действии:

[ImportModelStateFromTempData]
public ActionResult Index() {
    ProfileViewModel pVM = new ProfileViewModel { //setup }
    return View(pVM);
}

Код в Action Filters:

// Following best practices as listed here for storing / restoring model data:
// http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
public abstract class ModelStateTempDataTransfer : ActionFilterAttribute {
    protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName;
}

:

public class ExportModelStateToTempData : ModelStateTempDataTransfer {
    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        //Only export when ModelState is not valid
        if (!filterContext.Controller.ViewData.ModelState.IsValid) {
            //Export if we are redirecting
            if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult)) {
                filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
            }
        }
        // Added to pull message from ViewBag
        if (!string.IsNullOrEmpty(filterContext.Controller.ViewBag.Message)) {
            filterContext.Controller.TempData["Message"] = filterContext.Controller.ViewBag.Message;
        }

        base.OnActionExecuted(filterContext);
    }
}

:

public class ImportModelStateFromTempData : ModelStateTempDataTransfer {
    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;

        if (modelState != null) {
            //Only Import if we are viewing
            if (filterContext.Result is ViewResult) {
                filterContext.Controller.ViewData.ModelState.Merge(modelState);
            } else {
                //Otherwise remove it.
                filterContext.Controller.TempData.Remove(Key);
            }
        }
        // Restore Viewbag message
        if (!string.IsNullOrEmpty((string)filterContext.Controller.TempData["Message"])) {
            filterContext.Controller.ViewBag.Message = filterContext.Controller.TempData["Message"];
        }

        base.OnActionExecuted(filterContext);
    }
}

Я понимаю, что мои изменения здесь являются довольно очевидным расширением того, что уже было сделано с ModelState по коду @ссылки, предоставленной @bob, - но мне пришлось наткнуться на этот поток, прежде чем я даже подумал о его обработке в этом путь.

Ответ 5

Возможно, попробуйте

return View("Index");

вместо

return Index();