5 ноября 2012 г.

Использование MVVM + Validation от Kendo, для ASP.Net MVC

Наверное только ленивый в последнее время не писал о использовании MVVM в веб-приложениях. Эта штука действительно уж как-то быстро набирает обороты, а программирование на стороне клиента переходит на существенно новый уровень.

Если вдруг кто еще не в курсе, то MVVM - Model View View-Model. Похоже на наш православный MVC, но вместо контроллера выступает View-Model. Подробнее об этом можно почитать здесь.

Конечно же, самым ярким представителем среди реализаций этого паттерна является библиотека Knockout. Но раз уж мы начали активно использовать Kendo, то можно попробовать применить в проекте именно его реализацию. Да-да, Kendo может предложить нам и такое.

Сегодня мы опробуем Kendo MVVM в связке с простым asp.net mvc приложением. Заодно немножко зацепим Kendo-вскую валидацию на клиенте. Ну и на десерт, воспользуемся еще несколькими интересными плюшками фреймворка.


Если коротко, то MVVM позволяет сформировать модель прямо на клиенте, в javascript, после чего привязать свойства этой модели к конкретным элементам страницы. Это всеравно, что привязка модели к представлению в стандартном asp.net mvc, но с той лишь разницей, что привязка происходит не по атрибутам id и name, а через специальные атрибуты data-bind. Такая привязка является двухсторонней. А это означает, что если меняется значение поля, то тут же, автоматом, меняется и значение соответствующего свойства клиентской модели.

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

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

   1:  <form id="userForm" class="k-block">
   2:  <div class="k-header k-shadow centeredText">
   3:      @ViewBag.Message</div>
   4:  <br />
   5:  <div class="editor-label">
   6:      <label for="fname">
   7:          First Name:</label>
   8:  </div>
   9:  <div class="editor-field">
  10:      <input id="fname" name="fname" data-bind="value: person.firstName" 
                                required validationmessage="*" />
  11:  </div>
  12:  <div class="editor-label">
  13:      <label for="sname">
  14:          Last Name:</label>
  15:  </div>
  16:  <div class="editor-field">
  17:      <input id="sname" name="sname" data-bind="value: person.lastName" 
                               required validationmessage="*" />
  18:  </div>
  19:  <div class="editor-label">
  20:      Gender:
  21:  </div>
  22:  <div class="editor-field">
  23:      <select data-bind="source: genders, value: person.gender">
  24:      </select>
  25:  </div>
  26:  <div class="editor-field">
  27:      <label>
  28:          <input type="checkbox" data-bind="checked: agreed" />
  29:          Entered data is verifed by administrator</label>
  30:  </div>
  31:  <p>
  32:      <center>
  33:          <button data-bind="enabled: agreed, click: addUser">
  34:              Add User</button>
  35:      </center>
  36:  </p>
  37:  </form>

Обратите внимание на атрибут data-bind у элементов. Через него и происходит связывание модели. Атрибуты id и name нужны там только для валидации, но к ней вернемся чуть позже.

Рассмотрим строку:

data-bind="value: person.firstName"
Здесь указанно, что атрибут Value данного элемента будет связан с свойством firstName объекта Person, который содержится в модели. Другими словами, любой текст, введенный в это поле, будет тут же писаться в person.firstName.

В случае с выбором пола все немного сложнее:

data-bind="source: genders, value: person.gender"
Тут указанно, что источником данных (варианты выбора) для этого select-а будет объект genders модели, и в то же время атрибут Value связан с свойством gender объекта Person. Тоесть две привязки подряд. Так тоже можно.

С чекбоксом картина аналогичная:

data-bind="checked: agreed"
В agreed запишется true, если флажок поставится, и false - если снимется.

Ну и последнее, кнопка отправки данных:

data-bind="enabled: agreed, click: addUser"
Мы связали атрибут enabled с свойством agreed, и повесили на событие click метод AddUser модели. Как я уже говорил, все привязки двухсторонние. В данном случае это означает, что как только в свойство agreed попадет false, то у кнопки сразу появится атрибут disabled, и она станет неактивной. Довольно красивое решение, не правда ли?

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

required validationmessage="*"

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

