Валидация свойств с помощью атрибутов в WPF

Сложность 200-300

Уже очень давно меня не оставляет мысль о глобальной несправедливости для WPF в плане валидации свойств по сравнению с ASP.NET. Какое-то время хотелось даже самому взяться за это дело, но внутренне я понимал, что это не у меня одного такое ощущение и должны быть где-то уже реализации данного функционала в удобной и доступной форме, когда не нужно писать самому кучу кода, дополнительных свойств приаттаченых, или еще чего-нибудь в таком духе. Большинство статей на CodeProject по теме валидации свойств в WPF очень стары и предлагают написать очень много кода самому, но хочется ведь чтобы почти все стандартные простые проверки ввода уже работали из коробки.

Так как в последнее время опять я взялся крепко за PostSharp, то не мог не просмотреть страничку с упоминанием проектов основанных на PostSharp и среди них оказался интересный проект Wakizashi. Суть данного проекта сводится к тому, чтобы реализовать проверку свойств с помощью атрибутов.

Традиционный способ валидации

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

Для того, чтобы это можно было бы красиво отобразить на интерфейсе с помощью интерфейса IDataErrorInfo потребуется почти все то же самое, только перенести в аксессор класса.

Альтернативой является применение XAML тегов, что не добавляло прозрачности коду и создавало «утечки» логики в слой представления, что приводит к безблагодатности.

В целом примеров много, наиболее полные и удачные на мой взгляд примеры раз, два, три (тут особенно интересно показано как сделать мигающий идентификатор ошибки, с текстом, почти как профессиональный компоненты от DevExpress). Некоторые статьи старые, но от этого не теряют своей значимости и сейчас.

Настройка валидации с помощью атрибутов

Проект Wakidzashi позволяет использовать вместо громоздкого кода компактные атрибуты для определения правил на пользовательский ввод. Так же правила можно описывать императивно используя fluent API, и менять и применять их динамически, что невозможно при использовании атрибутов или большинства традиционных техник. Помимо всего прочего можно достаточно легко создавать свои правила. Но обо всем по порядку.

Для экспериментов буду использовать небольшое приложение, где есть два поля для ввода и кнопка для какого-либо действия. Внешний вид приложения очень прост:

XAML код так же не отличается сложностью, начнем с самого начала и с самого простого.

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

В качестве модели используется следующий код:

В качестве примера буду использовать правило на минимальное количество символов в строке. Думаю, что применение нескольких правил не должно кардинально отличаться от примера. Модель тогда приобретает такой вид:

Модифицируем XAML следующим образом:

И при запуске приложения не получаем ничего, так как само по себе правило не применится и не отобразится никоим образом.

Проверка условий указанных с помощью атрибутов происходит с помощью специального класса Validator. Общий сценарий использования выглядит так:

После чего анализируется результат метода Validate(). В моем случае я создал базовый класс в который перенес анализ и реализацию интерфейса IDataErrorInfo.


При запуске получаем следующую картину на старте:

Красные прямоугольники символизируют об ошибке ввода. При заполнении полей динамически меняется состояние полей.

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

Отложенная валидация

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

В базовом классе создаем метод ForceValidate() и переменную useImmidiateValidation. Все имена говорящие, так что лучше описать то, как это все будет работать.

В классах наследниках используем следующие методы:

Метод Calc() вызывается по нажатию на кнопку.

Итак, что же происходит теперь: по умолчанию пропускается валидация методов, так как в аксессоре базового метода идет проверка на условие мгновенной проверки. Когда требуется ее включить меняем значение переменной и принудительно обновляем значения полей с помощью PostSharp Domain Toolkit. Это делается с помощью метода ForceValidate() в котором получаем все публичные свойства экземпляра класса и принудительно их обновляем.

Теперь при запуске приложения не будет показываться красная рамка сигнализирующая о неправильном вводе данных. Но одновременно с этим, без форсирования валидации не будет производится мгновенная валидация данных. Советую поэкспериментировать.

DevExpress

При использовании компонентов от DevExpress, которые настроены на использование IDataErrorInfo получается вообще красота неописуемая. Без дополнительных действий в коде получаем следующий формат отображения:

Я думаю, что для стандартных элементов интерфейса можно использовать материал статьи.

Теория использования

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

Итак, полный список правил для проверки выглядит следующим образом:

DateTime

  • DateTimeConversionRule
  • DateTimeRangeRule

Enum

  • EnumConversionRule

 

Enumerable

  • EnumerableDuplicateRule
  • EnumerableNotEmptyRule

Int64

  • Int64RangeRule

Integer

  • IntegerRangeRule
  • IntegerConversionRule

String

  • MustIncludeTextRule
  • CannotIncludeTextRule
  • RegexMatchRule
  • StringLengthRule
  • StringRequiredRule
  • DirectoryPathExistsRule

Теория использования правил

Динамически менять и составлять правила можно с помощью fluent API следующего вида:

Пример не из приложения, но раскрывает основную суть. Естественно сами по себе они работать не будут, так как надо использовать, как уже было показано выше класс Validator.

Да, в некотором фантастическом будущем у разработчика есть желание автоматически реализовать интерфейс IDataErrorInfo, но к сожалению проект на CodePlex был последний раз обновлен в августе 2010 года, и где сейчас актуальная версия неизвестно, так как NuGet показывает совсем другую версию библиотеки и дату сентябрь 2011.

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

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

Итого

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

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

 

Hard’n’Heavy!

 

 

2 комментарий на “Валидация свойств с помощью атрибутов в WPF

    • Я так сразу не вспомню, но судя по этому код:

      public Model() {
      useImmidiateValidation = false;
      }

      public void Calc() {
      UseImmidiateValidation();
      }

      protected void UseImmidiateValidation() {
      ForceValidate(this);
      }

      public void Init() {
      useImmidiateValidation = true;
      }

      Переменная является полем модели. Т.е. вы сами ее создаете или не создаете и называете как хотите.

      хм, что-то разметка убежала, надо бы поправить код

Оставить комментарий