Suppose I have this model:
public class ViewModel
{
[Required]
public string UserInput { get; set; }
[Required]
public Guid EntityId { get; set; }
}
Now, when UserInput is wrong, I want to re-display the same page but with validation errors (e.g. /Edit). However, when EntityId is wrong, I want to redirect to some other page (e.g. /Create).
I can do this manually inside each controller...
if (!ModelState.IsValidField("EntityId")) { redirect }
//or
if (string.IsNullOrEmpty(data.EntityId)) { redirect }
but it's kind of boring and violates DRY. Imaging several entities, nested view models with entities... too cumbersome. I'd better have something like ModelState.IsValidUserData and ModelState.IsValidCriticalData. But there's no such thing.
Now, EntityId is actually bound using my custom model binder, that knows that it is mission-critical. So there's this solution:
- Usual fields do populate ModelState with errors as usual.
- (a) Critical fields are bound using custom model binder that throws special "CriticalModelErrorException". Controller actions have [HandleCrirticalError("action", "controller')] attribute - which handles critical errors and redirects to the given action.
- (b) Critical fields are bound using custom model binder that sets BaseController.CriticalModelErrors property (obviously all controllers are derived from supercontroller base class). Each action is free to check both ModelState.IsValid and base.CriticalModelErrors and behave freely based on that.
- (c) Critical fields are bound using custom model binder that sets special-format model state errors, e.g. AddModelError(name, "!CRITICAL! text"; Then base controller have method that detects such strings.
2a example:
[HandleCriticalError("Create")] // uses the same controller
[HandleModelStateError("Edit")] // redisplays page with validation errors
public ActionResult Edit(ViewModel data)
{
// here we know both our data entities and user data are valid and safe
}
2b example
public ActionResult Edit(ViewModel data)
{
if (!ModelState.IsValid)
return View(data);
if (base.CriticalModelErrors.Count > 0)
return RedirectToAction("Create");
// here we know both our data entities and user data are valid and safe
}
2c example
protected bool HasCriticalErrors()
{
return ModelState.Any(x => x.Value.Errors.Any(x => x.ErrorMessage.StartsWith("!CRITICAL!")))
}
// then same as 2b
Now, the questions: how it's handled by other apps and developers (you and your apps)? Which one would you prefer? Are there any drawbacks or better solutions?