2008-11-11 9 views
64

¿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?¿Cómo puedo mantener ModelState con RedirectToAction?

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 el 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"); 
} 

Respuesta

89

debe almacenar datos de vista en TempData y recuperarla desde allí en su acción index, si es que existe.

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

    RedirectToAction("Index"); 
} 

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

    ... 
} 

[EDIT] he comprobado la fuente en línea para MVC y parece que la ViewData en el controlador es ajustable, por lo que es probablemente más fácil sólo para transferir todos los ViewData, incluyendo el ModelState, a la Acción del índice

+0

ViewData.ModelState no tiene un setter. –

+0

bien. copiar en lugar de establecer no tuvo acceso a VS para verificarlo desde su casa. – tvanfosson

+0

, necesitará verificar la sintaxis de la función de copiado; nuevamente, no hay VS en casa. – tvanfosson

-2

quizás tratar

return View("Index"); 

en lugar de

return Index(); 
+0

Eso no funciona, ya que no ejecuta la lógica en la acción del índice. Todo lo que hace es intentar renderizar el modelo actual usando la vista de índice. –

+0

¿No solo desea mostrar los errores del modelo en la misma vista desde la que publicó? ¿Qué estás haciendo en la acción del índice que debe ejecutarse cuando hay errores del modelo? Acabo de devolver View ("ViewName", modelo) cuando hay errores y funciona bien. –

+0

No, deseo redireccionar a la acción Index para enlazar la vista a los datos generados por esa acción, así como también al ModelState que fue definido por la acción fallida Delete. –

10

Tenga en cuenta que la solución de tvanfosson no siempre funciona, aunque en la mayoría de los casos, debe estar muy bien.

El problema con esa solución particular es que si ya tiene alguna ViewData o ModelState que terminan sobrescribir 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 ha tenido una acción de filtrado que inicializa algunos errores ViewData o ModelState. De nuevo, serían sobrescritos por ese código.

Estamos pensando en algunas soluciones para ASP.NET MVC que permitirían a combinar más fácilmente el estado de las dos peticiones, así que estad atentos para eso.

Gracias, Eilon

+0

Hey Eilon, la respuesta de @bob (del blog de Kazi Manzur Rashid) sigue siendo la mejor manera de hacer esto, o el equipo de MVC recomienda algún otro método actualmente? –

+1

@PatrickMcDonald No hay nada nuevo en MVC que yo puedo pensar que eso resolvería esto. Sin embargo, me gustaría advertir sobre la sobreescritura completa de ViewData y, en su lugar, ser más selectivo sobre lo que se copia desde la solicitud anterior a la nueva solicitud. – Eilon

+0

ModelState tiene una función 'Merge'. –

37

Filtros Uso de acción (PRG) (patrón tan fácil como usar atributos)

mencionado here y here.

+3

La mejor respuesta para este problema IMO. –

+2

Sí, esta es la respuesta.Sin embargo, no estoy seguro de por qué esos atributos de acción no están en el marco MVC, ya que es un escenario bastante común –

+0

ModelState.Merge() era lo que estaba buscando. Gran enlace también. +1 – ryanulit

5

En caso de que esto es útil para cualquier persona que solía solución recomendada @bob @ s utilizando PRG:

tema, véase 13 ->link.

tuve la emisión adicional de los mensajes que se pasa en el VeiwBag a la vista que se escriben y se comprueba/carga manual de TempData en las acciones del controlador cuando se hace 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 parecían algo así como:

[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 Índice de Acción:

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

El código en la acción Filtros:

// 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); 
    } 
} 

realizo mis cambios aquí son una extensión bastante obvio de lo que ya se está haciendo con el ModelState por el código @ el enlace proporcionado por @bob - pero tenía que tropezar en este hilo antes de que yo pensaba de la manipulación de esta manera.

+0

Gracias, solo necesita editar la línea de ImportModelStateFromTempData para vistas parciales >> if (filterContext.Result es ViewResult || filterContext.Result es PartialViewResult) – k4st0r42

Cuestiones relacionadas