2

Martin Fowler recommends replacing exceptions with notifications when dealing with validations. Essentially, instead of throwing an exception, you append error messages to a Notification object that gets returned. This allows you to keep exceptions to "exceptional" circumstances, and return all the validation information, not just the first one checked.

The idea makes all sense. However, how do you apply this idea in cases where a method does some user input validation but also returns an object?

3
  • What's the problem with returning both things?
    – Doval
    Dec 12, 2014 at 18:50
  • 1
    @Doval: Many languages only allow one thing to be returned from the method, so it would have to be a composite object. See my answer below. Dec 12, 2014 at 18:52
  • 1
    @RobertHarvey Well, yes. I guess it could've been clearer, but I was implicitly asking what's the problem with creating a composite.
    – Doval
    Dec 12, 2014 at 18:57

1 Answer 1

4

By adding some metadata to your object, either by inheriting from an abstract class that contains that data, writing a composite object, or something similar.

In ASP.NET MVC 1, this would have been done by attaching a partial class to your entity (In the C# language, classes having the same name with the modifier partial in front of them are merely treated like one big single class).

So, for example:

public partial class Dinner {

    public bool IsValid {
        get { return (GetRuleViolations().Count() == 0); }
    }

    // Return a list of errors that we can iterate over with `foreach`
    public IEnumerable<RuleViolation> GetRuleViolations() {

        // Validation rules go here; see below for an example.

        yield break;
    }

    partial void OnValidate(ChangeAction action) {
        if (!IsValid)
            throw new ApplicationException("Rule violations prevent saving");
    }
}

A Rule Violation is just an immutable object containing an Error Message and a Property Name:

public class RuleViolation {

    public string ErrorMessage { get; private set; }
    public string PropertyName { get; private set; }

    public RuleViolation(string errorMessage, string propertyName) {
        ErrorMessage = errorMessage;
        PropertyName = propertyName;
    }
}

You would then check for errors like this:

public IEnumerable<RuleViolation> GetRuleViolations() {

    if (String.IsNullOrEmpty(Title))
        yield return new RuleViolation("Title required","Title");

    if (String.IsNullOrEmpty(Description))
        yield return new RuleViolation("Description required","Description");

    if (String.IsNullOrEmpty(HostedBy))
        yield return new RuleViolation("HostedBy required", "HostedBy");

    if (String.IsNullOrEmpty(Address))
        yield return new RuleViolation("Address required", "Address");

    if (String.IsNullOrEmpty(Country))
        yield return new RuleViolation("Country required", "Country");

    if (String.IsNullOrEmpty(ContactPhone))
        yield return new RuleViolation("Phone# required", "ContactPhone");

    if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
        yield return new RuleViolation("Phone# does not match country", "ContactPhone");

    yield break;
}

This arrangement allows you to pass the errors back to a view so that they can be displayed to the user, like so (This is a controller method; it accepts a form POST from a user web form):

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    try {

        // Map form fields to dinner object
        UpdateModel(dinner);

        // Validation occurs here.  Throws exception if validation fails
        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {

        // Add each rule violation to the View, so that errors can be displayed.
        foreach (var issue in dinner.GetRuleViolations()) {
            ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }

        // Display form with validation errors.
        return View(dinner);
    }
}

Note that the exception is there only to trigger the validation handling process; there isn't an exception for every possible validation error. Instead, the validation rules are encoded in the GetRuleViolations() method, and they are all handled at once.

Of course, nowadays, the process has become a bit more refined. In the newer versions of ASP.NET MVC, attributes are used to encode the validation rules. But the principle is still the same: you don't use exceptions to encode individual validation rules. Notice the validation metadata that is encoded in the attributes of the code below (the attributes are those lines of code that are surrounded by brackets):

public class Movie
{
    public int ID { get; set; }

    [StringLength(60, MinimumLength = 3)]
    public string Title { get; set; }

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    public DateTime ReleaseDate { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
    [Required]
    [StringLength(30)]
    public string Genre { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    public decimal Price { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
    [StringLength(5)]
    public string Rating { get; set; }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.