ASP.NET MVC - 如何在RedirectToAction中保留ModelState错误?

RPM*_*984 85 error-handling asp.net-mvc redirecttoaction modelstate http-redirect

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

[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"]});
   }   
}
Run Code Online (Sandbox Code Playgroud)

因此,如果验证通过,我将重定向到另一页(确认).

如果发生错误,我需要显示包含错误的同一页面.

如果我这样做return View(),则会显示错误,但如果我这样做return RedirectToAction(如上所述),则会丢失模型错误.

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

我当然可以返回相同的View而不是重定向,但我在"Create"方法中有逻辑,它填充了视图数据,我必须复制它.

有什么建议?

asg*_*eo1 76

我今天必须自己解决这个问题,并遇到了这个问题.

一些答案很有用(使用TempData),但并没有真正回答手头的问题.

我发现的最佳建议是在这篇博客文章中:

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"]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后根据您的示例,您可以保存/恢复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"]});
    }   
}
Run Code Online (Sandbox Code Playgroud)

如果您还想在TempData中传递模型(建议使用bigb),那么您仍然可以这样做.

  • 这个解决方案是我使用stackoverflow的原因.谢啦! (2认同)

Kun*_*vič 49

你需要有相同的实例Review上的HttpGet动作.为此,您应该Review reviewHttpPost操作中将对象保存在temp变量中,然后在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 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"]});
   }   
}
Run Code Online (Sandbox Code Playgroud)

另外我建议,如果你想让它在HttpGet第一次执行动作后按下浏览器刷新按钮也能正常工作,你可能会这样做

  Review review = TempData["Review"] as Review;  
  TempData["Review"] = review;
Run Code Online (Sandbox Code Playgroud)

否则刷新按钮对象review将为空,因为没有任何数据TempData["Review"].

  • @jfar,我同意,这个答案不起作用,并且不会持久存在于ModelState中.但是,如果你修改它,它就像`TempData ["ModelState"] = ModelState;`并用`ModelState.Merge((ModelStateDictionary)TempData ["ModelState"])恢复``,那么它会起作用 (17认同)
  • 这并没有真正回答标题中的问题.ModelState不会被保留,并且具有诸如输入HtmlHelpers之类的分支,而不会保留用户输入.这几乎是一种解决方法. (8认同)
  • 优秀。提及刷新问题的费用为+1。这是最完整的答案,一堆谢谢,我会接受的。:) (2认同)

Wim*_*Wim 7

为什么不使用"Create"方法中的逻辑创建一个私有函数,并从Get和Post方法中调用此方法,然后返回View().


rob*_*nal 5

我可以用TempData["Errors"]

TempData 在保存数据的操作中传递 1 次。


Ale*_*ant 5

Microsoft 删除了在 TempData 中存储复杂数据类型的功能,因此之前的答案不再有效;您只能存储简单类型,例如字符串。我已更改@asgeo1 的答案以按预期工作。

public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var controller = filterContext.Controller as Controller;
        var modelState = controller?.ViewData.ModelState;
        if (modelState != null)
        {
            var listError = modelState.Where(x => x.Value.Errors.Any())
                .ToDictionary(m => m.Key, m => m.Value.Errors
                .Select(s => s.ErrorMessage)
                .FirstOrDefault(s => s != null));
            controller.TempData["KEY HERE"] = JsonConvert.SerializeObject(listError);
        }
    }
}


public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var controller = filterContext.Controller as Controller;
        var tempData = controller?.TempData?.Keys;
        if (controller != null && tempData != null)
        {
            if (tempData.Contains("KEY HERE"))
            {
                var modelStateString = controller.TempData["KEY HERE"].ToString();
                var listError = JsonConvert.DeserializeObject<Dictionary<string, string>>(modelStateString);
                var modelState = new ModelStateDictionary();
                foreach (var item in listError)
                {
                    modelState.AddModelError(item.Key, item.Value ?? "");
                }

                controller.ViewData.ModelState.Merge(modelState);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

从这里,您可以根据需要在控制器方法上添加所需的数据注释。

[RestoreModelStateFromTempDataAttribute]
[HttpGet]
public async Task<IActionResult> MethodName()
{
}


[SetTempDataModelStateAttribute]
[HttpPost]
public async Task<IActionResult> MethodName()
{
    ModelState.AddModelError("KEY HERE", "ERROR HERE");
}
Run Code Online (Sandbox Code Playgroud)