8

I want to display an error to the user in an ASP.MVC 3 input form using ModelState.AddModelError() so that it automatically highlights the right field and puts the error next to the particular field.

In most examples, I see ModelState.AddModelError() and if(ModelState.IsValid) placed right in the Controller. However, I would like to move/centralize that validation logic to the model class. Can I have the model class check for model errors and populate ModelState.AddModelError()?

Current Code:

// Controller
[HttpPost]
public ActionResult Foo(Bar bar)
{  
    // This model check is run here inside the controller.
    if (bar.isOutsideServiceArea())
        ModelState.AddModelError("Address", "Unfortunately, we cannot serve your address.");

    // This is another model check run here inside the controller.
    if (bar.isDuplicate())
        ModelState.AddModelError("OrderNumber", "This appears to be a duplicate order");

    if (ModelState.IsValid)
    {
        bar.Save();
        return RedirectToAction("Index");
    }
    else
        return View(bar)
}

Desired Code:

// Controller
[HttpPost]
public ActionResult Foo(Bar bar)
{  
    // something here to invoke all tests on bar within the model class

    if (ModelState.IsValid)
    {
        bar.Save();
        return RedirectToAction("Index");
    }
    else
        return View(bar)
}


...
// Inside the relevant Model class
if (bar.isOutsideServiceArea())
    ModelState.AddModelError("Address", "Unfortunately, we cannot serve your address.");

if (bar.isDuplicate())
    ModelState.AddModelError("OrderNumber", "This appears to be a duplicate order");
Dan Sorensen
  • 11,403
  • 19
  • 67
  • 100
  • You want to do more than just the generic DataAnnotations like RequiredAttribute correct? – John Kalberer Jun 21 '11 at 22:23
  • I am actually using DataAnnotations quite a bit, but some errors must be calculated at runtime. Our generic model has some variations that are configured and stored in the database and applied at runtime. – Dan Sorensen Jun 21 '11 at 22:50
  • So far, I've only been able to get it to work calling that method from the controller or by using DataAnnotations in the model. Haven't figured out how to run some logic in the model class and to call AddModelError() – Dan Sorensen Jun 21 '11 at 22:54

5 Answers5

5

If you are using MVC 3, you should checkout IValidatableObject, it's what you're after.

Scott Gu mentions it in his MVC3 Intro blog posting.

Charlino
  • 15,802
  • 3
  • 58
  • 74
1

You can do something like this using custom data annotations or using RuleViolations like what they did in the NerdDinner example.

John Kalberer
  • 5,690
  • 1
  • 23
  • 27
  • Wow, I think this is the way to do it so far, but that's a whole lot more code... not sure it's worth the added complexity. There must be an easier way to just add an error to the modelstate – Dan Sorensen Jun 21 '11 at 22:59
  • Like Charlino said you can use the IValidatableObject if you are using MVC 3 (close to what NerdDinner does but you do not have to manually add the errors to the model state in your controller) but other than that, this is all I have seen. I personally prefer to use the custom data annotations so they can be reused as well as the fact that you simply decorate the model properties with attributes. – John Kalberer Jun 22 '11 at 14:52
  • I am using the DataAnnotations everywhere that I can at design time. However, there are some checks such as isUnique (against other records) that must be done at runtime. So I'll have a combo. Thanks. – Dan Sorensen Jun 22 '11 at 15:38
0

You would use custom validation via the IValidatableObject interface on your model.

Custom validation example

0

If you're after least amount of code, a hacky way of doing it is to stuff that message into the Session from your View Model, then add that message from your Controller, for example:

// In Model (or elsewhere deep inside your app):
string propertyName = "Email";
string errorMessage = "This is the error message, you should fix it";

Session["DeepModelError"] = new KeyValuePair<string, string>(propertyName, errorMessage);

// In Controller
var model = new LoginModel();
if (Session["DeepModelError"] != null)
{
    var deepError = (KeyValuePair<string, string>)Session["DeepModelError"];
    if (deepError.Key == "Email" || deepError.Key == "Password")
    {
        ModelState.AddModelError(deepError.Key, deepError.Value);

        Session["DeepModelError"] = null;
    }
}

Let me reiterate that I realize that this is kind of hacky, but it's simple and it gets the job done...

Serj Sagan
  • 28,927
  • 17
  • 154
  • 183
0

Perhaps you could create an error interface like IErrorHandler and pass that into a public method called Validate on you model class assuming its a partial class and you can seperate your data model from your rules.

With the interface you could create a class in your contoller that wraps the ModelState error handler. So the inteface might have AddError and in that metghod tou just delegate to your local modelstate.

So your method might be something like:

IErrorHandler errorHandler = CreateErrorHandler();
model.Validate(errorHandler);

if(errorHandler.IsValid())
... do something
dreza
  • 3,605
  • 7
  • 44
  • 55