Как связать валидацию форм в ASP.NET MVC и Bootstrap 3

Проблема

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

<div class="form-group">

    <label for="FirstName">First name</label>

    <input class="input-validation-error form-control" data-val="true"
           data-val-required="The First name field is required."
           id="FirstName" name="FirstName" type="text" value="">

    <span class="field-validation-error" data-valmsg-for="FirstName"
          data-valmsg-replace="true">The First name field is required.</span>
</div>

Как мы видим, ASP.NET MVC использует свои собственные классы input-validation-error (для подсветки контрола) и field-validation-error (для элемента отображающего ошибку).

Без описания этих классов в CSS вышеупомянутый HTML выглядит следующим образом:

Не плохо, но могло бы быть лучше. Bootstrap подсвечивает весь контрол и выделяет так же, с помощью CSS, текст ошибки:

 

Для этого, согласно документации Bootstrap, нам нужно удостовериться что элемент с ошибкой имеет родительский элемент с классом has-error, а также элемент для текстового отображения ошибки имеет класс text-danger. Вот пример как должен выглядеть HTML для его корректной  подсветки :

<!-- Класс 'has-error' добавлен к родительскому элементу относительно input -->
<div class="has-error form-group">

    <label for="FirstName">First name</label>

    <input class="input-validation-error form-control" data-val="true"
           data-val-required="The First name field is required."
           id="FirstName" name="FirstName" type="text" value="">

    <!-- Класс 'text-danger' был добавлен к элементу span -->
    <span class="text-danger field-validation-error" data-valmsg-for="FirstName"
          data-valmsg-replace="true">The First name field is required.</span>
</div>

Решение 

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

 

Без валидации на стороне клиента 

Если валидация происходит на стороне сервера мы можем просто добавить нужные нам CSS классы при загрузке страницы:

$(document).ready(function() {
    $('.input-validation-error').parents('.form-group').addClass('has-error');
    $('.field-validation-error').addClass('text-danger');
});

ASP.NET MVC ненавязчивая валидация

В большинстве случаев используется плагин unobtrusive jQuery validation, и все что нам остается это перехватить события success и errorPlacement плагина валидации JQuery:

$(document).ready(function () {

    //Будем обрабатывать все формы на странице для которых создан валидатор
    var forms = $('form'), formData;
    for (var i = 0; i < forms.length; i++) {
        formData = $.data(forms[i]);
        if (formData.hasOwnProperty("validator")) {

            // Сохранить уже существующие обработчки в локальных переменных
            var settings = formData.validator.settings,
                oldErrorPlacement = settings.errorPlacement,
                oldSuccess = settings.success;

            settings.errorPlacement = function (label, element) {

                // Вызываем старый обработчик 
                oldErrorPlacement(label, element);

                // Добавляем классы Bootstrap 
                label.parents('.form-group').addClass('has-error');
                label.addClass('text-danger');
            };

            settings.success = function (label) {
                // Удаляем  has-error класс из <div class="form-group">
                // Можно не заботиться об обработке текстового отображения ошибки, так как плагин его автоматичски удалит при успешной валидации
                label.parents('.form-group').removeClass('has-error');

                // Вызываем старый обработчик 
                oldSuccess(label);
            };

        }
    }
});

Я поместил этот код в отдельный файл MVCToBootstrapValidation.js и добавил в его бандл ~/bundles/jqueryval который теперь выглядит следующим образом:

bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.validate*",
                        "~/Scripts/MVCToBootstrapValidation.js"));

 

Что такое Boilerplate code?

Читая разные статьи или книги часто встречаюсь с понятием boilerplate code, но как оказалось, в моем окружении мало кто понимает его значение в программировании. 

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

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

Примеры :

Самый простой пример это структура html :

<!DOCTYPE html>   
<html>   
   <head>   
      <title></title>   
   </head>   
   <body> </body>   
</html>

 

В C#, чтобы объявить обычные свойства класса, раньше мы писали так :

class Student   
{   
  private string studentName;   
  public string StudentName   
  {   
     get {return studentName;}   
     set {studentName = value;}   
  }   
}


Если у нас нет никакой специфической логики для чтения/записи этого свойста, то оно расценивается как boilerplate. Такой код занимает много места и как мы уже отметили не делает ничего кроме инкапсуляции доступа к свойству studentName. К тому же они довольно утомительны для написания, если их нужно написать десяток. Конечно же Visual Studio может их сгенерировать, но более эффективным способом борьбы будет использование автосвойств в C#:

class Student   
{   
   public string StudentName{ get; set; }   
}

Такой код абсолютно индентичен, более краток и лучше читаем чем предыдущий.