107 votes

ASP.NET MVC - Comment préserver les erreurs de ModelState à travers RedirectToAction ?

J'ai les deux méthodes d'action suivantes (simplifiées pour la question) :

[HttpGet]
public ActionResult Create(string uniqueUri)
{
   // get some stuff based on uniqueuri, set in ViewData.  
   return View();
}

[HttpPost]
public ActionResult Create(Review review)
{
   // validate review
   if (validatedOk)
   {
      return RedirectToAction("Details", new { postId = review.PostId});
   }  
   else
   {
      ModelState.AddModelError("ReviewErrors", "some error occured");
      return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
   }   
}

Ainsi, si la validation passe, je redirige vers une autre page (confirmation).

Si une erreur se produit, je dois afficher la même page avec l'erreur.

Si je le fais return View() l'erreur est affichée, mais si je fais return RedirectToAction (comme ci-dessus), il perd les erreurs de modèle.

Je ne suis pas surpris par ce problème, mais je me demande comment vous gérez ça.

Je pourrais bien sûr simplement renvoyer la même vue au lieu de la redirection, mais j'ai une logique dans la méthode "Create" qui alimente les données de la vue, que je devrais dupliquer.

Des suggestions ?

10 votes

Je résous ce problème en n'utilisant pas le motif Post-Redirect-Get pour les erreurs de validation. J'utilise simplement View(). C'est parfaitement valable de faire cela au lieu de sauter à travers un tas de cerceaux - et la redirection gâche l'historique de votre navigateur.

2 votes

Et en plus de ce qu'a dit @JimmyBogard, extrayez la logique dans les Create qui remplit les ViewData et l'appelle dans la méthode Create GET et également dans la branche de validation échouée dans le fichier Create Méthode POST.

1 votes

Je suis d'accord, éviter le problème est une façon de le résoudre. J'ai une logique qui permet de remplir des éléments dans mon fichier Create je l'ai juste mis dans une méthode populateStuff que j'appelle dans les deux GET et l'échec POST .

88voto

asgeo1 Points 3336

J'ai dû résoudre moi-même ce problème aujourd'hui, et je suis tombé sur cette question.

Certaines des réponses sont utiles (utilisation de TempData), mais ne répondent pas vraiment à la question posée.

Le meilleur conseil que j'ai trouvé se trouve dans cet article de blog :

http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html

Fondamentalement, utilisez TempData pour enregistrer et restaurer l'objet ModelState. Cependant, c'est beaucoup plus propre si vous abstrayez tout cela dans des attributs.

Par exemple

public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);         
        filterContext.Controller.TempData["ModelState"] = 
           filterContext.Controller.ViewData.ModelState;
    }
}

public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        if (filterContext.Controller.TempData.ContainsKey("ModelState"))
        {
            filterContext.Controller.ViewData.ModelState.Merge(
                (ModelStateDictionary)filterContext.Controller.TempData["ModelState"]);
        }
    }
}

Ensuite, comme dans votre exemple, vous pourriez sauvegarder / restaurer le ModelState comme suit :

[HttpGet]
[RestoreModelStateFromTempData]
public ActionResult Create(string uniqueUri)
{
    // get some stuff based on uniqueuri, set in ViewData.  
    return View();
}

[HttpPost]
[SetTempDataModelState]
public ActionResult Create(Review review)
{
    // validate review
    if (validatedOk)
    {
        return RedirectToAction("Details", new { postId = review.PostId});
    }  
    else
    {
        ModelState.AddModelError("ReviewErrors", "some error occured");
        return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
    }   
}

Si vous voulez également transmettre le modèle dans TempData (comme bigb l'a suggéré), vous pouvez toujours le faire.

0 votes

Merci. Nous avons mis en œuvre quelque chose de similaire à votre approche. gist.github.com/ferventcoder/4735084

0 votes

@asgeo1 - excellente solution, mais j'ai rencontré un problème en l'utilisant en combinaison avec des vues partielles répétées. J'ai posté la question ici : stackoverflow.com/questions/28372330/

0 votes

Attention - si la page est servie en une seule requête (et non fractionnée via AJAX), vous risquez d'avoir des problèmes en utilisant cette solution, car les données temporelles sont conservées jusqu'à l'étape de l'enregistrement. suivant demande. Par exemple : vous entrez des critères de recherche dans une page, puis PRG aux résultats de la recherche, puis cliquez sur un lien pour retourner directement à la page de recherche, les valeurs de recherche originales seront repeuplées. D'autres comportements étranges et parfois difficiles à reproduire apparaissent également.

53voto

Riapp Points 2889

Vous devez avoir la même instance de Review sur votre HttpGet action. Pour ce faire, vous devez enregistrer un objet Review review dans une variable temporaire sur votre HttpPost et le restaurer ensuite sur HttpGet action.

[HttpGet]
public ActionResult Create(string uniqueUri)
{
   //Restore
   Review review = TempData["Review"] as Review;            

   // get some stuff based on uniqueuri, set in ViewData.  
   return View(review);
}
[HttpPost]
public ActionResult Create(Review review)
{
   //Save your object
   TempData["Review"] = review;

   // validate review
   if (validatedOk)
   {
      return RedirectToAction("Details", new { postId = review.PostId});
   }  
   else
   {
      ModelState.AddModelError("ReviewErrors", "some error occured");
      return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
   }   
}

Si vous voulez que cela fonctionne même si le navigateur est rafraîchi après la première exécution de la commande HttpGet action, vous pourriez faire ça :

  Review review = TempData["Review"] as Review;  
  TempData["Review"] = review;

Sinon, sur l'objet bouton de rafraîchissement review sera vide parce qu'il n'y aurait pas de données dans TempData["Review"] .

2 votes

Excellent. Et un gros +1 pour avoir mentionné le problème de rafraîchissement. C'est la réponse la plus complète, je l'accepte donc, merci beaucoup :)

