ASP.NET MVC – 如何保留RedirectToAction的ModelState错误?

我有以下两个操作方法(简化问题):

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

所以,如果validation通过,我redirect到另一个页面(确认)。

如果发生错误,我需要显示与错误相同的页面。

如果我return View() ,则会显示错误,但是如果return RedirectToAction (如上所示),则会丢失模型错误。

我对这个问题并不感到惊讶,只是想知道你们是如何处理这个问题的?

我当然可以只是返回相同的视图,而不是redirect,但我有填充视图数据,我不得不重复的“创build”方法的逻辑。

有什么build议么?

您需要在您的HttpGet操作上拥有相同的Review实例。 要做到这一点,你应该保存一个对象在你的HttpPost动作的临时variablesReview review ,然后恢复HttpGet行动。

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

另外我会build议,如果你想让它工作,当浏览器刷新button后执行第一次HttpGet行动,你可能会像这样

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

否则,刷新button对象review将是空的,因为TempData["Review"]没有任何数据。

我今天不得不解决这个问题,并且遇到了这个问题。

一些答案是有用的(使用TempData),但是并没有真正回答这个问题。

我发现的最好的build议是在这篇博文中:

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

基本上,使用TempData来保存和恢复ModelState对象。 但是,如果将其抽象为属性,则会更清晰。

例如

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

然后按照你的例子,你可以像这样保存/恢复ModelState:

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

如果你还想在TempData中传递模型(如bigbbuild议),那么你仍然可以这样做。

为什么不用“Create”方法中的逻辑创build一个私有函数,并从Get和Post方法中调用这个方法,并返回View()。

我build议你返回视图,并通过动作的一个属性避免重复。 这是一个填充查看数据的例子。 你可以用你的创build方法逻辑做类似的事情。

 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 } 

对于那些刚刚很难想象如何工作的人来说,

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

我可以使用TempData["Errors"]

TempData通过保存数据1次的操作传递。

我有一种将模型状态添加到临时数据的方法。 然后我有一个方法在我的基地控制器检查任何错误的临时数据。 如果有,则将其添加回模型状态。

我的场景有点复杂,因为我使用的是PRG模式,所以我的ViewModel(“SummaryVM”)在TempData中,我的摘要屏幕显示它。 这个页面上有一个小表单,用于向另一个Action发送一些信息。 复杂性来源于用户需要编辑此页面的SummaryVM中的一些字段。

Summary.cshtml具有validation摘要,它将捕获我们将创build的ModelState错误。

 @Html.ValidationSummary() 

我的表单现在需要发布到Summary()的HttpPost操作。 我有另一个非常小的视图模型来表示编辑的领域,和modelbinding会得到这些给我。

新的forms:

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

和行动…

 [HttpPost] public ActionResult Summary(EditedItemsVM vm) 

在这里我做了一些validation,我发现一些不好的input,所以我需要返回到错误的摘要页面。 为此我使用TempData,它将在redirect中生存下来。 如果数据没有问题,我将SummaryVM对象replace为一个副本(但当然编辑的字段已更改),然后执行RedirectToAction(“NextAction”);

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

摘要控制器操作(在这一切开始的地方)查找tempdata中的任何错误并将它们添加到模型状态。

 [HttpGet] [OutputCache(Duration = 0)] public ActionResult Summary() { // setup, including retrieval 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<string> editErrors = new List<string>(); object errData = TempData["SummaryEditedErrors"]; if (errData != null) { editErrors = (List<string>)errData; foreach(string err in editErrors) { // ValidationSummary() will see these ModelState.AddModelError("", err); } }