Расширение Windows Forms библиотекой специальных компонентов

Источник: hardline
Майкл Вейнхардт

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

Введение

Считайте меня странным, но одним из моих самых любимых фильмов является Amazon Women on the Moon (Амазонка на Луне), пародия на научно-фантастические фильмы класса В 1950х годов, сдобренная несколькими короткими комедийными скетчами в стиле The Kentucky Fried Movie. Один из скетчей, "Two IDs", изображает Карен и Джерру (в исполнении Розанны Аркетт (Rosanna Arquette) и Стива Гуттенберга (Steve Guttenberg), соответственно) в вечер первого свидания. Рассказ начинается с приезда Джерри в квартиру Карен. После нескольких дружеских шуток Карен совершенно необычно просит у Джерри две формы идентификации - основную кредитную карту и действительные водительские права. Карен использует идентификацию, чтобы проверить возраст Джерри, и в этом и заключается захватывающий поворот событий, который образует комедийную суть. К несчастью для Джерри, проверка его подноготной не увенчалась успехом, и Карен отменяет свидание.

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

Основные моменты проверки достоверности Windows Forms

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

Эта форма должна проверить следующее:

  • Обязательны Name (Имя), Date of Birth (Дата рождения) и Phone Number (Телефон)
  • Поле Date of Birth (Дата рождения) должно содержать действительное значение даты/времени
  • Phone Number (Телефон) должен соответствовать австралийскому формату: (xx) xxxx xxxx
  • Новому служащему должно быть не менее 18 лет

Чтобы реализовать такую проверку, нужна соответствующая инфраструктура, и неудивительно, что Windows Forms предоставляют ее, встроенную непосредственно в каждый элемент управления. Намерение элемента управления поддерживать проверку задается путем присваивания его свойству CausesValidation значения True, стандартного значения для всех элементов управления. Когда свойству CausesValidation элемента управления присвоено значение True, его событие Validating вызывается, если фокус переходит на другой элемент управления, значение свойства CausesValidation которого тоже True. В результате, вы обрабатываете событие Validating, чтобы реализовать свою логику проверки, например, подтверждение Name:

void txtName_Validating(object sender, CancelEventArgs e) {
  // Name обязательно
  if(txtName.Text.Trim() == "" ) {
    e.Cancel = true;
    return;
  } 
}

Validating поставляет параметр CancelEventArgs, который позволяет вам сообщать о действительности или недействительности поля через определение его свойства Cancel. Если Cancel имеет значение True (недействительное поле), фокус остается на недействительном элементе управления. Если по умолчанию значение False (действительное поле), вызывается событие Validated, и фокус перемещается на новый элемент управления.

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

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

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

Активизация этого компонента проста и состоит в перетягивании компонента ErrorProvider на форму и конфигурации его значка, частоты мерцания и стиля мерцания, после чего ErrorProvider может быть включен в код проверки достоверности:

void txtName_Validating(object sender, CancelEventArgs e) {
  // Name обязательно
  if(txtName.Text.Trim() == "" ) {
    errorProvider.SetError(txtName, "Name is required");
    e.Cancel = true;
    return;
  }    
  // Name действительно
  errorProvider.SetError(txtName, "");
}

CausesValidation, Validating и ErrorProvider предоставляют базовую инфраструктуру для реализации поэлементной проверки достоверности по шаблону, который мы можем повторно использовать для других элементов управления, таких как txtDateOfBirth и txtPhoneNumber:

void txtDateOfBirth_Validating(object sender, CancelEventArgs e) {    
  DateTime dob;
  // DOB обязателен и должно быть действительной датой
  if( !DateTime.TryParse(txtDateOfBirth.Text, out dob) ) {
    errorProvider.SetError(txtDateOfBirth, "Must be a date/time value");
    e.Cancel = true;
    return;
  }
  // Должен быть 18-ти летним или старше
  if( dob > DateTime.Now.AddYears(-18) ) {
    errorProvider.SetError(txtDateOfBirth, "Must be 18 or older");
    e.Cancel = true;
    return;
  }
  // DOB действителен
  errorProvider.SetError(txtDateOfBirth, "");
}  
void txtPhoneNumber_Validating(object sender, CancelEventArgs e) {      
  // Phone number обязателен и должен быть в австралийском формате
  Regex re = new Regex(@"^\(\d{2}\) \d{4} \d{4}$");
  if( !re.IsMatch(txtPhoneNumber.Text) ) {
    errorProvider.SetError(txtPhoneNumber, "Must be (xx) xxxx xxxx");
    e.Cancel = true;
    return;
  }
  // Phone Number действителен
  errorProvider.SetError(txtPhoneNumber, ""); 
}
 
