Knockout MVC — Сила Knockout.js для ASP.NET MVC

в 3:32, , рубрики: .net, asp.net mvc, asp.net mvc 3, javascript, knockoutjs, mvvm, Веб-разработка, метки: , , , ,

knockoutmvcНа Хабре уже много писали про Knockout.js (раз, два, три, четыре, пять, видео). Для тех кто не в курсе, Knockout.js — это популярная JavaScript библиотека, позволяющая реализовать Model-View-View Model (MVVM) паттерн на клиенте. Освоить Knockout.js можно очень быстро — ведь есть система интерактивного обучения, куча живых примеров (можно потыкать и посмотреть исходный код) и прекрасная документация.

Очень часто Knockout.js используют в связке с ASP.NET MVC — ведь библиотека существенно упрощает написание клиентской логики. Однако, возникает много типичных проблем для клиент-серверной разработки: основную модель и часть логики её обработки приходится описывать как на клиенте (JavaScript), так и на сервере (C#/VB). Кроме того, есть рутинная часть, связанная с обращением клиента к серверным методам и передачи им модели для обработки. Но не стоит печалиться! Теперь у нас есть Knockout MVC — это .NET оболочка для Knockout.js, которая генерирует весь нужный JavaScript-код за нас. Нам остаётся только описать нашу модель на C# и в MVVM-стиле указать для каждого нужного html-элемента к какому свойству модели нужно привязаться (а можно указать и целые выражения — они будут транслированы в js). Таким образом, можно получить полноценное кроссбраузерное клиентское веб-приложение без единой строчки JavaScript!

А теперь немного подробнее

Модель

Модель пишется один раз на сервере (скажем, на C#). На стороне клиента весь JavaScript-код сгенерируется автоматически. Более того, мы можем описывать не только обычные свойства, но и не очень сложные выражения, которые будут транслированы в JavaScript. Причём это всё делается не только для основной модели, но и для её подмоделей (на чистом Knockout.js это делать совсем не так просто). Ну, например, описываем мы такую модель:

public class Item
{
public string FirstName { get; set; }
public string LastName { get; set; }

  public Expression<Func<string>> FullName()
  {
    return () => FirstName + " " + LastName;
  }
}

public class Model
{
  public List<Items> Items { get; set; } 
}

// ...

var model = new Model
  {
    Items = new List<Item>
      {
        new Item {FirstName = "Annabelle", LastName = "Arnie"},
        new Item {FirstName = "Bertie", LastName = "Brianna"},
        new Item {FirstName = "Charles", LastName = "Cayenne"},
      }
  };

а по ней автоматом сгенерируется вот такой JavaScript:

var viewModelJs = {"Items":[{"FirstName":"Annabelle","LastName":"Arnie"},{"FirstName":"Bertie","LastName":"Brianna"},{"FirstName":"Charles","LastName":"Cayenne"}]};
var viewModelMappingData = {
'Items': { create: function(options) {
var data = ko.mapping.fromJS(options.data);
data.FullName = ko.computed(function() { try { return this.FirstName()+' '+this.LastName()} catch(e) { return null; }  ;}, data);
return data;
}}};
var viewModel = ko.mapping.fromJS(viewModelJs, viewModelMappingData); 
ko.applyBindings(viewModel);

В данном примере полученный JavaScript относительно простой, но при чуть более сложной модели писать его ручками не так уж и приятно. А если вам захочется его порефакторить, то вам нужно будет синхронно и очень аккуратно менять JavaScript-модель и C#-модель (в нормальном приложении модель на C# всё равно понадобится для описания логики действий над этой самой моделью).

Привязки данных

Если вы работаете с ASP.NET MVC, то для описания представлений вы наверняка используете Razor View Engine. Если нет, то желательно хотя бы ознакомиться с функциональностью данного движка — он поднимает вёрстку сайта на новый уровень. Далее пойдут примеры представлений именно под Razor.
Ну, например я хочу создать TextBox, который будет содержать значение некоторого свойства A нашей модели. Мне достаточно написать:

@ko.Html.TextBox(m => m.A)

В скобочках указывается лямбда-выражение, которое нашей модели m сопоставляет свойство, к которому я бы хотел сделать привязку данных. Причём в данном случае мы пишем выражение на C#, а это означает что нам помогает IntelliSense:
screen-IntelliSense
А если я случайно напишу название неправильно, то узнаю об этом ещё на этапе компиляции — красная полосочка в студии покажет, что где-то я ошибся.
Ну, а теперь посмотрим пример посложнее. Например, я хочу, чтобы часть странички выводилась тогда и только тогда, когда в моей модели выполняется одновременно два каких-либо условия. Всё, что мне нужно написать:

@using (ko.If(model => model.Condition1 && model.Condition2))

Далее в фигурных скобочках указывается соответствующая часть представления. Просто, не правда ли? Ведь написав одну такую строчку, можно про неё сразу забыть — нам не нужно отслеживать состояние Condition1 и Condition2, писать js-код, который будет управлять видимостью элементов и т.п. А если мы заходим переименовать одно из условий или превратить его в computed-свойство, то магия рефакторинга сама поправит наше выражение (и, соответственно, исправится генерируемый JavaScript).
Knockout MVC содержит много полезных конструкций. Например, можно очень легко перебрать элементы какой-либо коллекции и для каждого элемента вывести чего-нибудь важное:

@using (var items = ko.Forearch(m => m.Items))

Полное описание всех доступных синтаксических конструкций можно посмотреть в документации.

Обращение к серверу

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

  • Нужно собрать данные о текущей модели и сериализовать их в строчку
  • Нужно привязать к нашей кнопке ajax-отсылку этой строчки на сервер
  • Нужно как-то описать логику получения данных с сервера и обновления визуальных элементов

Стоп! Ничего этого не нужно делать! Нужно просто написать вот такую строчку:

@ko.Html.Button("Some text", "FooAction", "Foo")

Вдумчивый читатель спросит: «А если я хочу передать в серверный метод некоторые параметры?» Нет ничего проще:

@ko.Html.Button("Some text", "FooAction", "Foo", new { index = 0, caption = "Knockout MVC"})

А вот так мы опишем соответствующий метод на сервере:

public class FooController : KnockoutController {  
  public ActionResult FooAction(FooModel model, int index, string caption)
  {
    model.FooAction(index, caption); // Тут будет основная логика
    return Json(model);
  }
}

Это всё! Ни одной лишней строчки писать не нужно! Можно вообще забыть о том, что у нас клиент-серверное приложение с сопутствующей технической рутиной — при разработке нужно концентрироваться только на логике модели! Посмотреть подробнее, как всё это работает, можно тут

Работа с контекстами

Что в Knockout.js сделано прям совсем не очень удобно — это работа с контекстами. Вложенные контексты возникают тогда, когда вы используете вложенные foreach и with конструкции. Если у вас один уровень вложенности — то всё хорошо. Но если вы пишете сложное приложение, в котором возникает 4-5 уровней вложенности, то иногда можно встретить конструкции вида $parents[2].name или $parentContext.$parentContext.$parentContext.$index. Воспринимать такой код достаточно сложно, а когда дело доходит до рефакторинга — то совсем становится грустно. В Knockout MVC писать намного проще — ведь теперь у каждого контекста есть своё имя! И теперь код выглядит так:

@using (var subModel = ko.With(m => m.SubModel))
  {
    using (var subSubModel = subModel.With(m => m.SubSubModel))
    {
      @subSubModel.Html.Span(m => ko.Model.A + " " + subModel.GetIndex() + " " + m.B)
    }
  }	

Вложенное выражение само превратится в:

	<span data-bind="text : $parents[1].A()+' '+$parentContext.$index()+' '+B()"></span>

Можно посмотреть пример.

Другие вкусные плюшки

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

  • Ленивая загрузка данных. Когда страничка грузится долго — это всегда раздражает. Иной раз недовольный пользователь может уйти с сайта, не дождавшись окончания загрузки. Особенно обидно, когда на самом деле страничка маленькая, а большую часть времени грузится JavaScript. Одна из возможностей Knockout MVC заключается в том, что можно в одном месте прописать небольшое слово Lazy — и о чудо — данные будут подгружаться дополнительным ajax-запросом после загрузки страницы. Понятно, что не так сложно и вручную написать соответствующий JavaScript-код, но всё равно приятно, когда такие решения достаются из коробки. Можно посмотреть как всё это выглядит на живом примере.
  • Дописывание пользовательского JavaScript. А вот ещё одна печальная ситуация: вы начали использовать очередной framework, вам всё нравится, но вот одной маленькой функциональности вам не хватает. И без использования framework-а вы бы эту функциональность легко дописали бы, но в контексте его использования это либо сделать невозможно, либо ну очень сложно. С Knockout MVC вам не нужно бояться таких ситуаций, ведь любую привязку данных, любую дополнительную функцию можно дописывать вручную к уже созданной автоматически сгенерированной модели — ваша свобода ничем не ограничена! И опять-таки есть живой пример
  • Совместимость с другими библиотеками. Если вы используете Knockout.js, то у вас уже по умолчанию есть совместимость с любой другой JavaScript-библиотекой (jQuery, Prototype и т.д.) или её плагином. Ну, например, вы можете посмотреть работу с jQuery.Validation

Резюме

Итак, подведём итог. Основные особенности Knockout MVC:

  • Написан на базе популярной js-библиоткеке Knockout.js
  • Полная интеграция с ASP.NET MVC3
  • Создание приложений с использованием паттерна Model-View-View Model (MVVM)
  • Все привязки данных (к конкретным свойствам и к выражениям) пишутся на C#/VB
  • Модель нужно описать только один раз на сервере
  • Для написания всех привязок данных работает IntelliSense
  • Можно вообще не писать JavaScript-код
  • А размер подключаемого js-кода очень мал (сжатый Knockout занимает порядка 40Kb)
  • Активное использование Ajax-все серверные методы отрабатывают без перезагрузки страниц
  • Краткий лаконичный синтаксис, который легко пишется и легко читается
  • Разработка очень проста и напоминает создание обычного приложения — можно вообще забыть о клиент-серверном взаимодействии
  • Зависимостей почти нету: только Knockout.js (само-собой) и jQuery (а он скорее всего и так у вас подключен)
  • Кроссбраузерность: IE 6+, Firefox 2+, Opera 10+, Chrome, Safari
  • Полная совместимость с другими библиотеками (jQuery, Prototype и всё, что вам захочется)
  • Детальная документация, подробное описание API, много живых примеров

Ссылки

Автор: DreamWalker

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js