ASP.NET MVC – Comment préserver les erreurs ModelState sur RedirectToAction?

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

[HttpGet] public ActionResult Create(ssortingng 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"]}); } } 

Donc, si la validation réussit, je redirige vers une autre page (confirmation).

Si une erreur survient, je dois afficher la même page avec l’erreur.

Si je return View() , l’erreur s’affiche, mais si je return RedirectToAction (comme ci-dessus), les erreurs du modèle sont perdues.

Je ne suis pas surpris par le problème, je me demande comment vous vous en sortez?

Je pourrais bien sûr juste retourner la même vue au lieu de la redirection, mais j’ai la logique dans la méthode “Create” qui remplit les données de vue, que je devrais dupliquer.

Aucune suggestion?

    Vous devez avoir la même instance de Review sur votre action HttpGet . Pour ce faire, vous devez enregistrer un object Review review dans la variable temporaire de votre action HttpPost , puis le restaurer sur l’action HttpGet .

     [HttpGet] public ActionResult Create(ssortingng 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 you 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"]}); } } 

    Aussi je conseillerais, si vous voulez le faire fonctionner aussi quand le bouton d’actualisation du navigateur pressé après l’action HttpGet exécutée la première fois, vous pouvez aller comme ça

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

    Sinon, lors de l’actualisation du bouton, la review object sera vide car il n’y aurait aucune donnée dans TempData["Review"] .

    J’ai dû résoudre ce problème aujourd’hui moi-même et j’ai rencontré cette question.

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

    Le meilleur conseil que j’ai trouvé était sur ce blog:

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

    Fondamentalement, utilisez TempData pour enregistrer et restaurer l’object ModelState. Cependant, c’est beaucoup plus propre si vous faites abstraction de ceci en atsortingbuts.

    Par exemple

     public class SetTempDataModelStateAtsortingbute : ActionFilterAtsortingbute { public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); filterContext.Controller.TempData["ModelState"] = filterContext.Controller.ViewData.ModelState; } } public class RestoreModelStateFromTempDataAtsortingbute : ActionFilterAtsortingbute { 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, selon votre exemple, vous pouvez enregistrer / restaurer le ModelState comme suit:

     [HttpGet] [RestoreModelStateFromTempData] public ActionResult Create(ssortingng 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 souhaitez également transmettre le modèle dans TempData (comme suggéré par bigb), vous pouvez toujours le faire.

    Pourquoi ne pas créer une fonction privée avec la logique dans la méthode “Create” et appeler cette méthode à partir de la méthode Get et de la méthode Post et renvoyer simplement View ().

    Je pourrais utiliser TempData["Errors"]

    TempData sont transmis à travers les actions en conservant les données 1 fois.

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

     public class GetStuffBasedOnUniqueUriAtsortingbute : ActionFilterAtsortingbute { 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); } 

    J’ai une méthode qui ajoute l’état du modèle aux données temporaires. J’ai ensuite une méthode dans mon contrôleur de base qui vérifie les données temporaires pour toutes les erreurs. Si c’est le cas, il les ajoute à ModelState.

    Mon scénario est un peu plus compliqué car j’utilise le modèle PRG, donc mon ViewModel (“SummaryVM”) se trouve dans TempData et mon écran Summary l’affiche. Il y a un petit formulaire sur cette page pour envoyer des informations à une autre action. La complication provient de la nécessité pour l’utilisateur de modifier certains champs dans SummaryVM sur cette page.

    Summary.cshtml a le résumé de validation qui détectera les erreurs ModelState que nous allons créer.

     @Html.ValidationSummary() 

    Mon formulaire doit maintenant afficher une action HttpPost pour Summary (). J’ai un autre très petit ViewModel pour représenter les champs édités et le modelbinding me les fournira.

    La nouvelle forme:

     @using (Html.BeginForm("Summary", "MyController", FormMethod.Post)) { @Html.Hidden("TelNo") @* // Javascript to update this *@ 

    et l’action …

     [HttpPost] public ActionResult Summary(EditedItemsVM vm) 

    Ici, je fais de la validation et je détecte de mauvaises entrées, donc je dois retourner à la page Résumé avec les erreurs. Pour cela, j’utilise TempData, qui survivra à une redirection. S’il n’y a pas de problème avec les données, je remplace l’object SummaryVM par une copie (mais avec les champs modifiés bien sûr), puis effectuez une RedirectToAction (“NextAction”);

     // Telephone number wasn't in the right format List listOfErrors = new List(); listOfErrors.Add("Telephone Number was not in the correct format. Value supplied was: " + vm.TelNo); TempData["SummaryEditedErrors"] = listOfErrors; return RedirectToAction("Summary"); 

    L’action du contrôleur Summary, où tout cela commence, recherche les erreurs éventuelles dans tempdata et les ajoute à l’état du modèle.

     [HttpGet] [OutputCache(Duration = 0)] public ActionResult Summary() { // setup, including resortingeval of the viewmodel from TempData... // And finally if we are coming back to this after a failed attempt to edit some of the fields on the page, // load the errors stored from TempData. List editErrors = new List(); object errData = TempData["SummaryEditedErrors"]; if (errData != null) { editErrors = (List)errData; foreach(ssortingng err in editErrors) { // ValidationSummary() will see these ModelState.AddModelError("", err); } } 

    Je préfère append une méthode à mon ViewModel qui remplit les valeurs par défaut:

     public class RegisterViewModel { public ssortingng FirstName { get; set; } public IList Genders { get; set; } //Some other properties here .... //... //... ViewModelType PopulateDefaultViewData() { this.FirstName = "No body"; this.Genders = new List() { Gender.Male, Gender.Female }; //Maybe other assinments here for other properties... } } 

    Alors je l’appelle quand j’ai besoin des données originales comme ceci:

      [HttpGet] public async Task Register() { var vm = new RegisterViewModel().PopulateDefaultViewValues(); return View(vm); } [HttpPost] public async Task Register(RegisterViewModel vm) { if (!ModelState.IsValid) { return View(vm.PopulateDefaultViewValues()); } var user = await userService.RegisterAsync( email: vm.Email, password: vm.Password, firstName: vm.FirstName, lastName: vm.LastName, gender: vm.Gender, birthdate: vm.Birthdate); return Json("Registered successfully!"); }