ASP.NET MVC-如何在重定向操作中保存模型状态错误?
-
09-10-2019 - |
题
我有以下两种操作方法(简化了问题):
[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"]});
}
}
因此,如果验证通过,我将重定向到另一页(确认)。
如果发生错误,我需要显示带有错误的同一页面。
如果我做 return View()
, ,显示错误,但是如果我这样做 return RedirectToAction
(如上所述),它失去了模型错误。
我对这个问题并不感到惊讶,只是想知道你们如何处理这个问题?
我当然可以返回相同的视图而不是重定向,但是我在“创建”方法中具有逻辑,该方法填充了视图数据,我必须复制。
有什么建议么?
解决方案
您需要具有相同的实例 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"]});
}
}
如果您希望它可以正常工作,即使在第一次执行后刷新了浏览器 HttpGet
行动,您可以做到这一点:
Review review = TempData["Review"] as Review;
TempData["Review"] = review;
否则在刷新按钮对象上 review
会空的,因为没有任何数据 TempData["Review"]
.
其他提示
今天我必须自己解决这个问题,并遇到了这个问题。
一些答案很有用(使用tempdata),但并没有真正回答手头的问题。
我发现的最好的建议是在此博客文章中:
http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html
基本上,使用tempdata保存和还原模型状态对象。但是,如果您将其抽象成属性,则更干净。
例如
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"]);
}
}
}
然后,按照您的示例,您可以这样保存 /还原模型标准:
[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中传递模型(正如BigB所建议的那样),那么您仍然可以做到这一点。
为什么不在“创建”方法中使用逻辑创建一个私有函数,而是从get和post方法调用此方法,而只需返回视图()。
我可以使用 TempData["Errors"]
tempdata通过保存数据1次的负责操作传递。
我建议您返回视图,并避免通过动作的属性重复。这是填充数据的示例。您可以对创建方法逻辑进行类似的操作。
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);
}
我有一种将模型状态添加到温度数据的方法。然后,我的基本控制器中有一个方法,该方法检查了任何错误的临时数据。如果有它们,它将它们添加回模型State。
当我使用PRG模式时,我的情况更加复杂,因此我的ViewModel(summaryvm”)在tempdata中,并且我的摘要屏幕显示了它。此页面上有一个小表格,可以将一些信息发布到另一个动作中。并发症来自用户在此页面上摘要中编辑某些字段的要求。
summary.cshtml具有验证摘要,该摘要将捕获我们将创建的ModelState错误。
@Html.ValidationSummary()
现在,我的表格需要发布到httppost操作中以进行摘要()。我还有另一个非常小的ViewModel来代表编辑的字段,模型框架将把它们带给我。
新表格:
@using (Html.BeginForm("Summary", "MyController", FormMethod.Post))
{
@Html.Hidden("TelNo") @* // Javascript to update this *@
和动作...
[HttpPost]
public ActionResult Summary(EditedItemsVM vm)
在这里,我进行了一些验证,并检测到一些不良输入,因此我需要返回带有错误的摘要页面。为此,我使用tempdata,它将在重定向上幸存下来。如果数据没有问题,我将摘要对象替换为副本(但是,随着编辑的字段更改),请执行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);
}
}
我更喜欢在我的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...
}
}
然后,每当我需要这样的原始数据时,我都会称其为:
[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!");
}
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);
}
}
}
}
}
从这里开始,您可以根据需要在控制器方法上添加所需的数据注释。
[RestoreModelStateFromTempDataAttribute]
[HttpGet]
public async Task<IActionResult> MethodName()
{
}
[SetTempDataModelStateAttribute]
[HttpPost]
public async Task<IActionResult> MethodName()
{
ModelState.AddModelError("KEY HERE", "ERROR HERE");
}