8 votes

Cela ne répond pas vraiment à la question posée dans le titre. ModelState n'est pas préservé et cela a des ramifications telles que les aides Html d'entrée ne préservant pas la saisie de l'utilisateur. C'est presque une solution de contournement.

1 votes

J'ai fini par faire ce que @Wim a suggéré dans sa réponse.

7voto

Wim Points 1445

Pourquoi ne pas créer une fonction privée avec la logique de la méthode "Create" et appeler cette méthode à partir des méthodes "Get" et "Post" et faire simplement "return View()".

1 votes

C'est ce que je fais également, mais au lieu d'avoir une fonction privée, je fais simplement en sorte que la méthode POST appelle la méthode GET en cas d'erreur (c'est-à-dire que la méthode GET est appelée par la méthode POST). return Create(new { uniqueUri = ... }); . Votre logique reste DRY (tout comme l'appel RedirectToAction ), mais sans les problèmes liés à la redirection, comme la perte de votre ModelState.

1 votes

@DanielLiuzzi : en procédant de cette façon, l'URL ne sera pas modifiée. Vous devez donc terminer avec une URL comme "/controller/create/".

0 votes

@SkorunkaFrantišek Et c'est exactement le problème. La question dit Si une erreur se produit, je dois afficher la même page avec l'erreur. Dans ce contexte, il est parfaitement acceptable (et préférable OMI) que l'URL ne change PAS si la même page est affichée. En outre, l'un des avantages de cette approche est que si l'erreur en question n'est pas une erreur de validation mais une erreur système (dépassement de délai de la base de données, par exemple), l'utilisateur peut simplement rafraîchir la page pour soumettre à nouveau le formulaire.

5voto

rob waminal Points 4567

Je pourrais utiliser TempData["Errors"]

Les données temporaires sont transmises entre les actions en préservant les données une fois.

4voto

CRice Points 4717

Je vous suggère de retourner la vue, et d'éviter la duplication via un attribut sur l'action. Voici un exemple de remplissage des données de la vue. Vous pourriez faire quelque chose de similaire avec la logique de votre méthode de création.

public class GetStuffBasedOnUniqueUriAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var filter = new GetStuffBasedOnUniqueUriFilter();

        filter.OnActionExecuting(filterContext);
    }
}

public class GetStuffBasedOnUniqueUriFilter : IActionFilter
{
    #region IActionFilter Members

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {

    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Controller.ViewData["somekey"] = filterContext.RouteData.Values["uniqueUri"];
    }

    #endregion
}

Voici un exemple :

[HttpGet, GetStuffBasedOnUniqueUri]
public ActionResult Create()
{
    return View();
}

[HttpPost, GetStuffBasedOnUniqueUri]
public ActionResult Create(Review review)
{
    // validate review
    if (validatedOk)
    {
        return RedirectToAction("Details", new { postId = review.PostId });
    }

    ModelState.AddModelError("ReviewErrors", "some error occured");
    return View(review);
}

0 votes

En quoi est-ce une mauvaise idée ? Je pense que l'attribut évite de devoir utiliser une autre action, car les deux actions peuvent utiliser l'attribut pour charger les ViewData.

1 votes

Veuillez consulter le modèle Post/Redirect/Get : fr.wikipedia.org/wiki/Post/Redirect/Get

2 votes

Cette méthode est normalement utilisée une fois que la validation du modèle est satisfaite, afin d'éviter que le même formulaire ne soit à nouveau affiché lors de l'actualisation. Mais si le formulaire présente des problèmes, il doit être corrigé et réaffiché de toute façon. Cette question traite de la gestion des erreurs de modèle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X