У меня есть два следующих метода действий (упрощенные для вопроса):
[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
(как указано выше), он потеряет ошибки модели.
Я не удивлен этой проблемой, просто интересно, как вы с этим справляетесь?
Конечно, я мог бы просто вернуть то же представление вместо перенаправления, но у меня есть логика в методе «Create», который заполняет данные представления, которые мне пришлось бы продублировать.
Какие-либо предложения?
asp.net-mvc
error-handling
modelstate
redirecttoaction
http-redirect
Об / мин1984
источник
источник
Create
методе, который заполняет ViewData, и вызовите его вCreate
методе GET, а также в ветке неудачной проверки вCreate
методе POST.Create
представлении, я просто помещаю ее в какой-то метод,populateStuff
который я вызываю как в, такGET
и в ошибкеPOST
.Ответы:
В
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["ModelState"] = ModelState;
и восстанавливал с помощьюModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
, тогда он будет работатьreturn Create(uniqueUri)
тогда, когда проверка POST не удалась? Поскольку значения ModelState имеют приоритет над ViewModel, переданным в представление, опубликованные данные все равно должны оставаться.Мне пришлось сегодня самому решать эту проблему, и я столкнулся с этим вопросом.
Некоторые ответы полезны (с использованием 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 ().
источник
return Create(new { uniqueUri = ... });
ваша логика остается СУХОЙ (как и при вызовеRedirectToAction
), но без проблем, связанных с перенаправлением, таких как потеря вашего ModelState.Я мог бы использовать
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); }
источник
У меня есть метод, который добавляет состояние модели к временным данным. Затем у меня есть метод в моем базовом контроллере, который проверяет временные данные на наличие ошибок. Если они есть, он добавляет их обратно в ModelState.
источник
Мой сценарий немного сложнее, поскольку я использую шаблон PRG, поэтому моя ViewModel («SummaryVM») находится в TempData, и мой экран Summary отображает его. На этой странице есть небольшая форма для отправки информации в другое действие. Сложность возникла из-за того, что пользователю требовалось отредактировать некоторые поля в SummaryVM на этой странице.
Summary.cshtml содержит сводку проверки, которая будет улавливать ошибки ModelState, которые мы создадим.
@Html.ValidationSummary()
Моя форма теперь должна отправить POST в действие HttpPost для Summary (). У меня есть еще одна очень маленькая ViewModel для представления отредактированных полей, и привязка модели предоставит мне их.
Новая форма:
@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, с которого все это начинается, ищет любые ошибки во временных данных и добавляет их в состояние модели.
[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); } }
источник
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"); }
источник
Я предпочитаю добавить в свою 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!"); }
источник
Я даю здесь только образец кода. В вашем viewModel вы можете добавить одно свойство типа "ModelStateDictionary" как
public ModelStateDictionary ModelStateErrors { get; set; }
и в вашем методе действия POST вы можете писать код напрямую, например
model.ModelStateErrors = ModelState;
а затем назначьте эту модель Tempdata, как показано ниже
TempData["Model"] = model;
и когда вы перенаправляете на другой метод действия контроллера, тогда в контроллере вы должны прочитать значение Tempdata
if (TempData["Model"] != null) { viewModel = TempData["Model"] as ViewModel; //Your viewmodel class Type if(viewModel.ModelStateErrors != null && viewModel.ModelStateErrors.Count>0) { this.ViewData.ModelState.Merge(viewModel.ModelStateErrors); } }
Вот и все. Для этого не нужно писать фильтры действий. Это так же просто, как и приведенный выше код, если вы хотите передать ошибки состояния модели другому представлению другого контроллера.
источник