Стратегии валидации - PullRequest
9 голосов
/ 06 июля 2011

У меня есть бизнес-модель со многими классами, некоторые логические объекты в этой модели состоят из множества разных классов (Parent-child-внучка). В этих различных классах я определяю инвариантные ограничения, например, что кореньсоставной должен иметь значение для кода.

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

public interface IValidatable
{
    IEnumerable<ValidationError> GetErrors(string path);
}

Родитель добавляет ошибку проверки, если код не установлен, и затем выполняет GetErrors для каждого дочернего элемента, чтов свою очередь вызовет GetErrors для каждого внука.

Теперь мне нужно проверить различные ограничения для разных операций, например

  1. Некоторые ограничения всегда следует проверять, поскольку они инвариантны
  2. Некоторые ограничения должны быть проверены, когда я хочу выполнить операцию X с корнем.
  3. Некоторые дополнительные ограничения могут быть проверены при выполнении операции Y.

Я рассмотрел возможность добавленияПараметр «Reason» для метода GetErrors, но по какой-то причине я не могу понять, как это выглядит, неправильно.Я также рассмотрел создание посетителя и наличие конкретной реализации для проверки для OperationX и другой для OperationY, но мне это не нравится, потому что некоторые проверки ограничений потребуются для нескольких операций, но не для всех (например, для OperationX + OperationY требуется дата).но не OperationZ), и мне не хотелось бы дублировать код, который проверяет.

Буду признателен за любые предложения.

Ответы [ 8 ]

4 голосов
/ 06 июля 2011

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

Один из возможных вариантов - создать параллельный набор классов, подобный этому:

public interface IValidate<T>
{
    IEnumerable<ValidationError> GetErrors(T instance, string path);
}

public sealed class InvariantEntityValidator : IValidate<Entity>
{
    public IEnumerable<ValidationError> GetErrors(Entity entity, string path)
    {
        //Do simple (invariant) validation...
    }
}

public sealed class ComplexEntityValidator : IValidate<Entity>
{
    public IEnumerable<ValidationError> GetErrors(Entity entity, string path)
    {
        var validator = new InvariantEntityValidator();
        foreach (var error in validator.GetErrors(entity, path))
            yield return error;

        //Do additional validation for this complex case
    }
}

Вам все еще нужно решить, как вы хотите связать классы валидации с различными валидируемыми классами. Похоже, это должно происходить как-то на уровне операций, так как именно здесь вы знаете, какой тип проверки должен произойти. Трудно сказать без лучшего понимания вашей архитектуры.

3 голосов
/ 06 июля 2011

Я бы сделал проверку на основе атрибутов:

public class Entity
{
    [Required, MaxStringLength(50)]
    public string Property1 { get; set; }  

    [Between(5, 20)]
    public int Property2 { get; set; }

    [ValidateAlways, Between(0, 5)]
    public int SomeOtherProperty { get; set; }      

    [Requires("Property1, Property2")]
    public void OperationX()
    {
    }
}

Каждое свойство, которое передается атрибуту Requires, должно быть допустимым для выполнения операции.

Свойства, имеющие атрибут ValidateAlways, должны быть действительными всегда - независимо от того, какая операция.

В моем псевдокоде Property1, Property2 и SomeOtherProperty должны быть действительными для выполнения OperationX.

Конечно, вы должны добавитьОпция атрибута Require для проверки атрибутов проверки также для дочернего элемента.Но я не могу предложить, как это сделать, не увидев пример кода.

Может быть что-то вроде этого:

[Requires("Property1, Property2, Child2: Property3")]

При необходимости вы можете также получить строго типизированные указатели свойств с лямбда-выражениями вместо строк ( Пример ).

2 голосов
/ 06 июля 2011

Я бы предложил использовать библиотеку Fluent Validation For .Net .Эта библиотека позволяет довольно легко и гибко настраивать валидаторы, и если вам нужны разные валидации для разных операций, вы можете очень легко использовать ту, которая применяется для этой конкретной операции (и изменить их).

1 голос
/ 06 июля 2011

Если вы используете .Net 4.0, вы можете использовать Кодовые контракты для управления некоторыми из них.

1 голос
/ 06 июля 2011

Я использовал механизм проверки Sptring.NET по той же причине - он позволяет вам использовать Условные валидаторы . Вы просто определяете правила - какую проверку применять и при каких условиях и Spring делает все остальное. Хорошо, что ваша бизнес-логика больше не загрязнена интерфейсами для проверки

Более подробную информацию вы можете найти в документации на springframework.net. Я просто скопирую образец их документа, чтобы показать, как он выглядит:

<v:condition test="StartingFrom.Date >= DateTime.Today" when="StartingFrom.Date != DateTime.MinValue">

<v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary"/>

</v:condition>

В этом примере сравнивается свойство StartingFrom объекта Trip, чтобы увидеть, не позже ли оно текущей даты, т.е. DateTime, но только когда дата была установлена ​​(начальное значение StartingFrom.Date было установлено в DateTime.MinValue ).

Валидатор условий можно считать «матерью всех валидаторов». Вы можете использовать его для достижения почти всего, что может быть достигнуто с помощью других типов валидаторов, но в некоторых случаях тестовое выражение может быть очень сложным, поэтому вам следует использовать более конкретный тип валидатора, если это возможно. Однако средство проверки условий по-прежнему является лучшим выбором, если вам необходимо проверить, относится ли конкретное значение к определенному диапазону или выполнить аналогичный тест, поскольку эти условия довольно легко написать.

0 голосов
/ 11 сентября 2014

Я полагаюсь на проверенную стратегию проверки, которая реализована в .net framework в ' System.ComponentModel.DataAnnotations Namespace ' и, например, используется в ASP.NET MVC.

Этот предоставляет ненавязчивый способ применения правил проверки (с использованием атрибутов или реализации IValidatableObject) и средств для проверки правил.

Скотт Аллен описал этот способ в большой статье ' Проверка вручную с аннотациями данных '.

  • если вы хотите применить атрибуты проверки к интерфейсу, посмотрите на MetadataTypeAttribute
  • чтобы лучше понять, как это работает, посмотрите MS source code
0 голосов
/ 06 июля 2011

Я бы отделил логику проверки варианта, возможно, с посетителем, как вы упомянули.Отделяя валидацию от классов, вы будете отделять свои операции от ваших данных, и это может реально помочь сохранить вещи в чистоте.

Подумайте об этом тоже так - если вы совмещаете проверку и операции с вашими классами данных, подумайте о том, как будут выглядеть события через год, когда вы делаете усовершенствование и вынужно добавить новую операцию.Если правила проверки каждой операции и логика операции являются отдельными, это в основном просто «добавление» - вы создаете новую операцию и нового посетителя проверки, чтобы пойти с ней.С другой стороны, если вам нужно вернуться назад и коснуться множества логики «если операция == x» в каждом из ваших классов данных, то у вас есть некоторый дополнительный риск ошибок регрессии / и т.д.

0 голосов
/ 06 июля 2011

Попробуйте прочитать эту статью сверху вниз, я получил от этого немало идей.

http://codebetter.com/jeremymiller/2007/06/13/build-your-own-cab-part-9-domain-centric-validation-with-the-notification-pattern/

Это валидация домена на основе атрибутов с уведомлением, которое переноситсяэти проверки до более высоких уровней.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...