Проверка всей формы

Сочетание события Validating и компонента ErrorProvider обеспечивает замечательное решение, которое проводит динамическую проверку достоверности каждого элемента управления в точке воздействия, т.е. когда пользователи вводят данные. К сожалению, использование событий Validating не позволяет этому решению автоматически масштабироваться на поддержание проверки всей формы, которая нужна, когда пользователь щелкает кнопку OK, завершая введение данных. Существует вероятность того, что фокус не располагался на некоторых элементах управления перед тем, как была нажата кнопка OK, и, следовательно, эти элементы управления не инициировали свои события Validating. Проверка всей формы реализуется путем вызова вручную логики проверки достоверности каждого события Validating, что достигается через перечисление всех элементов управления на форме, установление фокуса на каждом из них и вызов метода Validate формы, вот так:

void btnOK_Click(object sender, System.EventArgs e) {
  foreach( Control control in Controls ) {
    // Установить фокус на элементе управления
    control.Focus();
    // Validate вызывает событие Validating элемента управления,
    // если CausesValidation имеет значение True
    if( !Validate() ) {
      DialogResult = DialogResult.None;
      return;
    }
  }
}

Однако кнопка Cancel не нуждается в применении к ней проверки всей формы, потому что ее работа заключается в простом закрытии формы. Увы, пользователи, возможно, не смогут щелкнуть Cancel, если элемент управления, на котором они находятся в данный момент, недействителен, потому что фокус удерживается, т.к. свойству CausesValidation кнопки Cancel также присвоено значение True по умолчанию. Этой ситуации легко избежать, задавая свойству CausesValidation кнопки Cancel значение False, таким образом, инициация события Validating происходит не в любом элементе управления, который теряет фокус/

С помощью примерно 60 строк кода наша форма Add New Employee поддерживает базовую проверку достоверности.

Сравнение программной и декларативной проверки достоверности

С точки зрения производительности, в этом решении есть значительная проблема, заключающаяся в том, что нетривиальные приложения обычно имеют больше одной формы зачастую с большим количеством элементов управления на каждой форме, чем в нашем примере, и, следовательно, требующие большего количества кода проверки. Написание все большего и большего количества кода при повышении сложности UI не является маштабируемым методом и понятно, что этого лучше избегать. Одним из решений могло бы быть идентифицировать и упаковывать общую логику проверки достоверности в многократно используемые типы. Это могло бы помочь сократить необходимость создания и конфигурации экземпляров объектов проверки в клиентском коде. Однако, хотя это шаг в правильном направлении, это решение все еще требует программного воздействия. Но это решение предлагает обычный для UI Windows Forms шаблон - типы, которые выполняют большую часть работы от лица Windows Form, в которых находится основная часть клиентского кода, написанного для их конфигурации. Эти типы оказались идеальным вариантом решения для компонентов и элементов управления Windows Forms. Двигаясь в этом направлении, работа разработчика превращается в перетягивание компонента или элемента управления из Toolbox на форму, его конфигурацию с помощью средств времени разработки, таких как Property Browser, и предоставление дизайнеру Windows Forms возможности выполнить тяжкую работу по транслированию наших замыслов времени разработки в код, который сохраняется в InitializeComponent. Таким образом, программные приемы трансформируются в декларативные, где слово декларативный является синонимом слову продуктивный. Эта ситуация оказывается идеально подходящей для проверки достоверности Windows Forms.

Устанавливаем поддержку времени разработки

