ASP.NET MVC-как сохранить ошибки ModelState через RedirectToAction?
у меня есть следующие два метода действий (упрощенный на вопрос):
[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
(как и выше), он теряет ошибки модели.
Я не удивлен этой проблемой, просто интересно, как вы, ребята, справляетесь с этим?
Я могу конечно, просто верните то же самое представление вместо перенаправления, но у меня есть логика в методе "создать", который заполняет данные представления, которые мне придется дублировать.
какие предложения?
9 ответов:
вы должны иметь тот же экземпляр
Review
наHttpGet
действие. Для этого вы должны сохранить объектReview review
в переменной temp на вашем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 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"]}); } }
также я бы посоветовал, Если вы хотите, чтобы он работал также при нажатии кнопки обновления браузера после
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 для сохранения и восстановления объекта 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 (как предложил bigb), вы все равно можете это сделать.
Почему бы не создать частную функцию с логикой в методе "Create" и не вызвать этот метод из метода Get и Post и просто не вернуть View().
Я мог бы использовать
TempData["Errors"]
TempData передаются через действия, сохраняющие данные 1 раз.
Я предлагаю вам вернуть представление и избежать дублирования через атрибут действия. Вот пример заполнения для просмотра данных. Вы можете сделать что-то подобное с вашей логикой метода create.
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); }
У меня есть метод, который добавляет состояние модели к временным данным. Затем у меня есть метод в моем базовом контроллере, который проверяет временные данные на наличие ошибок. Если они у него есть, он добавляет их обратно в ModelState.
мой сценарий немного сложнее, поскольку я использую шаблон PRG, поэтому мой ViewModel ("SummaryVM") находится в TempData, и мой экран сводки отображает его. На этой странице есть небольшая форма для публикации некоторой информации в другом действии. Сложность возникла из-за требования к пользователю редактировать некоторые поля в SummaryVM на этой странице.
резюме.cshtml имеет сводку проверки, которая будет ловить ошибки ModelState, которые мы создадим.
@Html.ValidationSummary()
моя форма сейчас необходимо опубликовать действие HttpPost для Summary (). У меня есть еще один очень маленький ViewModel для представления отредактированных полей, и modelbinding получит их мне.
новая форма:
@using (Html.BeginForm("Summary", "MyController", FormMethod.Post)) { @Html.Hidden("TelNo") @* // Javascript to update this *@
и действия...
[HttpPost] public ActionResult Summary(EditedItemsVM vm)
здесь я делаю некоторые проверки, и я обнаруживаю некоторые плохие входные данные, поэтому мне нужно вернуться на страницу резюме с ошибками. Для этого я использую 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");
действие Summary controller, где все это начинается, ищет любые ошибки в tempdata и добавляет их в 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); } }
Я предпочитаю добавить метод в мой 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"); }