Reputation: 2632
I have a question regarding the method i am using for doing Business Rule Validations in asp.net mvc.
Currently i have an exception class that looks something like this
public class ValidationException : Exception
{
private ModelStateDictionary State { get; set; }
public ValidationException(ModelStateDictionary state)
{
State = state;
}
public void MergeModelStates(ModelStateDictionary state)
{
state.Merge(this.State);
}
}
and a validator that looks like this
public void Validate(IEntity entity)
{
ModelStateDictionary state = new ModelStateDictionary();
if (entity.Contact != null && _service.GetBy(entity.Contact.Id) == null)
state.AddModelError("Contact", "Invalid Contact.");
if (entity.Title.Length > 8)
state.AddModelError("title", "Title is too long...");
... etc
if (!state.IsValid)
throw new ValidationException(state);
}
and a Controller That does something like this
public ActionResult Add()
{
var entity = new InputModel;
try
{
TryUpdateMode(inputModel);
..... Send input to a Repository (Repository calls Validate(entity);
}
catch (ValidationException validationException)
{
validationException.MergeModelStates(this.ModelState);
TryUpdateModel(inputModel);
return View("Add",inputModel);
}
return View("List");
}
Is it wrong to use an exception to do something like this? Are there examples of a better method? I don't really want to add the validation to the Model Entity itself. The only other way i've seen it done is to inject the Controllers ModelState to the Repository layer, but that seemed sloppy to me.
Thanks for any help
Upvotes: 7
Views: 5326
Reputation: 14851
Exceptions should generally be for exceptional cases, and not to handle something that could routinely happen during normal execution of your program. There's a lot of good reasons for this - here's some I've run into fairly regularly:
One approach I usually like to do is to provide a public Validate method that returns a list of errors (but never throws an exception itself), and then a Save method which calls Validate() and throws an exception if there are any errors. You switch the behavior from "throw if the model isn't valid" to "throw if the code tries to save while the model is in an invalid state".
To address the comment below regarding performance of throws in Validate vs. Save - throwing in Save() will have the exact same performance penalty as throwing in Validate(). The key difference however is that this should never happen - you're guarding against a developer using your classes improperly rather than using exceptions as a validation method. Properly written, code that calls the save method should look something like:
ValidationResult result = obj.Validate();
if (result.IsValid) {
obj.Save();
} else {
// display errors to the user
}
The exception would only be thrown if the developer forgot to check the validation status before saving. This has the benefit of both allowing validation to happen without using exceptions, as well as protecting your database by never allowing invalid entities to be saved. Ideally you wouldn't catch the exception in the controller at all and let general error handling routines deal with it, since the problem is no longer with user input but instead the fault of the developer.
Upvotes: 15
Reputation: 19190
Ideally you'd use the data annotation feature in ASP.NET MVC 2:-
http://weblogs.asp.net/scottgu/archive/2009/07/31/asp-net-mvc-v2-preview-1-released.aspx
For example:-
public class Person
{
[Required(ErrorMessage="Please enter a name.")]
public String Name { get; set; }
}
If you're not up for upgrading, there's still a solution:
If you pass a reference to the Controller's ModelState dictionary (wrap it up inside an interface if you're worried about separation of concerns) to your Validator, call AddModelError if you find errors, then in your Controller you can call if (ModelState.IsValid) and take appropriate action:-
var entity = new InputModel();
TryUpdateModel(entity);
MyValidator.Validate(entity, ModelState);
if(ModelState.IsValid) { ...
and your Validator looks like:-
public void Validate(IEntity entity, ModelStateDictionary state)
{
if (entity.Contact != null && _service.ValidId(entity.Contact.Id) == null)
state.AddModelError("Contact", "Invalid Contact.");
// etc
}
Upvotes: 2