О том, как можно проверять значения, введёные пользователем

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

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

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

    На практике это выливается с довольно длинные последовательности «if-else». Лично мне это жутко не нравилось, так как сложно с первого взгляда определить, какие поля как проверяются и какие сообщения выдаются в случае ошибок. А ведь полей в форме может быть и десять, тогда код проверки вообще затягивается. Вобщем, я задумался над тем, как можно минимизировать объём работ и вот что из этого получилось.

    Я представил проверку как преобразование значения одного типа в значение другого типа (например, проверка того, что в поле введено чило, это преобразование строки в число). Тоесть проверка — это некая функция, имеющая сигнатуру делегата System.Converter<T, U>.

    Для проверки значение помещаем в класс обёртку:

    public class ValidationStep<T>
    {
      public T Value { get; set; }
    }


    Суть проверки заключается в последовательном вызове методов-расширений для объектов класса ValidationStep<T>, которые опять же возращают объект класса ValidationStep<T>. Это позволяет создавать цепочки проверок. Вот пример таких методов-расширений:

    public static class ValidationExtensions
    {
      public static ValidationStep<T> Validate<T>(this T value)
      {
        return new ValidationStep<T> { Value = value };
      }
      public static ValidationStep<T> Validate<T>(this ValidationStep<T> step,
        Predicate<T> predicate, string message)
      {
        if (predicate(step.Value)) return step;
        throw new ValidationException(message);
      }
      public static ValidationStep<U> Convert<T, U>(this ValidationStep<T> step,
        Converter<T, U> converter, string message)
      {
        try { return converter(step.Value).Validate(); }
        catch { throw new ValidationException(message); }
      }
    }


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

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

    public static class Validation
    {
      public static bool Validate<T, U>(T value, Converter<T, U> validator,
        Action<U> onSuccess, Action<ValidationException> onFailure)
      {
        try
        {
          var result = validator(value);
          onSuccess(result);
          return true;
        }
        catch (ValidationException e)
        {
          onFailure(e);
          return false;
        }
      }
    }


    А теперь о том, как это использовать. Допустим нам нужно проверить текстовое поле (tb1) и убедиться, что в него введено целое число в диапазоне от 0 до 10. Это можно сделать так:

    Validation.Validate(
      tb1.Text, value => value.Validate()
        .Validate(x => x.Length > 0, "Введите что-нибудь")
        .Convert(x => Convert.ToInt32(x), "Введёное значение не является целым числом")
        .Validate(x => x >= 0, "Число не должно быть меньше нуля")
        .Validate(x => x <= 10, "Число не должно быть больше десяти"),
      v => MessageBox.Show("Введено корректное значение: " + v.Value),
      e => MessageBox.Show(e.Message));


    Учитывая, что проверок может быть больше, да и число полей в форме, как правило, больше одного, такой способ проверок может быть очень даже удобен.

    Ну вот, собственно, и всё: о)
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 61

      +29
      — введите число
      — -1.1
      — положительное!
      — 1.1
      — целое!
      — 1
      — чётное!
      — как же вы задолбали со своими исключениями. нельзя что-ли сразу все ошибки выдать?
      — введите число

      * основано на реальных событиях
        +2
        Что именно нужно вводить должно быть указано рядом, например:
        Введите число от 0 до 10: [ А тут текст бокс ]
        Но это уже дело разработчика.

        Мой вариант только проверяет то, что пользователь введёт.
          0
          К тому же (при регистрации пользователя например) может проверяться то, что ещё не известно до проверки.
          Думаю пользователь не поймёт юмора, если ему написать «Введите корректный логин, который ещё не используется»
            0
            вот именно что не поймёт.
          • UFO just landed and posted this here
              0
              абсолютно задолбало!
              особенно когда где-нибудь регистрируешься, и логин-пароль только с десятой попытки разрешает, каждый раз находя какую-нибудь чепуху.
              очень бесит!
              0
              а вы думали над реализацией механизм валидации на базе атрибутов и регулярных выражений? скажем для метода задается набор атрибутов для каждого из параметров с указанием регулярного выражения для валидации.
                0
                По-моему это не совсем то. Я не говорю о проверке параметров метода. Я говорю именно о проверке значений, введёных пользователем в некую форму и сопоставление каждой проверке некоторого сообщения, которое должно выдаваться пользователю если он ввёл неверное значение.
                  0
                  Основной идеей является сократить код, который в любом случае придётся писать, до минимума, чтобы с одного взгляда было понятно, что значение проверяется сначала на пустоту, потом на то, что оно является целым, а затем, что оно принадлежит некоторому интервалу.

                  Главное, что так можно организовать проверку любого значения на любые условия.
                    0
                    а у меня валидацией занимается одна функция, она принимает обьект, в котором находится регулярное выражение и текст ошибки, и данные пользователя… в итоге мы имеем одну универсальную функцию, которой надо лишь дать один обьект с 2-мя параметрами и все, кода меньше чем минимум :) как Вам такое?
                    +1
                    да, сорри я уже привык к mvc
                    но все равно, для проверки, по моему, лучше использовать регулярные выражения
                      0
                      Смысл в том, что если пользователь, например, не введёт логин вообще, то ему нужно выдавать не сообщение о том что «введён неправильный логин» а о том, что «Вам необходимо ввести логин» или «Вы забыли ввести логин»

                      В ValidationExtensions можно добавить и метод проверки регулярных выражений:

                      public static ValidationStepRegexValidate(this ValidationStepstep,
                      string regexString, string message)
                      {
                      var regex = new Regex(regexString);
                      if(regex.Match(step.Value)) return step;
                      else throw new ValidationException(message);
                      }

                      Тогда функция авторизации пользователя будет выглядеть так:
                      bool Auth(string login, string password)
                      {
                      bool success = true;
                      Validation.Validate(
                      login, value => value.Validate()
                      .Validate(x => x.Length > 0, «Вы не ввели логин»)
                      .RegexValidate(«Тут регулярка для логина», «Введён некорректный логин»),
                      null,
                      e => { ShowError(e.Message); success = false });

                      if(!success) return false;

                      //Дальше проверяем пароль
                      }

                      Просто иногда число проверок может быть не две, как тут, а пять и более (при каждой должно выдаваться своё сообщение), тогда такая запись оазывается и понятнее и короче.
                        0
                        Как вы собираетесь проверить регулярным выражением при регистрации тот факт, что такой логин уже существует? Лучше наверное сделать базовый класс валидатора, от которого уже наследовать различные валидаторы, проверяющие значения регекспами или кастом-кодом.
                          0
                          автор говорит о проверке ввода, понятно что уникальность значения регуляркой не проверишь
                            0
                            Вообще есть два типа проверок: проверка предикатом и проверка на возможность преобразования.
                            проверка регуляркой может быть записана и так:

                            var regex = new Regex(«Тут регулярка для логина»);

                            Validation.Validate(
                            login, value => value.Validate()
                            .Validate(x => x.Length > 0, «Вы не ввели логин»)
                            .Validate(x => regex.Match(step.Value).Success, «Введён некорректный логин»)
                            0
                            ниже написал, как проверить, проверка же осуществляется функцией вида string -> bool, соответственно и проверка будет:

                            login, value => value.Validate()
                            .Validate(x => x.Length > 0, «Вы не ввели логин»)
                            .RegexValidate(«Тут регулярка для логина», «Введён некорректный логин»)
                            .Validate(x => !IsUsedLogin(login), «Такой логин уже есть»),
                            null,
                            e => { ShowError(e.Message); success = false });
                        +1
                        Если к данным привязываемся через data binding, то методы с атрибутами весьма накладно использовать. Валидировать придется по событию формы, например, нажатию кнопки, после чего вызывать методы. Здесь вилка — или усложняем логику формы и не паримся с валидацией, или упрощаем привязку данных и паримся с валидацией:) Имхо, второе лучше. По-вашему, можно навешать атрибутов на сам класс, потом его инстанс проверять в специальном валидаторе (такое решение в Enterprise Library, кстати). Но это загромождение бизнес-объекта лишними излишествами и не совсем быстро покрывается тестами.

                        А можно наваять свое, как у автора. Имхо, все прикольно, но я бы не стал валидировать степ-бай-степ. Кусочки валидации надо разделить на простые, и сложные. Простые — это регэкп, например, а сложные — запрос в базу данных на уникальность. Когда пользователь заполнил все что нужно и жмакнул ок, ему вывелся ValidationResult сначала с проверкой на простые условия. Когда выполнил все простые условия, идут проверки на сложные:) Но идея в статье хорошая — соединить flow of control с валидацией:)
                          0
                          Идея передавать функции для валидации включает возможность передачи заранее предопределенных функций, которые содержат регекспы, простые условия или запросы к марсоходу, или лямбды, как в примере.

                          Проблема в том, что тут нет ничего нового, никакого оригинального подхода.

                          Есть набор стандартных решений:
                          Валидатор со стандартным интерфейсом.
                          Последовательное выполенение проверок до первого исключения.
                          Исключение выводит сообщение об ошибке.

                          Что я вижу в статье: автор обнаружил и делится возможностью перехватывать исключения и передавать лямбды в качестве параметров. Еще он обнаружил chained method call и еще не понял почему это антипаттерн.
                            0
                            Никого насильственным образом не принуждаю.: о)
                          +1
                          а вот на Python: bsdemon.habrahabr.ru/blog/49949/
                            0
                            Почему нельзя написать несколько классов валидаторов с единым интерфесом, грубо говоря все строки проверять StringValidator, email EmailValidator и т.д. Написать прослойку, через которую будет забераться поле, что то типа InputData.get(Name). а уже прослойка на основе конфига, будет создовать объект валидатор, валидировать и результат отдовать приложению.

                            PS: Возможно я не совем понял ваш метод.
                              0
                              Можно и даже наверное нужно, но (ещё раз говорю) как правило нужно производить несколько проверок и выдавать сообщение об ошибке в зависимости от этапа проверки.

                              например проверка емейла в вашем варианте будет иметь вид:
                                0
                                string email = «pupkin@yandex.ru»
                                if(email.Length = 0)
                                {
                                Error.Text = «Вы не ввели емейл»;
                                return;
                                }
                                else if(IsValidEMail(email))
                                {
                                Error.Text = «Это не емейл»;
                                return;
                                }
                                else if(!IsUsedEmail(email)) //Если нужно проверить, что юзер с таким адресом есть
                                {
                                Error.Text = «Нет пользователя с таким адресом»;
                                return;
                                }
                                  +1
                                  несовесем, ближе к этому, ну да дело вкуса
                                  public void validation(){
                                  try{
                                  isMoreThenZero();//на самом деле проверяется вместе с валидностью
                                  isValidMail();
                                  isUsedMail();
                                  catch(){
                                  }
                                  }

                                  protected isMoreThenZero();
                                  protectd isValidMail();
                                  protected isUsedMail();
                                  0
                                  Я не предлагаю новых способов проверки, я предлагаю немного изменить запись

                                  bool success = true;

                                  Validation.Validate(
                                  email, value => value.Validate()
                                  .Validate(x => x.Length > 0, «Вы не ввели емейл»)
                                  .Validate(x => IsValidEMail(email), «Это не емейл»)
                                  .Validate(x => !IsUsedEmail(email), «Нет пользователя с таким адресом»)
                                  null, e => { ShowError(e.Message); success = false });

                                  if(!success) return false;
                                  • UFO just landed and posted this here
                                      0
                                      сообщения, должны быть в отдельную dll затолканы, хардкод некашерно, да и переводить если, что…
                                        0
                                        сама валидация не выносится в конфиг, конфиг прсто говорит какой из классов валидаторов использовать, для конкретно этого параметра
                                          0
                                          конфиг сдесь вообще не нужен
                                          что там хранить? Есть конкретная форма, в ней конкретное поле конкретно проверяется конкретной цепочкой проверок: о)

                                          Это же просто аналог конструкции if-else-if-else-…
                                            +1
                                            предположим проект, с 200 сотнями форм, в каждой от 5 до 15 полей.
                                            писать для каждой цепочка зае… ну короче быстро надоест
                                            мой вариант
                                            пшется около 20(если не повезет то побольше) классов валидаторов, каждый из которых делает свой специфичный стек проверок. тексты ошибки это отдельный ресурс(ы).
                                            на каждую форму(обычно оптимизируется) прописываетя конфиг с соответствием поле->валидатор.

                                            вот за этим и нужны конфиги.

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

                                            в этом случае конфиг помогает автоматизировать установку соответствие имяни поля и типа.

                                            у обоих методов свои недостатки, но отлов косяков (через unittest) был удобен
                                          • UFO just landed and posted this here
                                              0
                                              наверное мне не очень везло, но мне часто достовался вариант с динамическим созданием форм, ну и довольно часто от релиза к релизу скакали требования
                                                0
                                                Я эту штуку как раз и делаю для динамических форм: о)
                                            0
                                            .Validate(x => x.Length > 0, GetText(«Empty Field»)) //Собственно достать конкретное сообщение для текущей культуры
                                            С выносом текстов проблемм нет, все эти проверки появляются в конечном продукте.
                                            можно вместо текста ошибки возвращать некоторый строковой код, главное чтобы в программе были определены методы, которые ошибку будут обрабатывать
                                            Эти методы будут вызываться в делегатах (ActiononSuccess, ActiononFailure)

                                            Цепочка проверок никаким образом не зависит от того что проверяется и какие ошибки при этом возникают
                                              0
                                              делегаты съелись Action<U> onSuccess, Action<ValidationException> onFailure
                                              • UFO just landed and posted this here
                                            0
                                            Интересный подход, хороший синтаксис.

                                            А как будет выглядить валидация в форме смены пароля?

                                            Надо что бы пользователь ввёл старый пароль, новый пароль и его подтверждение. Также проверка на сложность нового пароля.
                                              0
                                              никаких проблемм, вам же всё равно значения собирать нужно в некоторых переменных:

                                              var success = true;

                                              string password;

                                              Validation.Validate(
                                              PasswordTB.Text, value => value.Validate()
                                              .Validate(x => x.Length > 0, «Вы не ввели пароль»)
                                              .Validate(x => IsValidPassword(email), «Неверный пароль»)//Тут вообще могут идти проверки на длину, сложность итд.
                                              v => { password = v.Value; },//При успешной проверке сохраняем пароль
                                              e => { ShowError(e.Message); success = false });//При неуспешной показываем ошибку

                                              if(!success) return;

                                              string passwordConfirm;

                                              Validation.Validate(
                                              PasswordTBConfirm.Text, value => value.Validate()
                                              .Validate(x => x.Length > 0, «Вы не ввели подтверждение пароль»)
                                              .Validate(x => x == password, «Подтвержение не верное»),
                                              v => { passwordConfirm= v.Value; },//При успешной проверке сохраняем подтверждение
                                              e => { ShowError(e.Message); success = false });//При неуспешной показываем ошибку
                                                0
                                                главное, чтобы проверка на сложность выводила всего лишь предупреждение, а не была обязательным критерием для акцептирования пароля системой

                                                а то ставил я тут на игровую консоль линукс, так он потребовал обязательный сложный пароль не короче шести символов! ноги открывать таким программистам!
                                                  0
                                                  да ладно вам, у каждого свои понятия о норме… ну и степень паранои
                                                  у меня на домашнем рутовый на 11 символов стоит:-)
                                                    +2
                                                    это игровая консоль
                                                    она подключена только к телевизору
                                                    чтобы запустить на ней линукс, надо ещё постараться

                                                    НА НЕЙ СОВСЕМ НЕ НУЖЕН ПАРОЛЬ!!!

                                                    ну вывел ты предупреждение, что пароль «плохой», ну и успокойся, позволь пользователю решать.
                                                    это не ЦУП и не онлайн-банкинг, ЭТО ИГРОВАЯ КОНСОЛЬ!!!
                                                      0
                                                      а эта сборка для консоли?
                                                      тогда сори
                                                    0
                                                    Вообще, я так предполагаю, эти проверки должны осуществляться только когда пользователь нажал на кнопку «Выполнить», «Отправить», «Зарегистрироваться» и в случае успеха выполнить какое-то действие.

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

                                                    Поэтому сдесь чётко: Есть ошибка — выполнить то-то, нету ошибки — то-то
                                                      0
                                                      «слабый» пароль — это не ошибка!
                                                      это как заставить всех ходить по городу в шлеме и защите не локтях и коленях, а то вдруг упадёте
                                                        0
                                                        Я и не говорю что это ошибка, просто это должно проверяться до того как пользователь нажал «Зарегистрироваться» или что-то в этом роде.

                                                        Поэтому эта проверка должна отдельно производиться до нажатия. После нажатия уже проверяются только ошибки которые не позволяют выполнить некоторое действие.
                                                      –1
                                                      Ну уж Линукс позволяет настротить все что можно, я думаю в нем вообще можно отключить пароли при желании.
                                                    0
                                                    Основная идея написания кода — понятность, простота.

                                                    Вот ...validate().validate().validate().validate().validate().validate()… нужно разбить на отдельные независимые вызовы, чтобы их разделить, и этим облегчить чтение и отладку кода.

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

                                                    Это антипаттерн часто встречается в коде с использованием jQuery: $(".button").filter(".blue").attr(«more», «gore»).end().filter(".red").remove().end().filter()…
                                                    В результате нужно держать в памяти все точки .end(), где сбрасывается предыдущий фильтр.
                                                      0
                                                      Согласен, но я преследовал две цели:
                                                      1) Сократить наисание рутиного кода (Первую я всегда преследую)
                                                      2) Иметь возможность передавать методы валидации как параметры в другие методы. (Это мне сейчас особо нужно) А другим способом собрать единый метод валидации из нескольких составных частей я не вижу.
                                                      0
                                                      Вам не кажется, что валидатор должен проверять входной параметр на соответствие некоторым правилам, а не выполнять функции бизнес-логики (например: проверка пользователя с указанным емайлом)?
                                                      А вообще, подобные действия вписываются в паттерн Strategy
                                                        0
                                                        Валидатор не проверяет адрес, он проверяет условие x=>IsUsedEMail(x)
                                                        0
                                                        Раз уж тут тема пошла по поводу валидаторов, конечно немного не в тему) но все же) как обстаят дела с яваскрипт валидаторами для асп.нет мвц, как валидировать на клиенте? или нужно писать свои яваскрипт валидаторы?
                                                          0
                                                          Там же вроде есть ещё со времен 1.0 или 2.0, я правда ими не пользовался, не знаю, обычно аяксом проверки делаю: о)
                                                          –1
                                                          Эту тему уже поднимали. Exceptions предназаначены для исключительных ситуаций в программе, нарушающих обычный ход выполнения, а ввод неправильного значения — это самая банальная ситуация.

                                                          Использорвание exceptions тут неоправданно и порождает путаницу. Лучше уж циклом проверять, а устаовия хранить в масиве массивов.
                                                            –1
                                                            +1!
                                                              +1
                                                              Это религиозный вопрос.
                                                              Отвалилась база, внешнее стройство не отвечает… все тоже банальные ситуация которые можно предусмотреть без использования ексепшенов.

                                                              Оба подходода(и комбинированный за компанию) имеют права на жизнь. И обычно решаются на уровне Нотации
                                                                0
                                                                неполучится в массиве, меняются типы делегатов.
                                                                А чем вам не нравится исключительная ситуация при проверке значения?
                                                                  0
                                                                  Даже не типы делегатов, а тип проверяемого значения, сначала строка, потом int
                                                                    0
                                                                    Тем, что по моему, исключительная ситуация — та, при которой и упасть не грех, мол БД не работает, извиняйте, хозяин)) В редких случаях можно перехватить исклбчение и что-то исправить. А неправильно введенное поле — банальная ситуация, в ней в принципе ничего исключительного нет.
                                                                  0
                                                                  Очень понравилось
                                                                  возьму не заметку
                                                                    +1
                                                                    Внимательно прочитал обсуждения и нигде не увидел мысли о том, проверку нужно привязывать не к полю на форме, а к свойству того объекта, которое изменяется полем. Долго имел дело с большими системами с тоннами кода и практически везде видел одну и ту же картину: есть объекты с данными, один объект можно редактировать в разных формах и во все формы разработчики упорно копируют проверки ввода, делают подпорки что бы меньше писать кода проверок и т.д. Однако каждая смена условий превращается в увлекательное сафари «Найди, где пропустил проверку».

                                                                    Сейчас пробую использовать несколько иной подход к проверкам.
                                                                    Для начала делим все проверки на 2 больших класса: индивидуальные проверки свойств и проверки с использованием значений других свойств и(или) объектов.
                                                                    Далее разбиваем проверки на 3 этапа: проверка введенного текста (практически все данные от пользователя идут в виде текста), конвертация текста в нужный нам тип, проверка полученного значения в контексте значений других свойств и(или) объектов. Индивидуальные проверки могут идти на всех 3-х этапах, групповые проверки практически никогда не идут на этапе 1, обычно редко идут на этапе 2, и очень часто на этапе 3.

                                                                    Все проверки прописываются как атрибуты на свойстве класса.

                                                                    Делаем статический класс, который имеет метод setValue(object o, string propertyname, string value) этот метод делает следующее:
                                                                    1. Берет у указанного свойства объекта все атрибуты проверок и разбивает на 3 части: массив проверок текста, конвертер, массив проверок значения
                                                                    2. Вызывает все проверки из массива проверок текста и собирает сообщения для показа. Если какая-то проверка не проходит, то дальше не идем.
                                                                    3. Вызывает конвертер, если преобразовать данные невозможно, то выводит сообщение и дальше не идем.
                                                                    4. Вызывает все проверки из массива проверки значений и собирает сообщения для показа. Если какая-то проверка не проходит, то дальше не идем.
                                                                    5. Реально устанавливает значения свойства. Готово.

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

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

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

                                                                      0
                                                                      Мы на php в веб-приложениях например можем валидацию вообще в модель перенести, тоже код в принципе в одном месте собран  — в функции Validate() общего для всех моделей класса. А частные проверки можно сделать например, переопределяя метод validateField(...) (вызывается для каждого поля). Ну и предусмотреть функцию invalidate($field_name, $error_message), которая вызывается при обнаружении ошибки и пополняет соответствующий массив с ошибками валидации.

                                                                    Only users with full accounts can post comments. Log in, please.