Suppose an OnGet
handler loads data used on the page. Then if an error is encountered in a corresponding OnPost()
handler, it cannot simply return RedirectToPage()
but rather must reload that data somehow and return Page()
so as not to lose the ModelState
.
But return Page()
has side effects:
- it shows a URL like
www.example.com/Foo?handler=OnPostAddCustomer
, and - if the user presses refresh the browser shows the "do you want to repost?" error (because a PRG was not performed).
My hacky solution was to perform a PRG to avoid those problems, but pass the errors in TempData
so I can recreate the ModelState
in the OnGet()
handler:
[BindProperty] public List<long> Ids { get; } = new List<long>();
// ...
public async Task<IActionResult> OnGet()
{
// maybe recreate ModelState from OnPost handler
if (TempData["CustomModelState"] is string s) {
var errors = JsonSerializer.Deserialize<IDictionary<string, IEnumerable<string>>>(s);
if (errors != null) {
foreach (var (Key, Errors) in errors) {
foreach (var error in Errors) {
ModelState.AddModelError(Key, error);
}
}
}
}
// normal get request...
await LoadDataUsedOnPage();
return Page();
}
public async Task<IActionResult> OnPostCreateCustomer()
{
// normal error handling
if (!ModelState.IsValid)
return Page();
// custom error handling for empty list `Ids` (e.g. user must choose items from dropdown)
if (!Ids.Any()) {
ModelState.AddModelError(nameof(Ids), "Must choose at least one item.");
var errors =
ModelState
.Where(x => x.Value != null)
.Where(x => x.Value!.Errors.Any())
.ToDictionary(k => k.Key, v => v.Value!.Errors.Select(y => y.ErrorMessage));
TempData["CustomModelState"] = JsonSerializer.Serialize(errors);
return RedirectToPage(); // will run OnGet() again and thus load data
}
// normal post request...
await PersistData();
return RedirectToPage(); // PRG
}
I've always avoided TempData
as it seems dirty, but in this case I can't find a better way. (PS: I used TempData["CustomModelState"]
instead of TempData["ModelState"]
as it seems the latter was already used.)
What is the canonical way to solve this problem? Is there a built-in approach for this scenario?