Handle errors in RazorPages OnPost handler without side effects

86 Views Asked by At

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:

  1. it shows a URL like www.example.com/Foo?handler=OnPostAddCustomer, and
  2. 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?

0

There are 0 best solutions below