asp.net mvc - net - ¿Cómo puedo mantener ModelState con RedirectToAction?
libro asp.net mvc 5 español (5)
Almacene sus datos de vista en TempData y recupérelos desde allí en su acción de índice, si existe.
...
if (!ModelState.IsValid)
TempData["ViewData"] = ViewData;
RedirectToAction( "Index" );
}
public ActionResult Index()
{
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
...
}
[EDIT] Comprobé la fuente en línea para MVC y parece que ViewData en el Controller es configurable, por lo que probablemente sea más fácil simplemente transferir todos los ViewData, incluido el ModelState, a la acción Index.
¿Cómo puedo devolver el resultado de una acción diferente o mover al usuario a una acción diferente si hay un error en mi ModelState sin perder la información de mi ModelState?
El escenario es; La acción de eliminación acepta una POST de un formulario DELETE rendido por mi acción / vista de índice. Si hay un error en la eliminación, deseo mover al usuario de nuevo a la acción / vista de índice y mostrar los errores almacenados por la acción Eliminar en ViewData.ModelState
. ¿Cómo se puede hacer esto en 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");
}
En caso de que esto sea útil para cualquier persona, utilicé la solución recomendada de @bob utilizando PRG:
ver el elemento 13 -> link .
Tuve el problema adicional de que los mensajes se pasaban en el VeiwBag a la vista que se está escribiendo y comprobando / cargando manualmente desde TempData en las acciones del controlador al hacer una RedirectToAction("Action")
. En un intento de simplificar (y también hacer que se pueda mantener) extendí ligeramente este enfoque para verificar y almacenar / cargar otros datos también. Mis métodos de acción se parecían a los siguientes:
[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");
}
Y mi acción de índice:
[ImportModelStateFromTempData]
public ActionResult Index() {
ProfileViewModel pVM = new ProfileViewModel { //setup }
return View(pVM);
}
El código en los filtros de acción:
// 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);
}
}
Me doy cuenta de que mis cambios aquí son una extensión bastante obvia de lo que ya se estaba haciendo con ModelState mediante el código @ el enlace provisto por @bob, pero tuve que tropezar con este hilo incluso antes de pensar en manejarlo de esta manera.
Tal vez intente
return View("Index");
en lugar de
return Index();
Tenga en cuenta que la solución de tvanfosson no siempre funcionará, aunque en la mayoría de los casos debería estar bien.
El problema con esa solución en particular es que si ya tiene ViewData o ModelState, termina sobreescribiendo todo con el estado de la solicitud anterior. Por ejemplo, la nueva solicitud puede tener algunos errores de estado del modelo relacionados con parámetros no válidos que se pasan a la acción, pero que terminarían ocultos porque se sobrescriben.
Otra situación en la que podría no funcionar como se espera es si tenía un filtro de acción que inicializó algunos errores de ViewData o ModelState. De nuevo, serían sobrescritos por ese código.
Estamos buscando algunas soluciones para ASP.NET MVC que le permitan fusionar más fácilmente el estado de las dos solicitudes, así que estén atentos para eso.
Gracias, Eilon