asp.net-mvc ASP.NET MVC - 如何在 RedirectToAction 中保留 ModelState 错误?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4642845/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ASP.NET MVC - How to Preserve ModelState Errors Across RedirectToAction?
提问by RPM1984
I have the following two action methods (simplified for 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"]});
}
}
So, if the validation passes, i redirect to another page (confirmation).
因此,如果验证通过,我将重定向到另一个页面(确认)。
If an error occurs, i need to display the same page with the error.
如果发生错误,我需要显示与错误相同的页面。
If i do return View()
, the error is displayed, but if i do return RedirectToAction
(as above), it loses the Model errors.
如果我这样做return View()
,则会显示错误,但如果我这样做return RedirectToAction
(如上所述),则会丢失模型错误。
I'm not surprised by the issue, just wondering how you guys handle this?
我对这个问题并不感到惊讶,只是想知道你们是如何处理这个问题的?
I could of course just return the same View instead of the redirect, but i have logic in the "Create" method which populates the view data, which i'd have to duplicate.
我当然可以只返回相同的视图而不是重定向,但是我在“创建”方法中有逻辑来填充视图数据,我必须复制这些数据。
Any suggestions?
有什么建议?
采纳答案by kuncevic.dev
You need to have the same instance of Review
on your HttpGet
action.
To do that you should save an object Review review
in temp variable on your HttpPost
action and then restore it on HttpGet
action.
你需要Review
在你的HttpGet
动作上有相同的实例。为此,您应该Review review
在操作的临时变量中保存一个对象HttpPost
,然后在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"]});
}
}
If you want this to work even if the browser is refreshed after the first execution of the HttpGet
action, you could do this:
如果您希望即使在第一次执行HttpGet
操作后刷新浏览器也能正常工作,您可以这样做:
Review review = TempData["Review"] as Review;
TempData["Review"] = review;
Otherwise on refresh button object review
will be empty because there wouldn't be any data in TempData["Review"]
.
否则刷新按钮对象review
将为空,因为TempData["Review"]
.
回答by asgeo1
I had to solve this problem today myself, and came across this question.
我今天不得不自己解决这个问题,并遇到了这个问题。
Some of the answers are useful (using TempData), but don't really answer the question at hand.
一些答案很有用(使用 TempData),但并没有真正回答手头的问题。
The best advice I found was on this blog post:
我发现最好的建议是在这篇博文中:
http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html
http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html
Basically, use TempData to save and restore the ModelState object. However, it's a lot cleaner if you abstract this away into attributes.
基本上,使用 TempData 来保存和恢复 ModelState 对象。但是,如果您将其抽象为属性,它会更清晰。
E.g.
例如
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"]);
}
}
}
Then as per your example, you could save / restore the ModelState like so:
然后根据您的示例,您可以像这样保存/恢复 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"]});
}
}
If you also want to pass the model along in TempData (as bigb suggested) then you can still do that too.
如果您还想在 TempData 中传递模型(如 bigb 建议的那样),那么您仍然可以这样做。
回答by Wim
Why not create a private function with the logic in the "Create" method and calling this method from both the Get and the Post method and just do return View().
为什么不使用“Create”方法中的逻辑创建一个私有函数,并从 Get 和 Post 方法调用此方法,然后只执行 return View()。
回答by CRice
I suggest you return the view, and avoid duplication via an attribute on the action. Here is an example of populating to view data. You could do something similar with your create method logic.
我建议您返回视图,并通过操作上的属性避免重复。这是填充以查看数据的示例。您可以对创建方法逻辑执行类似的操作。
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
}
Here is an example:
下面是一个例子:
[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);
}
回答by rob waminal
I could use TempData["Errors"]
我可以用 TempData["Errors"]
TempData are passed accross actions preserving data 1 time.
TempData 在保存数据的操作之间传递 1 次。
回答by nick
I have a method that adds model state to temp data. I then have a method in my base controller that checks temp data for any errors. If it has them, it adds them back to ModelState.
我有一种将模型状态添加到临时数据的方法。然后我在我的基本控制器中有一个方法来检查临时数据是否有任何错误。如果有它们,它会将它们添加回 ModelState。
回答by VictorySaber
My scenario is a little bit more complicated as I am using the PRG pattern so my ViewModel ("SummaryVM") is in TempData, and my Summary screen displays it. There is a small form on this page to POST some info to another Action. The complication has come from a requirement for the user to edit some fields in SummaryVM on this page.
我的场景有点复杂,因为我使用的是 PRG 模式,所以我的 ViewModel(“SummaryVM”)在 TempData 中,我的摘要屏幕显示它。此页面上有一个小表单,用于将一些信息发布到另一个操作。复杂性来自要求用户在此页面上编辑 SummaryVM 中的某些字段。
Summary.cshtml has the validation summary which will catch ModelState errors that we'll create.
Summary.cshtml 包含验证摘要,它将捕获我们将创建的 ModelState 错误。
@Html.ValidationSummary()
My form now needs to POST to a HttpPost action for Summary(). I have another very small ViewModel to represent edited fields, and modelbinding will get these to me.
我的表单现在需要为 Summary() POST 到 HttpPost 操作。我有另一个非常小的 ViewModel 来表示已编辑的字段,模型绑定会将这些交给我。
The new form:
新形式:
@using (Html.BeginForm("Summary", "MyController", FormMethod.Post))
{
@Html.Hidden("TelNo") @* // Javascript to update this *@
and the action...
和行动...
[HttpPost]
public ActionResult Summary(EditedItemsVM vm)
In here I do some validation and I detect some bad input, so I need to return to the Summary page with the errors. For this I use TempData, which will survive a redirection. If there is no issue with the data, I replace the SummaryVM object with a copy (but with the edited fields changed of course) then do a RedirectToAction("NextAction");
在这里,我进行了一些验证并检测到一些错误的输入,因此我需要返回包含错误的“摘要”页面。为此,我使用 TempData,它将在重定向后继续存在。如果数据没有问题,我将用副本替换 SummaryVM 对象(但编辑的字段当然已更改),然后执行 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");
The Summary controller action, where all this begins, looks for any errors in the tempdata and adds them to the modelstate.
所有这一切开始的摘要控制器操作会查找临时数据中的任何错误并将它们添加到模型状态中。
[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);
}
}
回答by Mohammed Noureldin
I prefer to add a method to my ViewModel which populates the default values:
我更喜欢向我的 ViewModel 添加一个方法来填充默认值:
public class RegisterViewModel
{
public string FirstName { get; set; }
public IList<Gender> Genders { get; set; }
//Some other properties here ....
//...
//...
ViewModelType PopulateDefaultViewData()
{
this.FirstName = "No body";
this.Genders = new List<Gender>()
{
Gender.Male,
Gender.Female
};
//Maybe other assinments here for other properties...
}
}
Then I call it when ever I need the original data like this:
然后我在需要这样的原始数据时调用它:
[HttpGet]
public async Task<IActionResult> Register()
{
var vm = new RegisterViewModel().PopulateDefaultViewValues();
return View(vm);
}
[HttpPost]
public async Task<IActionResult> 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!");
}
回答by Alex Marchant
Microsoft removed the ability to store complex data types in TempData, therefore the previous answers no longer work; you can only store simple types like strings. I have altered the answer by @asgeo1 to work as expected.
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);
}
}
}
}
}
From here, you can simply add the required data annotation on a controller method as needed.
从这里,您可以根据需要简单地在控制器方法上添加所需的数据注释。
[RestoreModelStateFromTempDataAttribute]
[HttpGet]
public async Task<IActionResult> MethodName()
{
}
[SetTempDataModelStateAttribute]
[HttpPost]
public async Task<IActionResult> MethodName()
{
ModelState.AddModelError("KEY HERE", "ERROR HERE");
}