Первый шаг - установление поддержки времени разработки, которая требует, чтобы ваша реализация происходила от одного из трех типов компонентов времени разработки: System.ComponentModel.Component, System.Windows.Forms.Control, или System.Windows.Forms.UserControl. Если ваша логика не требует UI, правильным выбором будет Component, в противном случае - Control или UserControl. Разница между Control и UserControl в том, как генерируется UI. В первом случае UI генерируется из специального написанного вами кода отрисовки, тогда как во втором - UI генерируется из других элементов управления и компонентов. Наш существующий код проверки достоверности не отрисовывает себя самостоятельно, потому что он передает эту задачу ErrorProvider. Следовательно, получается, Component является самым подходящим выбором для упаковки наших классов проверки достоверности.

 
Имитация - самая открытая форма лести

Следующий шаг - решить, валидаторы какого сорта нам нужны. Хорошо начать с проверки, какие валидаторы реализует ASP.NET. Почему? Я большой фанат последовательности, кроме того, у меня на лбу вытатуировано "не придумывай колесо заново" (конечно же, задом наперед, чтобы я мог видеть эту надпись в зеркале, когда чищу зубы). По существу, я думаю, что основной целью должно быть создание валидаторов Windows Forms, совместимых с их ASP.NET-аналогами с точки зрения предоставляемых типов и членов (где решение имеет смысл в Windows Forms). В настоящий момент ASP.NET предлагает следующие валидаторы:

Элемент управления проверки достоверности Описание
RequiredFieldValidator Гарантирует, что поле содержит значение
RegularExpressionValidator Для проверки формата поля использует регулярное выражение
CompareValidator Осуществляет проверку на равенство заданного поля и другого поля или значения
RangeValidator Проверяет соответствие типа поля заданному и/или находится ли поле в определенном диапазоне значений
CustomValidator Создает обработчик события со стороны сервера для специальной логики проверки, более сложной, чем могут реализовать другие валидаторы

Таблица 1. Сущаствующие валидаторы в ASP.NET

Я также думаю, что еще одной целью должна быть расширяемость, чтобы в случае, когда предоставляемых валидаторов не достаточно, разработчики могли без особого труда объединять собственные специальные валидаторы в инфраструктуру. И, наконец, чтобы не делать лишней работы, реализация должна использовать существующую инфраструктуру проверки Windows Forms, рассмотренную ранее.

 
Валидатор обязательного поля

Помня о намеченных целях, время перейти к делу. Давайте начнем с построения самого простого из возможных валидаторов, RequiredFieldValidator.

Проект в Visual Studio .NET

Нам понадобится проект Visual Studio .NET, в котором мы будем реализовывать RequiredFieldValidator. Независимо от того, создаю ли я компонент или библиотеку элементов управления, я предпочитаю "двухпроектный" подход, который подразумевает разделение решения на проект компонента или элемента управления и соответствующий проект средства тестирования. Итак:

  • Создаем решение (winforms03162004_CustomValidation)
  • Добавляем проект Windows Forms Application в решение (CustomValidationSample)
  • Добавляем проект Windows Forms Control в решение (CustomValidation)
  • Из проекта CustomValidation удаляем стандартный UserControl1
  • Добавляем новый класс RequiredFieldValidator и наследуем его от System.ComponentModel.Component
Комментарий Комментарий: Я использую Visual Studio .NET 2003 и .NET Framework 1.1.

Интерфейс

Условие совместимости влечет за собой необходимость реализации того же интерфейса, который имеет смысл и в Windows Forms. Это означает реализацию тех же членов, что предоставляются RequiredFieldValidator ASP.NET:

class RequiredFieldValidator : Component {
  string ControlToValidate {get; set;}
  string ErrorMessage {get; set;}
  string InitialValue {get; set;}
  bool IsValid {get; set;}
  void Validate();
}

В следующей таблице детально описан каждый член.

