Skip to content

AVPolyakov/ObjectValidator

Repository files navigation

ObjectValidator

Простое API для валидации .NET объектов. Привязка ошибок к имени свойства объекта. Для ссылки на свойство используется быстрый тип Func<> вместо медленных деревьев выражений Expression<Func<>>. Реализован быстрый алгоритм получения имени свойства. Алгоритм основан на разборе тела метода Parsing the IL of a Method Body.

Почему не FluentValidation

Typically, FluentValidation is used against a viewmodel/inputmodel not a business entity [1], [2].

Для получения имени свойства в FluentValidation используется дерево выражений (expression tree). К сожалению, деревья выражений в .Net работают медленно. Деревья выражений замедляют работу со свойствами объектов в 60 раз [3]. Из-за этого рекомендуется кешировать валидаторы в статическом контексте [4], [10]. Это усложняет передачу внешних параметров в валидатор [5], [6], [1], [7], [8], [9]. Автор FluentValidation предлагает использовать обходной путь [1].

Do not forget that your goal is to implement validation, not to use FluentValidation everywhere. Sometimes we implement validation logic as a separate method, that works with ViewModel and fill ModelState in ASP.NET MVC. If you can't find solution, that match your requirements, then manual implementation would be better than crutchful implementation with library [6].

ObjectValidator использует быстрый тип Func<>, поэтому кэширование не требуется. Отсутствие кэширования существенно упрощает API для валидации.

Принцип работы ObjectValidator

Для задания правил валидации в ValidationCommand необходимо добавить функции (лямбды), которые выполняют валидацию.

var message = new Message(); //объект, который проверяем
var command = new ValidationCommand();
command.Add(
   nameof(Message.Subject),
   () => string.IsNullOrWhiteSpace(message.Subject)
       ? new ErrorInfo {
           PropertyName = nameof(Message.Subject),
           Message = $"'{nameof(Message.Subject)}' should not be empty."
       }
       : null
);

В этом коде есть ссылка на имя свойства nameof(Message.Subject) и получение значения свойства message.Subject. С помощью IPropertyValidator для обеих ссылок можно завести одну переменную.

var subject = message.Validator().For(_ => _.Subject);
subject.Command.Add(
   subject.PropertyName,
   () => string.IsNullOrWhiteSpace(subject.Value)
       ? new ErrorInfo {
           PropertyName = subject.PropertyName,
           Message = $"'{subject.PropertyName}' should not be empty."
       }
       : null
);

Теперь можно написать метод расширения NotEmpty:

message.Validator().For(_ => _.Subject)
    .NotEmpty();

Метод Validate возвращает список объектов ErrorInfo с информацией об ошибках.

var errorInfos = await command.Validate();
Assert.Equal("Subject", errorInfos.Single().PropertyName);
Assert.Equal("'Subject' should not be empty.", errorInfos.Single().Message);

В процессе валидации если для свойства уже есть ошибка, то остальные связанные с этим свойством функции не вызываются.

Вложенные объекты

Валидация свойств вложенного объекта:

var message = new Message {
    Person = new Person()
};
var validator = message.Validator();
validator.For(_ => _.Person).Validator()
    .For(_ => _.FirstName)
    .NotEmpty();
var errorInfos = await validator.Validate();
Assert.Equal("Person.FirstName", errorInfos.Single().PropertyName);
Assert.Equal("'FirstName' should not be empty.", errorInfos.Single().Message);

Вложенные коллекции

Валидация свойств элементов вложенной коллекции:

var message = new Message {
    Attachments = new List<Attachment> {
        new Attachment(),
        new Attachment()
    }
};
var validator = message.Validator();
foreach (var attachmentValidator in validator.For(_ => _.Attachments).Validators())
{
    attachmentValidator.For(_ => _.FileName).NotEmpty();
}
var errorInfos = await validator.Validate();
Assert.Equal(2, errorInfos.Count);
Assert.Equal("Attachments[0].FileName", errorInfos[0].PropertyName);
Assert.Equal("'FileName' should not be empty.", errorInfos[0].Message);
Assert.Equal("Attachments[1].FileName", errorInfos[1].PropertyName);
Assert.Equal("'FileName' should not be empty.", errorInfos[1].Message);

About

Валидация .Net объектов.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages