views:

41

answers:

3

I am about to implement a class to represent a validation error. The class would definitely contain a string value called Message, which is a default message to display to a user. I also need a way to represent what the validation error is to the programmer. The idea is that there should be an easy way to determine if a particular validation error occurred.

It would be simple to implement a string member called Type, but to determine if a ValidationError is of that type, I would need to remember the string that describes that type.

if (validationError.Type == "PersonWithoutSurname") DoSomething();

Clearly, I need something more strongly typed. An enumeration would be good:

if (validationError.Type == ValidationErrorType.PersonWithoutSurname) DoSomething();

But given the potentially hundreds of types of validation error, I could end up with an ugly enum with hundreds of values.

It also occurred to me to use subclassing:

if (validationError.GetType() == typeof(PersonWithoutSurnameValidationError)) DoSomething();

But then my class library is littered with hundreds of classes which will mostly be used once each.

What do you guys do? I can spend hours agonising over this sort of thing.

Answer go to whoever comes up with the suggestion I use. Enum suggestion is the one to beat.

+3  A: 

I use FluentValidation, where you can set up rules for each class, with default or customisable messages for each property.

Because it is a fluent framework, you can combine rules such as:

RuleFor(customer => customer.Address)
   .NotNull().Length(20, 250).Contains("Redmond")
   .WithMessage(@"Address is required, it must contain 
    the word Redmond and must be between 20 and 250 characters in length.");

Typical usage for a validator of the Customer class:

public class CustomerValidator: AbstractValidator<Customer> {
  public CustomerValidator() {
    RuleFor(customer => customer.Surname).NotEmpty();
    RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
    RuleFor(customer => customer.Company).NotNull();
    RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
    RuleFor(customer => customer.Address).Length(20, 250);
    RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
  }

  private bool BeAValidPostcode(string postcode) {
    // custom postcode validating logic goes here
  }
}

Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);

bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;  
//Bind these error messages to control to give validation feedback to user; 
Daniel Dyson
So what would the code be to check the validation result to see if the customer's surname is empty?
David
The default for a string, which would be "{propertyname} cannot be empty.", Which would result in "Surname cannot be empty."
Daniel Dyson
You only put WithMessage if you want to override the default message. The default messages are help in a resx file so you can change these if you like. There are resx files with default messages for many languages too.
Daniel Dyson
Say if I have a Customer object and I'm checking it's in a valid state. I have a branch of code that is executed if the customer's Surname is empty. What do I write in my if condition?
David
I like the idea of the validation logic being in a separate class I must say. Tidy.
David
if (!results.IsValid) { //don't process further but do report validation errors gridview.DataSource = results.Errors; //Arbitrary example}
Daniel Dyson
It means that you can validate at any time (not just on Page.IsValid). It also allows you to generate ClientSide validation such as JQuery validation, baseed on the same rules that run on the serverside. (Important for web apps)
Daniel Dyson
That last thing sounds amazing. I'm going to take a look at this framework of yours.
David
I have done a rework of the framework to make it backwards compatible with .NET 2.0, but this is a little out of date with the current .NET 3.5 version. Let me know what version you are targetting and whether it is worth me updating the .NET 2 version
Daniel Dyson
.NET 3.5. Just one question - I assume I can implement logic across properties, e.g. one of two particular properties must be set, or the product of two float properties must not exceed a particular amount, etc etc?
David
Oh, and another, sorry! Sometimes the particulars of validation vary by context. You might want more lenient validation of an object in one place compared to another. I assume I would just create two subclasses of AbstractValidator<Customer> and use the appropriate one?
David
Tell me to butt out if you want, but in terms of API design, isn't it a bit confusing to call a collection of ValidationFailures 'Errors'? Why not call it 'ValidationFailures'? (Apart from that, it looks fantastic!)
David
Hi David, that would be a question for the guy that wrote the orignal version, Jeremy Skinner. But as you say, overall he has done a great job.
Daniel Dyson
Cool, well thanks for your advice Daniel, and thanks for the heads-up on what looks like a great framework.
David
A: 

If the question is storing the types (especially so you can add new ones) how about a config file in XML, or something database driven?

With an app.config you could have:

Which would get called in code:

//Generate the error somehow:
Validation.ErrorType = 
    ConfigurationManager.AppSettings["PersonWithoutSurnameValidationError"].Value;

//Handle the error
[Your string solution here]

This way, you get your error types documented somewhere outside your code so they're easier to remember. If, on the other hand, your main question is the storage so you can get the correct type to handle, stick with the enum.

AllenG
With an app config file you lose the business layer portability than I'm on about, although of course I could just use an XML file embedded in the business layer project. I don't really need something I can edit on the fly, just something which is easy to use in code.
David
A: 

I seriously don't get why you getting into so much trouble....

If its validating fields that you are doing, then I usually add a regex validator & and a required field validator. For some fields I do add custom validator for my own set of rules. But that's it. For the client side as well as server side. All I do then is a page.validate command which if ever throws an error means the client script has been modified & I usually reload the page as response.

Also if I want to handle a check to single value I use

 System.Text.RegularExpressions.Regex.IsMatch(...

So Is there more to this?? If there is please point out.

loxxy
He's trying to perform some action depending on specific types of validation. It's error handling, not the validation. The OP seems to have the validation rules down, but he is concerned he'll have enough possible validation errors that he wants to present those error type values in a consistant format.
AllenG
It's more about adding validation rules to the underlying domain entity than to the controls of the page.That way you can port your business layer to another front-end (e.g. from web to Windows) and the validity of your domain entities can still be easily checked.
David
Ohh thanks for letting me know that. Sry OP I guess I'm not helpful here.
loxxy
Every response is useful. Your way of doing validation is just different to mine. Thanks!
David