Член Описание
ControlToValidate Определяет, какой элемент управления проверяется.
ErrorMessage Сообщение, отображаемое в случае, если ControlToValidate недостоверен. По умолчанию "".
InitialValue Обязательное значения не всегда означает значение, отличное от "". В некоторых случаях стандартное значение элемента управления может использоваться как приглашение, например, использование "[Выберите значение]" в элементе управления ListBox. В этом случае требуемое значение должно отличаться от исходного "[Выберите значение]". InitialValue поддерживает это требование. По умолчанию - "".
IsValid Сообщает, являются ли данные ControlToValidate достоверными или нет после вызова Validate. IsValid может быть задан из клиентского кода, когда вам надо переопределить Validate, так же как в ASP.NET-реализации. По умолчанию имеет значение True.
Validate Проверяет значение ControlToValidate и сохраняет результат в IsValid.

Таблица 2. Члены интерфейса

В ASP.NET ControlToValidate - это строка, используемая для индексации ViewState экземпляров элементов управления на серверной стороне. Такая косвенность необходима для обработки основанной на запросе не ориентированной на установление соединения природы Web-приложения. Поскольку этого нет в Windows Forms, мы можем обращаться в элементу управления непосредственно. Также, т.к. мы будем использовать ErrorProvider внутри, мы добавим свойство Icon, чтобы обеспечить разработчикам возможность настраивать. С учетом всего этого нам надо скорректировать интерфейс следующим образом:

class RequiredFieldValidator : Component {
  ...
  Control ControlToValidate {get; set;}
  Icon Icon {get; set;}
  ...
}

Реализация

Полученная в результате реализация выглядит так:

class RequiredFieldValidator : Component {
  // Закрытые поля
  ...
  Control ControlToValidate 
    get { return _controlToValidate; }
    set {  
      _controlToValidate = value;
      // Перехватывает событие Validating ControlToValidate
      // во время выполнения, т.е. не из VS.NET
      if( (_controlToValidate != null) && (!DesignMode) ) {
        _controlToValidate.Validating += 
        new CancelEventHandler(ControlToValidate_Validating);
      }
    }
  }
  string ErrorMessage {
    get { return _errorMessage; }
    set { _errorMessage = value; }
  }
  Icon Icon {
    get { return _icon; }
    set { _icon = value; }
  }
  string InitialValue {
    get { return _initialValue; }
    set { _initialValue = value; }
  }
  bool IsValid {
    get { return _isValid; }
    set { _isValid = value; }
  }  
  void Validate() {
    // Достоверен, если отличается от исходного значения,
    // которое не обязательно является пустой строкой
    string controlValue = ControlToValidate.Text.Trim();
    string initialValue;
    if( _initialValue == null ) initialValue = "";
    else initialValue = _initialValue.Trim();
    _isValid = (controlValue != initialValue);
    // Выводит на экран сообщение об ошибке, если ControlToValidate недостоверен
    string errorMessage = "";
    if( !_isValid ) {
      errorMessage = _errorMessage;
      _errorProvider.Icon = _icon;
    }
    _errorProvider.SetError(_controlToValidate, errorMessage);
  }
  private void ControlToValidate_Validating(
    object sender, 
    CancelEventArgs e) {
    // Мы не отменяем в случае недостоверности, поскольку не хотим
    // оставлять фокус на ControlToValidate, если он недостоверен
    Validate();
  }
}

Основная проблема этой реализации - как захватывается событие Validating элемента управления ControlToValidate:

