Saya memiliki dua metode tindakan berikut (disederhanakan untuk pertanyaan):
[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"]});
}
}
Jadi, jika validasi lolos, saya redirect ke halaman lain (konfirmasi).
Jika terjadi kesalahan, saya perlu menampilkan halaman yang sama dengan kesalahan tersebut.
Jika saya lakukan return View()
, kesalahan ditampilkan, tetapi jika saya lakukan return RedirectToAction
(seperti di atas), itu kehilangan kesalahan Model.
Saya tidak terkejut dengan masalah ini, hanya bertanya-tanya bagaimana kalian menangani ini?
Tentu saja saya bisa mengembalikan Tampilan yang sama daripada pengalihan, tetapi saya memiliki logika dalam metode "Buat" yang mengisi data tampilan, yang harus saya duplikat.
Ada saran?
Create
metode yang mengisi ViewData dan panggil dalamCreate
metode GET dan juga di cabang validasi yang gagal dalamCreate
metode POST.Create
pandangan saya , saya hanya memasukkannya ke dalam beberapa metodepopulateStuff
yang saya panggil baik dalamGET
dan gagalPOST
.Jawaban:
Anda harus memiliki contoh yang sama
Review
tentangHttpGet
tindakan Anda . Untuk melakukan itu, Anda harus menyimpan objekReview review
dalam variabel temp padaHttpPost
tindakan Anda dan kemudian mengembalikannya padaHttpGet
tindakan.[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"]}); } }
Jika Anda ingin ini berfungsi meskipun browser di-refresh setelah eksekusi pertama
HttpGet
tindakan, Anda dapat melakukan ini:Review review = TempData["Review"] as Review; TempData["Review"] = review;
Jika tidak, objek tombol segarkan
review
akan kosong karena tidak akan ada data masukTempData["Review"]
.sumber
TempData["ModelState"] = ModelState;
dan memulihkanModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
, maka itu akan berfungsireturn Create(uniqueUri)
ketika validasi gagal pada POST? Karena nilai ModelState lebih diutamakan daripada ViewModel yang diteruskan ke tampilan, data yang diposting harus tetap ada.Saya harus menyelesaikan masalah ini sendiri hari ini, dan menemukan pertanyaan ini.
Beberapa jawaban berguna (menggunakan TempData), tetapi tidak benar-benar menjawab pertanyaan yang ada.
Saran terbaik yang saya temukan ada di entri blog ini:
http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html
Pada dasarnya, gunakan TempData untuk menyimpan dan memulihkan objek ModelState. Namun, jauh lebih bersih jika Anda mengabstraksi ini menjadi atribut.
Misalnya
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"]); } } }
Kemudian sesuai contoh Anda, Anda dapat menyimpan / memulihkan ModelState seperti:
[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"]}); } }
Jika Anda juga ingin meneruskan model di TempData (seperti yang disarankan bigb) maka Anda masih bisa melakukannya juga.
sumber
Mengapa tidak membuat fungsi privat dengan logika dalam metode "Buat" dan memanggil metode ini dari metode Get dan Post dan cukup mengembalikan View ().
sumber
return Create(new { uniqueUri = ... });
. Logika Anda tetap KERING (seperti memanggilRedirectToAction
), tetapi tanpa masalah yang dibawa oleh pengalihan, seperti kehilangan ModelState Anda.Saya bisa menggunakan
TempData["Errors"]
TempData diteruskan melalui tindakan yang menyimpan data 1 kali.
sumber
Saya sarankan Anda mengembalikan tampilan, dan menghindari duplikasi melalui atribut pada tindakan. Berikut adalah contoh pengisian untuk melihat data. Anda bisa melakukan hal serupa dengan logika metode create Anda.
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 }
Berikut ini contohnya:
[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); }
sumber
Saya memiliki metode yang menambahkan status model ke data temp. Saya kemudian memiliki metode di pengontrol dasar saya yang memeriksa data temp untuk kesalahan apa pun. Jika memilikinya, itu menambahkan mereka kembali ke ModelState.
sumber
Skenario saya sedikit lebih rumit karena saya menggunakan pola PRG sehingga ViewModel saya ("SummaryVM") ada di TempData, dan layar Ringkasan saya menampilkannya. Ada formulir kecil di halaman ini untuk POST beberapa info ke Action lain. Komplikasi berasal dari persyaratan bagi pengguna untuk mengedit beberapa bidang di SummaryVM pada halaman ini.
Summary.cshtml memiliki ringkasan validasi yang akan menangkap kesalahan ModelState yang akan kita buat.
@Html.ValidationSummary()
Formulir saya sekarang perlu POST ke tindakan HttpPost untuk Summary (). Saya memiliki ViewModel lain yang sangat kecil untuk mewakili bidang yang diedit, dan modelbinding akan memberikannya kepada saya.
Bentuk baru:
@using (Html.BeginForm("Summary", "MyController", FormMethod.Post)) { @Html.Hidden("TelNo") @* // Javascript to update this *@
dan aksinya ...
[HttpPost] public ActionResult Summary(EditedItemsVM vm)
Di sini saya melakukan beberapa validasi dan mendeteksi beberapa input yang buruk, jadi saya perlu kembali ke halaman Ringkasan dengan kesalahan. Untuk ini saya menggunakan TempData, yang akan bertahan dari pengalihan. Jika tidak ada masalah dengan data, saya mengganti objek SummaryVM dengan salinan (tetapi tentu saja dengan bidang yang diedit berubah) kemudian lakukan 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");
Tindakan pengontrol Ringkasan, tempat semua ini dimulai, mencari error apa pun dalam tempdata dan menambahkannya ke 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); } }
sumber
Microsoft menghapus kemampuan untuk menyimpan tipe data kompleks di TempData, oleh karena itu jawaban sebelumnya tidak lagi berfungsi; Anda hanya dapat menyimpan jenis sederhana seperti string. Saya telah mengubah jawaban oleh @ asgeo1 agar berfungsi seperti yang diharapkan.
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); } } } }
Dari sini, Anda cukup menambahkan anotasi data yang diperlukan pada metode pengontrol sesuai kebutuhan.
[RestoreModelStateFromTempDataAttribute] [HttpGet] public async Task<IActionResult> MethodName() { } [SetTempDataModelStateAttribute] [HttpPost] public async Task<IActionResult> MethodName() { ModelState.AddModelError("KEY HERE", "ERROR HERE"); }
sumber
Saya lebih suka menambahkan metode ke ViewModel saya yang mengisi nilai default:
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... } }
Kemudian saya menyebutnya ketika saya membutuhkan data asli seperti ini:
[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!"); }
sumber
Saya hanya memberikan kode contoh di sini Di viewModel Anda, Anda dapat menambahkan satu properti jenis "ModelStateDictionary" sebagai
public ModelStateDictionary ModelStateErrors { get; set; }
dan dalam metode tindakan POST, Anda dapat menulis kode secara langsung seperti
model.ModelStateErrors = ModelState;
dan kemudian tetapkan model ini ke Tempdata seperti di bawah ini
TempData["Model"] = model;
dan ketika Anda mengarahkan ke metode tindakan pengontrol lain maka di pengontrol Anda harus membaca nilai 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); } }
Itu dia. Anda tidak perlu menulis filter tindakan untuk ini. Ini sesederhana kode di atas jika Anda ingin mendapatkan kesalahan status Model ke tampilan lain dari pengontrol lain.
sumber