С представлением закончили. Теперь перейдем к самому функционалу.
По сценарию, пользователь вводит данные и нажимает кнопку. Введенные данные, пройдя клиентскую валидацию, передаются в JSON-е на сервер, где обрабатываются.
Посмотрим на метод контроллера, который будет принимать запрос:

   1:  [HttpPost]
   2:  public ActionResult AddUser(Person user)
   3:  {
   4:      if (Request != null && Request.IsAjaxRequest() && ModelState.IsValid)
   5:      {
   6:          // Adding user to DB
   7:          Thread.Sleep(3000);
   8:   
   9:          return Content(bool.TrueString);
  10:      }
  11:   
  12:      return Content(bool.FalseString);
  13:  }

Здесь мы просто принимает данные, проверяем их на валидность, и обрабатываем (для имитации длительной обработки задержим поток на 3 секунды). Если все пройдет успешно, метод вернет браузеру ответ "True" - это и будет сигналом успешности операции.

Пришла пора самого интересного. Того, что лежит под капотом MVVM. Все дело будет происходить в javascript-файле, подключенном к представлению.

Для начала, нам необходимо "включить валидацию" для формы userForm. Делается это строкой:

var validator = $("#userForm").kendoValidator().data("kendoValidator");

Далее происходит само формирование модели, и ее ассоциация с формой:

   1:  var viewModel = kendo.observable({
   2:      person: {
   3:          firstName: "",
   4:          lastName: "",
   5:          gender: "Male"
   6:      },
   7:      genders: ["Male", "Female"],
   8:      agreed: false,
   9:      addUser: function (e) {
  10:          e.preventDefault();
  11:   
  12:          if (validator.validate()) {
  13:              SendData();
  14:          };
  15:      }
  16:  });
  17:   
  18:  kendo.bind($("#userForm"), viewModel);

Мы создаем объект viewModel, формируем все его нужные свойства с инициализацией их начальных значений. Также мы объявляем метод модели addUser. В этом методе первым делом перекрываем стандартную реализацию нажатия кнопки (для нас это будет обычный submit формы), а дальше предварительно запустив валидатор, вызываем саму функцию отправки данных.
Последняя строка делает объект viewModel моделью контейнера userForm. Тоесть связывает их.

Вот и все. Ничего сложного нет.
Осталось лишь написать метод SendData(), который будет брать нужные данные из модели, и отправлять их в JSON-формате в контроллер:

   1:  function SendData() {
   2:      kendo.ui.progress($('#loader'), true);
   3:   
   4:      var json = JSON.stringify(viewModel.get('person'));
   5:   
   6:      $.ajax({
   7:          type: 'POST',
   8:          url: '/home/adduser',
   9:          data: json,
  10:          contentType: 'application/json; charset=utf-8',
  11:          success: function (result) {
  12:              if (result == 'True') {
  13:                  alert('Success! User added!');
  14:                  ResetViewModelValues();
  15:              };
  16:          },
  17:          complete: function () {
  18:              kendo.ui.progress($('#loader'), false);
  19:          }
  20:      });
  21:  };
  22:   
  23:  function ResetViewModelValues(){
  24:      viewModel.set("agreed", false);
  25:      viewModel.set("person.gender", "Male");
  26:      viewModel.set("person.firstName", "");
  27:      viewModel.set("person.lastName", "");
  28:  };

Обратите внимание на kendo.ui.progress(...) - это стандартная плюшка Kendo для визуализации хода выполнения процесса. Первый параметр указывает на контейнер, который будет для этого использован (у на это пустой див с id="loader"), а от второго параметра зависит, показать или убрать нужно индикатор. Как и положено для всех ajax-запросов, мы покажем индикатор перед выполнением запроса, и уберем после обработки его результатов.
При успешном выполнении запроса (при ответе "True") мы выведем сообщение об успешности операции, а потом "обнулим" все свойства модели. И так как связь обратная, таким образом мы очистим форму, готовясь к вводу следующей порции данных.

Вот теперь уже точно все! Можно проверять.
Вот у нас и сообщения валидатора:

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


_____
Исходники (Перешел на VS2012. Простите если чего не так)