質問

In our system we have an entity Product, which might have various custom properties. Set of properties might differ from product to product, and is stored in a field of type List<Property>. Properties are of different types (strings, ints, double) and might have some special characteristics, e.g. they might be multivalued, might be a value of s specific range, a value from the given list etc. Value of the property is always contained in the string field Value.

Now my current problem is to implement validation of these properties and I am kind of stuck on what approach to take in part of erpresentation of the validation results. Requirements that I have to satisfy: - Results of the validation will be used by other application layers, so the resulting API should be clear and easy to use - Each error should have a distinct representation - Users of the API should have enough information to understand error specifics (for example for range errors they should be provided with minimal possible value, maximal possible value and actual value)

Here are the approaches I have considered so far:

  1. Classes hierarchy. There is one base abstract class ValidationError, and each specific error will be reflected in a inherited class. If error has any specifics, corresponding class will have necessary fields to store all the information. Example:

    public abstract class ValidationError
    {
        // common fields and methods, if any
    }
    
    public class IncorrectFormatValidationError : ValidationError
    {
        // just empty class, nothing to add here
    }
    
    public class RangeValidationError : ValidationError
    {
        public object MinValue { get; set; }
    
        public object MaxValue { get; set; }
    }
    

    This approach seems to be rather redundant to me because of variety of actually empty classes. Furthermore usage of such a API does not seem to be right (if (typeof(error) == typeof(RangeValidationError)) - nonsense!). However this is the first thing that came to my mind.

  2. Errors enumeration and, where necessary, classes hierarchy. All errors are represented with enumeration. There is one class ValidationError that is used in most cases, and when additional info is necessary for specific error, the inheritor is created, basically the same way as in the first approach. Example:

    public enum ValidationErrors
    {
        IncorrectFormat,
        ValueNotWithinRange,
        ...
    }
    
    public class ValidationError
    {
        public ValidationErrors ErrorType { get; set; } 
    
        public ValidationError(ValidationErrors type)
        {
            this.ErrorType = type;
            ...
        }
    
        // common fields and methods, if any
    }
    
    public class RangeValidationError : ValidationError
    {
        public object MinValue { get; set; }
    
        public object MaxValue { get; set; }
    
        public RangeValidationError(object minValue, object maxValue) :
            base(ValidationErrors.ValueNotWithinRange)
        {
            ...
        }
    }
    

    This approach looks much better, however there are drawbacks as well. The biggest one is that we as a API users cannot be guaranteed that when we have the error of type say ValueNotWithinRange, we are dealing with class of type RangeValidationError, and if we are not - how do we handle it? Idially I would like to have some design-level feature that would prevent even existing of such situations, since I am not the onyl one who develops the API. Another issue with this approach is that if most errors will in the end require some additional info, we'll eventually end up with the same aprroach number 1.

Anyone has any thoughts to share on these two approaches or suggest a better one? I will highly appreciate any response. Thanks in advance.

役に立ちましたか?

解決

It might be a good idea to take a step back and rethink the whole "multiple classes representing validation errors" approach, because it's almost guaranteed that you already have a hierarchy of validators.

Any validation error could be perfectly specified given:

  1. The validator that failed to accept the value of a property
  2. The name of the property
  3. The value that failed to validate

Any further information (e.g. limits of a range, a regular expression that did not match) is already present on the the validator instance and can be accessed through that.

So what about having just one validation error class that looks like this:

public sealed class ValidationError
{
    public Validator Validator { get; set; }
    public string PropertyName { get; set; }
    public object AttemptedValue { get; set; }
}

他のヒント

You might want to take a look at something like FluentValidation. It's lightweight, intuitive and covers a lot of validation scenarios.

If you're not satisfied with the validation results used by FluentValidation, you can always wrap them in your own custom validation error classes. At least you'll get a a lot of basic functionality for free.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top