niedziela, 5 września 2010

Walidacja obiektów domeny i nie tylko...

Witam troszkę teraz nie regularnie pisałem z powodu braku czasu ale już rozpoczął się wrzesień wiec czas do roboty. Dziś chciałbym omówić problem walidacji obiektów domenowych ale i nie tylko. Interfejs ten będzie można wykorzystać przy dowolnym obiekcie. Cele które chciałbym osiągnąć są 2: Przede wszystkim walidacja musi być wielokrotnego użytku i łatwo dostępna. Drugi cel to możliwość walidacji odrazu całego obiektu i zwrócenie do kontrolera informacji o wszystkich błędnych polach.

Interfejs IValidatable
Zacznijmy od samego początku ponieważ walidacja musi być łatwo dostępna i wielokrotnego użytku każdy obiekt który będzie posiadał walidacje musi implementować powyższy interfejs. Nic specjalnego:
public interface IValidatable
{
    ValidationResult Validate();
}

ValidationResult
Klasa ta składa się ze słownika który przechowuje informacje o błędzie jak i pole którego dotyczy informacja. Natomiast z powodu ze dane pole może posiadać wiele błędów dlatego przechowuje listę stringów. Oprócz tego posiada 2 funkcje 1 do zliczania błędów a druga do dodawania błędów do słownika. Sama klasa wygląda tak:
public class ValidationResult
{
    private IDictionary<string, IList<string>> _validationErrors;

    public ValidationResult()
    {
        _validationErrors = new Dictionary<string, IList<string>>();
    }

    public IDictionary<string, IList<string>> Errors
    {
        get { return _validationErrors; }
        private set { _validationErrors = value; }
    }

    public int CountErrors()
    {
        int errorCount = 0;

        foreach (var errorMessages in _validationErrors.Values)
        {
            errorCount += errorMessages.Count;
        }

        return errorCount;
    }

    public void AddError(KeyValuePair<string, string> error)
    {
        if (error.Key != null)
        {
            if (_validationErrors.ContainsKey(error.Key))
            {
                _validationErrors[error.Key].Add(error.Value);
            }
            else
            {
                IList<string> newErrorList = new List<string>();
                newErrorList.Add(error.Value);

                _validationErrors.Add(new KeyValuePair<string, IList<string>>(error.Key, newErrorList));
            }
        }
    }
}

ValidationHelper
Żeby troszeczkę sobie ułatwić dodawanie błędów postanowiłem stworzyć klasę pomocnicza która będzie nieco automatyzować dodawanie błędów. Będzie to obiekt który posiada 2 metody które dodają błąd jeżeli warunek jest niespełniony. Różnią się tylko 1 parametrem związanym z kluczem błędu. Zwykle nazywa się tak samo jak dane pole którego dotyczy. I w tym celu służy wersja metody z wyrażeniem lambda. Natomiast druga przyjmuje parametr string. Czyli jest bardziej uogólniona. Klasa wygląda tak:
public class ValidationHelper<T>
{
    public ValidationHelper()
    {

    }

    public KeyValuePair<string, string> CreateErrorIf(bool condition, string errorKey, string errorMessageKey)
    {
        if (!condition)
            return new KeyValuePair<string, string>(errorKey, ResourceHelper.GetErrorMessage(errorMessageKey));
        else
            return new KeyValuePair<string, string>();
    }

    public KeyValuePair<string, string> CreateErrorIf<TResult>(bool condition, Expression<Func<T, TResult>> errorKey, string errorMessageKey)
    {
        var expressionMember = errorKey.Body as MemberExpression;

        return CreateErrorIf(condition, expressionMember.Member.Name, errorMessageKey);
    }
}
Metoda która używa wyrażenia lambda po prostu pobiera z niego nazwę danego pola. A teraz przykładowe wykorzystanie powyższych klas:
public class User : BaseEntity, IEntity, IValidatable
{        
    ...

    public virtual ValidationResult Validate()
    {
        ValidationResult validationResult = new ValidationResult();
        ValidationHelper<User> validationHelper = new ValidationHelper<User>();

        validationResult.AddError(validationHelper.CreateErrorIf(!String.IsNullOrEmpty(Username), f => f.Username, ResourceKey.Common.RequiredField));
        validationResult.AddError(validationHelper.CreateErrorIf(Username.Length >= 256, f => f.Username, ResourceKey.Common.FieldLength));

        ...

        return validationResult;
    }
}

Oprócz pisania tego mechanizmu powolutku uzupełniam brakujące testy w projekcie związane już z gotową funkcjonalnością. To tyle na dziś :)

Brak komentarzy:

Prześlij komentarz