Control ControlToValidate {
  get {...}
  set {
    ...
    // Захватываем событие Validating ControlToValidate
    // во время выполнения, т.е. не во время VS.NET-разработки 
    if( (_controlToValidate != null) && (!DesignMode) ) {
      _controlToValidate.Validating += 
        new CancelEventHandler(ControlToValidate_Validating);
  }
}

Это позволяет, чтобы необходимая проверка поля выполнялась динамически, по мере ввода данных, как в стандартной реализации Windows Forms. Мы также получаем дополнительный выигрыш, который не так очевиден. В исходной реализации фокус удерживается на элементе управления до тех пор, пока он действителен, т.е. пока свойство CancelEventArgs.Cancel его события Validating имеет значение False, по существу, блокируя пользователя в элементе управления. UI для ввода данных не должны захватывать пользователей где бы то ни было, и эта идея отражена в ControlToValidate_Validating, которое обрабатывает событие как сигнал запуска собственной валидации, а не как механизм для интеграции с базовой инфраструктурой проверки достоверности. Интеграция времени разработки

Функциональный аспект реализации мы рассмотрели, остался вопрос времени разработки. Все хорошие жители времени разработки Visual Studio .NET используют множество различных возможностей для определения своего поведения во время разработки. Например, RequiredFieldValidator использует ToolboxBitmapAttribute, чтобы определить специальный значок, отображаемый в Toolbox и Component Tray, так же как CategoryAttribute и DescriptionAttribute - чтобы сделать открытые свойства RequiredFieldValidator в Property Browser более описательными:

[ToolboxBitmap(
  typeof(RequiredFieldValidator), 
  "RequiredFieldValidator.ico")]
class RequiredFieldValidator : Component {
  ...
  [Category("Behaviour")]
  [Description("Gets or sets the control to validate.")]
  Control ControlToValidate {...}
  [Category("Appearance")]
  [Description("Gets or sets the text for the error message.")]
  string ErrorMessage {...}
  [Category("Appearance")]
  [Description("Gets or sets the Icon to display ErrorMessage.")]
  Icon Icon {...}
  [Category("Behaviour")]
  [Description("Gets or sets the default value to validate against.")]  
  string InitialValue  {...}
}

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

  • Определение того, какие элементы управления могут быть выбраны для ControlToValidate из Property Browser. Эта реализация позволяет выбирать элементы управления TextBox, ListBox, ComboBox и UserControl
  • Сокрытие IsValid в Property Browser, поскольку это свойство только времени выполнения (все открытые свойства автоматически отображаются в Property Browser во время разработки).

Использование RequiredFieldValidator

Чтобы использовать RequiredFieldValidator, нам надо добавить его в Toolbox. Только тогда его можно перемещать на форму методом Drag-and-Drop и настраивать. Для этого мы:

  1. Пересобираем решение.
  2. Открывает тестовую форму в Visual Studio .NET.
  3. Щелкаем правой кнопкой мыши Toolbox и выбираем Add Tab.
  4. Создаем новую вкладку Custom Validation и выбираем ее.
  5. Щелкаем правой кнопкой мыши Toolbox и выбираем Add/Remove Items.
  6. Находим вновь созданную сборку компонента (CustomValidation.dll) и выбираем ее.

Эти шаги позволяют вам использовать компонент в дизайнере Windows Forms

Я применил RequiredFieldValidator ко всем полям на форме Add New Employee с указанием типа значений, которые пользователь может ввести в каждое поле.

BaseValidator: разделяй и властвуй

Имея за плечами RequiredFieldValidator, мы запросто можем реализовать оставшиеся валидаторы. Ну, возможно, не так уж и запросто. RequiredFieldValidator плотно объединяет особую "требуемую" логику с остальной логикой, которую должен реализовать каждый валидатор, например, определение элемента управление, который должен быть подвергнут проверке. В подобных ситуациях правильным решением будет аккуратно разделить RequiredFieldValidator на два новых типа: BaseValidator и намного меньший RequiredFieldValidator, наследуемый от него. Строго говоря, с точки зрения совместимости, мы должны также создать интерфейс IValidator и реализовать BaseValidator в той же манере, что и ASP.NET. Однако, степень расширяемости, предоставляемая этим подходом, превышает наши сию минутные нужды. BaseValidator реализует многократно используемую часть логики проверки достоверности, напоминая исходный RequiredFieldValidator:

abstract class BaseValidator : Component, IValidator {
  Control ControlToValidate {...}
  string ErrorMessage {...}
  Icon Icon {...}
  bool IsValid {...}    
  void Validate() {...}
  private void ControlToValidate_Validating(...) {...}
}

Поскольку IsValid и Validate являются общими для все валидаторов, они становятся фасадными методами в новой архитектуре, оставляя специальные проверки на совести производных валидаторов. Следовательно, BaseValidator нужен механизм, с помощью которого от может "спрашивать" производный валидатор, является ли тот действительным или нет. Это осуществляется через дополнительный абстрактный метод EvaluateIsValid. И EvaluateIsValid, и BaseValidator являются абстрактными с целью обеспечения того, чтобы каждый производный валидатор знал, что должен иметь возможность ответить на этот вопрос. Новые дополнения потребуют кое-каких изменений, как показано здесь:

abstract class BaseValidator : Component {
  ...
  void Validate() {
    ...
    _isValid = EvaluateIsValid();
    ...
  }
  protected abstract bool EvaluateIsValid();
}

Результат таков, что BaseValidator может использоваться только через наследование, и должен быть реализован EvaluateIsValid. Метод Validate был скорректирован для обращения к методу EvaluateIsValid потомка, чтобы осведомиться об его действительности. Этот метод также применяется к элементам управления ASP.NET, и, конечно же, добавляет необходимую согласованность.

RequiredFieldValidator: Redux

Покончив с BaseValidator, теперь нам нужно переделать RequiredFieldValidator, чтобы поддержать соглашение, унаследовав от BaseValidator и реализовав указанные элементы проверки необходимых полей и в EvaluateIsValid, и в InitialValue. Новый, более "тонкий" RequiredFieldValidator выглядит так:

[ToolboxBitmap(
  typeof(RequiredFieldValidator), 
  "RequiredFieldValidator.ico")]
class RequiredFieldValidator : BaseValidator {
  string InitialValue  {...}
  protected override bool EvaluateIsValid() {
    string controlValue = ControlToValidate.Text.Trim();
    string initialValue;
    if( _initialValue == null ) initialValue = "";
    else initialValue = _initialValue.Trim();
    return (controlValue != initialValue);
  }
}
 
Семь бед - один ответ

Замечательное разделение универсальных и специальных функций проверки между базовым классом и производными позволяет нам теперь сосредоточиться только на специальной проверке достоверности. Этот метод хорош для RequiredFieldValidator, но даже еще лучше работает в других валидаторах, которые мы должны реализовать, чтобы обеспечить совместимость с ASP.NET, включая:

  • RegularExpressionValidator
  • CustomValidator
  • CompareValidator
  • RangeValidator

Давайте рассмотрим каждый из них в отдельности и познакомимся с логикой, используемой для их реализации.

 
RegularExpressionValidator

Регулярные выражения - это мощная текстовая основанная на соответствии шаблонов технология, выстроенная на базе богатого набора символов. Когда поле требует значений определенного формата, регулярные выражения являются превосходной альтернативой строковых операций, особенно в случаях с необычными шаблонами. Например, чтобы обеспечить, что телефонный номер введен в формате, принятом в Австралии -(xx) xxxx xxxx - нужно всего лишь одно регулярное выражение:

^\(\d{2}\) \d{4} \d{4}$

Вот почему ASP.NET применяет RegularExpressionValidator, и вот почему мы будем поступать также:

using System.Text.RegularExpressions;
...
[ToolboxBitmap(
  typeof(RegularExpressionValidator),
  "RegularExpressionValidator.ico")]
class RegularExpressionValidator : BaseValidator {
  ...
  string ValidationExpression {...}
  protected override bool EvaluateIsValid() {
    // Не проводить проверку, если поле пустое
    if( ControlToValidate.Text.Trim() == "" ) return true;
    // Успешна, если имеет место полное соответствие тексту ControlToValidate
    string field = ControlToValidate.Text.Trim();
    return Regex.IsMatch(field, _validationExpression.Trim());  
  }
}

Во время разработки разработчики могут ввести выражение валидации через браузер свойств. 

CustomValidator

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

[ToolboxBitmap(typeof(CustomValidator), "CustomValidator.ico")]
class CustomValidator : BaseValidator {
  class ValidatingCancelEventArgs {...}
  delegate void ValidatingEventHandler(object sender, ValidatingCancelEventArgs e);
  event ValidatingEventHandler Validating;
  void OnValidating(ValidatingCancelEventArgs e) {
    if( Validating != null ) Validating(this, e);
  }

  protected override bool EvaluateIsValid() {
    // Передаем работу по проверке достоверности в обработчик события и ожидаем ответа
    ValidatingCancelEventArgs args = 
      new ValidatingCancelEventArgs(false,ControlToValidate);
    OnValidating(args);
    return args.Valid;
  }
}

Вы обрабатываете событие Validating элемента управления CustomValidator, дважды щелкнув его в Property Browser.

Затем просто добавляем соответствующую логику проверки, которая в данном случае обеспечивает, чтобы возраст новых служащих был от 18 лет и старше:

class AddNewEmployeeForm : Form
  void cstmDOB_Validating(object sender, ValidatingCancelEventArgs e) {
  try {
    // Должно быть 18 лет или больше
    DateTime dob = DateTime.Parse(((Control)e.ControlToValidate).Text);
    DateTime  legal = DateTime.Now.AddYears(-18);
    e.Valid = ( dob < legal );
  }
  catch( Exception ex ) { e.Valid = false; }
}

Заметьте, что CustomValidator ASP.NET называет эквивалентное событие, ServerValidate, с учетом того, что обработка в ASP.NET происходит на стороне сервера. Мы задействовали Validating, потому что нам не надо принимать такое решение и потому что BaseValidator уже использует Validate, от которого происходит CustomValidator.

 
BaseCompareValidator

Таким образом, валидаторы могут обрабатывать только одно поле. В некоторых случаях, однако, проверка достоверности может распространяться на несколько полей и/или значений: или если надо удостовериться, что значение поля находится в рамках допустимого диапазона (RangeValidator), или чтобы сравнить одно поле с другим (CompareValidator). В любом случае, для осуществления проверки типов, преобразования и сравнения, включая тип с которым должны сравниваться значения, необходимо дополнительно поработать. Эта функциональность должна быть упакована в новый тип - BaseCompareValidator, от которого могут наследоваться RangeValidator и CompareValidator, что избавит от лишних проблем. Сам BaseCompareValidator происходит от BaseValidator, чтобы получить поддержку базовой проверки достоверности и реализовать четыре новых члена:

abstract class BaseCompareValidator : BaseValidator { 
  ...  
  ValidationDataType Type {...}
  protected TypeConverter TypeConverter {...}
  protected bool CanConvert(string value) {...}
  protected string Format(string value) {...}
}

ValidationDataType - специальное перечисление, которое определяет типы, поддерживаемые для сравнения проверки диапазона:

enum ValidationDataType {
  Currency,
  Date,
  Double,
  Integer,
  String
}
 
RangeValidator

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

[ToolboxBitmap(typeof(RangeValidator), "RangeValidator.ico")]
class RangeValidator : BaseCompareValidator {
  ...
  string MinimumValue {...}
  string MaximumValue {...}
  protected override bool EvaluateIsValid() {
      // Не проводим проверку, если поле пустое,
      // Проверяем и преобразуем минимальное значение
      // Проверяем и преобразуем максимальное значение
      // Проверяем минимум <= максимум
      // Проверяем и преобразуем ControlToValue
      // Выполняется ли условие минимум <= значение <= максимум
    }
  }
CompareValidator

Последним, но не в смысле значимости, идет CompareValidator, который используется для проведения проверки равенства ControlToValidate и любого другого элемента управления или значения. Они называются ControlToCompare или ValueToCompare, соответственно:

[ToolboxBitmap(typeof(CompareValidator), "CompareValidator.ico")]
class CompareValidator : BaseCompareValidator {
  ...
  Control ControlToCompare {...}
  ValidationCompareOperator Operator {...}
  string ValueToCompare {...}   
  protected override bool EvaluateIsValid() {
    // Не проводим проверку, если поле пустое,
    // Проверяем предоставленный ControlToCompare
    // Проверяем предоставленный ValueToCompare
    // Проверяем и преобразуем CompareFrom
    // Проверяем и преобразуем CompareTo
    // Осуществляем сравнение, например, ==, >, >=, <, <=, !=
  }
}

Operator определяет тест на равенство, ControlToValidate и ControlToCompare или ValueToCompare. Operator определяется в ValidationCompareOperator:

enum ValidationCompareOperator {
  DataTypeCheck,
  Equal,
  GreaterThan,
  GreaterThanEqual,
  LessThan,
  LessThanEqual,
  NotEqual
}

Как вы можете заметить, для проверки того, что ControlToValidate является определенным типом данных, определенным значением DataTypeCheck, также можно использовать CompareValidator. Это единственно сравнение, которое не требует ни ControlToCompare, ни ValueToCompare.

 
Завершенная инфраструктура специальной проверки достоверности

Теперь все эквивалентные ASP.NET валидаторы представлены и учтены. Каждая реализация прямо или косвенно происходит от BaseValidator, чтобы унаследовать базовую и многократно используемую функциональность проверки достоверности. Такое наследование создает неявно расширяемую инфраструктуру, которую другие разработчики могут использовать так же просто, как и мы. На рис. 12 показано решение для проведения специальной валидации и то, где разработчики могут ее применить. К ситуациям, где это может быть необходимым, относятся:

  • Создание нового универсального валидатора
  • Создание специфической проверки бизнес правил
  • Введение дополнительной логики в один из существующих валидаторов
Заключение

Сначала мы рассмотрели "родную" инфраструктуру проверки достоверности, встроенную в .NET Framework для Windows Forms. Затем мы разбили нашу логику проверки на несколько компонентов времени разработки, чтобы превратить изначально программную проверку достоверности в более декларативную и продуктивную. В ходе этого мы создали расширяемую инфраструктуру проверки достоверности, построенную на базе BaseValidator для реализации RequiredFieldValidator, RegularExpressionValidator, CustomValidator, CompareValidator и RangeValidator. Такая схема позволяет другим разработчикам без труда создавать новые валидаторы, также наследуясь от BaseValidator, чтобы получить универсальную функциональность и сузить проблему реализации до создания специальной проверки достоверности. В этой статье сосредотачивается внимание на поэлементной проверке достоверности и только упоминается валидация всей формы. Следующий этап строится на специальной проверке и включает валидацию всей формы, предусматривает сочетание существующих валидаторов и вводит еще один элемент набора элементов управления ASP.NET для проверки достоверности - ValidationSummary. Между теперь и потом рекомендую посмотреть фильм "Amazon Women on the Moon (Амазонки на Луне)", просто для смеха.

 
Альтернативное решение реализации специальной проверки достоверности

Впервые я создал компоненты проверки достоверности для компании Genghis еще в конце 2002 года. Изучая в это время статьи MSDN Magazine, я обнаружил посвященную проверке достоверности работу в колонке Билли Холлиза (Billy Hollis) (и Роки (Rocky)) Adventures in Visual Basic .NET в MSDN Online. Я рекомендую вам прочитать эту статью, чтобы ознакомиться с классной альтернативной проверкой Windows Forms и, плюс ко всему, ознакомиться с замечательной дискуссией по реализации свойств расширителя для времени разработки.

 
C# и Visual Basic .NET

Некоторые замечания по двум моим предыдущим статьям свидетельствуют о том, что они написаны только для C#, тогда как большинство читателей колонки - разработчики Visual Basic .NET. Лично я, несомненно, предпочитаю C#, но я чрезвычайно рад писать для Visual Basic.NET. Я бы с удовольствием предоставлял и Visual Basic .NET, и C#-версии примеров кода, но у меня просто нет для этого времени. Чтобы никого не обделить, я буду чередовать Visual Basic .NET и C#. Таким образом, первая, ориентированная на Visual Basic .NET статья появится во второй части (следующая часть, посвященная валидации, продолжит начатую здесь на C#работу). Мне бы хотелось осчастливить оба сообщества в рамках того времени, которое я могу потратить на эту работу. Такое решение подходит? Пожалуйста, свои мнения присылайте мне по электронной почте.

 
Genghis

Более полная и немного отличная от данной версия этого решения в настоящее время доступна из Genghis, общий исходный набор расширений для разработчиков .NET Windows Forms а ля Библиотека базовых классов Microsoft (Microsoft Foundation Classes - MFC). Любопытно, что работая на этой статьей, мне понадобилось сделать несколько усовершенствований, которые будут отражены в Genghis в следующей версии. Однако свободно скачивайте ее сейчас, если не можете ждать и, если у вас есть время, пожалуйста, сообщайте мне о любых ошибках или своих пожеланиях.


Страница сайта http://185.71.96.61
Оригинал находится по адресу http://185.71.96.61/home.asp?artId=22041