RedirectToAction after validation errors

2019-07-04 03:06发布

问题:

If I have the usual Edit actions, one for GET to retrieve an object by it's ID and to display it in an edit form. The next for POST to take the values in the ViewModel and update the object in the database.

public virtual ActionResult Edit(int id)

[HttpPost]
public ActionResult Edit(VehicleVariantEditSaveViewModel viewModel)

If an error occurs during model binding in the POST action, I understand I can RedirectToAction back to the GET action and preserve the ModelState validation errors by copying it to TempData and retrieving it after the redirect in the GET action.

if (TempData["ViewData"] != null)
{
    ViewData = (ViewDataDictionary)TempData["ViewData"];
}

How do I then convert that ViewData, which includes the previous invalid ModelState, into a new model to send to the view so the user sees their invalid input with validation warnings? Oddly enough if I pass in a new instance of my ViewModel retrieved from the database (with the original valid data) to the View() this is ignored and the (invalid) data in the ViewData is displayed!

Thanks

回答1:

I had a similar problem and decided to use the following pattern:

public ActionResult PersonalRecord(Guid id)
{
    if (TempData["Model"] == null)
    {
        var personalRecord = _context.PersonalRecords.Single(p => p.UserId == id);
        var model = personalRecord.ToPersonalRecordModel();
        return View(model);
    }
    else
    {
        ViewData = (ViewDataDictionary) TempData["ViewData"];
        return View(TempData["Model"]);
    }
}

[HttpPost]
public ActionResult PersonalRecord(PersonalRecordModel model)
{
    try
    {
        if (ModelState.IsValid)
        {
            var personalRecord = _context.PersonalRecords.Single(u => u.UserId == model.UserId);
            personalRecord.Email = model.Email;
            personalRecord.DOB = model.DOB;
            personalRecord.PrimaryPhone = model.PrimaryPhone;
            _context.Update(personalRecord);
            _context.SaveChanges();
            return RedirectToAction("PersonalRecord");
        }
    }
    catch (DbEntityValidationException ex)
    {
        var errors = ex.EntityValidationErrors.First();
        foreach (var propertyError in errors.ValidationErrors)
        {
            ModelState.AddModelError(propertyError.PropertyName, propertyError.ErrorMessage);
        }
    }

    TempData["Model"] = model;
    TempData["ViewData"] = ViewData;

    return RedirectToAction("PersonalRecord", new { id = model.UserId });
}

Hope this helps.



回答2:

I noticed that the Model is included in ViewData so you don't need to pass it in addition to the ViewData, what I don't understand is how you get at it to then return it to the view.

    public ViewResult Edit(int id)
    {
        // Check if we have ViewData in the session from a previous attempt which failed validation
        if (TempData["ViewData"] != null)
        {
            ViewData = (ViewDataDictionary)TempData["ViewData"];
        }

        VehicleVariantEditViewModel viewModel = new VehicleVariantControllerViewModelBuilder()
            .BuildForEdit(id);

        return View(viewModel);      
    }

The above works but obviously it's making an unnecessary call to the database to build a new Model (which gets automagically overwritten with the invalid values from the Model in the passed ViewData)

Confusing.