1
На примере текстового редактора ASPxRichEdit
Опыт разработки сложных
клиент-серверных приложений на
TypeScript и ASP.NET
Developer Express Inc.
Роман Решетников
Ведущий разработчик команды ASP.NET
2
Go#Conferences
Продукты DevExpress - для множества платформ
3
Go#Conferences
Продукты DevExpress - для всех устройств
4
Go#Conferences
Продукты команды ASP.NET
5
Go#Conferences
Офисные контролы ASP.NET - ASPxSpreadSheet
6
Go#Conferences
ASPxRichEdit – процессор rich-text документов
 Работа со всеми популярными rich-text форматами
(DOCX, DOC, RTF, TXT, MHT, ODT, EPUB и т.д.)
 Нативная работа с rich-text документами без потери
содержимого
 Поддержка стилей, настроек секций, форматирования
текста и параграфов
 Печать, экспорт в PDF
 Знакомый UI вдохновленный продуктами MS Office
7
Go#Conferences
ASPxRichEdit – не очередной HTML редактор
Rich-Text Editor HTML Editor
Постраничная разбивка
Настраиваемые поля
Поддержка колонок
Header и Footer
Поддержка содержания
Стили документа
Форматирование текста
Поддержка параграфов
Принцип работы Полный контроль над
документом
Нативная браузерная
функциональность
Формат документа RTF, DOCX, DOC, ODT… HTML
8
Go#Conferences
RichEdit для десктопных платформ: WinForms, WPF
 30+MB исходного кода (базового для всех платформ)
 9000+ тестов базового кода
 Более 8 лет разработки
 Язык разработки C#
9
Go#Conferences
Выбираем инструменты
 TypeScript
 Jasmine + Chutzpah
Перенос логики на
клиент
Синхронизация с
сервером
Integration тесты
 Генерация кода
 Асинхронность в JS
 Сервис/хандлер вместо привычных
ASP.NET коллбеков
 Синхронизируем реквесты, версионность
 Тестируем синхронизацию
Как перенести код с сервера на клиент?
10
Инструменты
TypeScript – язык, позволяющий работать со сложной типизированной моделью
Тестирование клиентского кода
11
Go#Conferences
TypeScript для .NET разработчика
 Строгая типизация – необходимость для приложений со
сложными моделями
 700+ классов в TS проекте
 5000+ классов в .NET библиотеке
 Компиляция в «чистый» .JS
 Размер скомпиленного .js кода на 15% больше размера .ts.
 Простая отладка js кода даже без .map файлов
 Хорошая поддержка Visual Studio
 Простой синтаксис подобный C#
public class CharacterFormattingInfo {
public string FontName { get; set; }
public int Size { get; set; }
public bool Bold { get; set; }
public bool Italic { get; set; }
public StrikeoutType StrikeoutType { get; set; }
public UnderlineType UnderlineType { get; set; }
public bool AllCaps { get; set; }
…
export class CharacterFormattingInfo {
fontName: string;
size: number;
bold: boolean;
italic: boolean;
strikeoutType: StrikeoutType;
underlineType: UnderlineType;
allCaps: boolean;
…
C# код
TypeScript код
12
Go#Conferences
Но не все идеально в TypeScript 
 Отсутствует автоматическая линковка .ts файлов.
Компилятору надо подсказывать в каком порядке их
склеивать
 Сложно выделить .ts файлы с тестами за пределы
основного TypeScript кода
 TS классы не линкуются между проектами Visual Studio
 Медленный и не всегда корректный анализ ошибок
компиляции в VS (на больших проектах)
 Как вынести тесты в отдельный проект и не потерять связь
между типами:
<TypeScriptCompile Include="{Путь-до-базового-проекта}***.ts">
<Link>_referencesTS%(RecursiveDir)%(FileName)</Link>
</TypeScriptCompile>
13
Go#Conferences
Покрываем клиентский код тестами
 Jasmine – удобный framework для BDD unit тестирования
 Встроенный framework для быстрого создание mock функций
 Простое тестирование timeout функций
 «Читабельный» синтаксис
 Совместим с TypeScript
 Chutzpah – runner тестов в Visual Studio
 Простой запуск тестов прямо в VS
 Сам создает необходимое окружение для запуска теста в
браузере
 Поддерживает тесты написанные на TypeScript и Jasmine
14
Перенос логики на клиент
Перенос серверных типов на клиент
Очереди процессов вместо асинхронности
15
Go#Conferences
Быстрый отклик – когда все на клиенте
 Клиент работает независимо от сервера. Сервер только
уведомляется о новом состоянии модели
 Для продолжения работы с документом клиенту не нужен
ответ сервера
 UI не блокируется на время request/response
 Модель отправляется на клиент частями
 Вся модель разбивается на небольшие части – chunks
 Вместе с рендером страницы отправляется первый chunk
 Остальные chunks отсылаются на клиент асинхронно
16
Go#Conferences
Передача модели между сервером и клиентом
Сервер
Model→JSON
Response
JSON→Model
Клиент
Model→JSON
Request
JSON→Model
17
Go#Conferences
TextTemplates для генерации JSON конвертеров
RichEdit.Core DLL
TextTemplate Engine
• Type FindType(string
typeName)
• PropertyInfo[]
GetProperties()
Converters
• .NET type to JSON
• TS type to .NET
public class ParagraphFormattingInfo {
[JSONEnum(JSONParagraphFormattingProperty.Alignment)]
public ParagraphAlignment Alignment { get; set; }
[JSONEnum(JSONParagraphFormattingProperty.BackColor)]
public Color BackColor { get; set; }
[JSONEnum(JSONParagraphFormattingProperty.LeftIndent)]
public int LeftIndent { get; set; }
…
public class ParagraphFormattingInfoExporter : IExporter<ParagraphFormattingInfo> {
public void FillHashtable(Hashtable result, ParagraphFormattingInfo info) {
result.Add(0, (int)info.Alignment);
result.Add(1, info.BackColor.ToArgb());
result.Add(2, info.LeftIndent);
…
public void RestoreInfo(Hashtable source, ParagraphFormattingInfo info) {
info.Alignment = (ParagraphAlignment)source[0];
info.BackColor = Color.FromArgb(source[1]);
info.LeftIndent = Convert.ToInt32(source[2]);
…
18
Go#Conferences
Асинхронное выполнение длительных функций
 Длительные операции выполняются «асинхронно»
 Цикличные алгоритмы упаковываются в вызовы множества
«быстрых» примитивных функций
 Быстрые функции вызываются поочередно из setTimeout
функции с нулевым интервалом
runFormating() {
if(this.timerID)
return;
var asyncCalculating = () => {
if(this.formatter.formatNext())
this.timerID = setTimeout(asyncCalculating, 0);
else
this.timerID = null;
};
this.timerID = setTimeout(asyncCalculating, 0);
}
runFormatting() {
while(this.formatter.formatNext()) { }
}
19
Синхронизация с сервером
Модель стейтов и реквестов
Синхронизация изменений от нескольких пользователей
20
Go#Conferences
Сессионная модель сервера
 Реквесты делаются к HttpHandler, но не к странице
 Инстанс пользовательской сессии хранится в статичном
Dictionary. Ключ отсылается на клиент
 Сессия идентифицирует конкретную модель (документ), а
не пользователя
 Пользователя идентифицирует его ClientGuid (создается
на открытии страницы)
 Несколько человек могут открыть один и тот же документ
и работать с одной сессией
Клиент
• Тело реквеста
• ID рабочей сессии
• ID клиента
HttpHandler
• Поиск рабочей сессии по ID
• Передача ей ID клиента и
тела реквеста
Сессия
• Обработка реквеста
• Формирование
ответа
21
Go#Conferences
Очередь реквестов
 Реквесты на сервер не блокируют UI контрола
 Формирование очереди реквестов
 Сформированные реквесты добавляются очередь
 Очередь накапливается в течение N секунд
 Защита от коллизий
 Один и тот же реквест может быть отправлен несколько раз,
пока сервер не подтвердит его принятие
 Каждый реквест помечен своим идентификатором (версией
документа)
 Сервер не выполнит реквест с одним и тем же ID дважды
22
Go#Conferences
Путь к collaboration
 Две роли клиентов – Editor и Viewer
 Роли клиентов распределяет сервер
 Editor – клиент, последний изменивший актуальную
модель
 Viewer может стать редактором, если у него была
последняя версия модели и он ее поменял
 Viewer с устаревшей моделью – менять ее не может
 Рассинхронизация возможна только на коротком
интервале времени или при потере связи
23
Integration тестирование
Как протестировать корректную инициализацию клиента
Тестирование синхронизации клиента и сервера
PhantomJS
24
Go#Conferences
Клиентская часть повторяет серверную
Server-side
Client-side
25
Go#Conferences
Тестируем клиент и сервер одновременно
 Инициализация стартового состояния
 Тестирование применения изменений клиента на
сервере
 Тестирование применения изменений состояния
серверной модели к клиентской модели
 Тестирование возможных коллизий при синхронизации
состояний
26
Go#Conferences
Цикл интеграционного тестирования
Серверный код теста
Assert полученного состояния
клиентской модели
Применение клиентского
JSON к серверной модели
Assert нового состояния
серверной модели
PhantomJS с клиентским инстансом
Чтение инициализационного
JSON
Выполнение клиентских
операций
Запись в console клиентского
состояния модели
Серверный код теста
Создание серверного инстанса Заполнение модели
27
Go#Conferences
Пример интеграционного теста
[Test]
public void TestParagraphProperties() {
ChangeDocumentModel(); // предварительная настройка серверной модели
string[] clientResults = RunClientSide(
GetClientModelStateAction(), // записать Model в Output
ExecuteClientCommandAction() // выполнить клиентскую команду, записать
реквест в Output
);
// clientResults[0] – JSON с клиентским состоянием модели (инициализация)
// clientResults[1] – JSON с реквестом на изменение серверной модели
AssertClientModelState(clientResults[0]);
ApplyClientRequestToServerModel(clientResults[1]);
AssertServerModelState(DocumentModel);
}
28
Go#Conferences
Выбор инструментов
Синхронизация и
интеграционное тестирование
Миграция кода на клиент
 TypeScript – отличный
инструмент для сложных
клиентских проектов
 Jasmine+Chutzpah –
покрывают все задачи по
тестированию клиентского
кода
 PhantomJS – помогает
написать интеграционные
тесты
 Для переноса серверных
типов на клиент можно
использовать TextTemplates
 Сложные вычисления можно
разделить на атомарные
операции и выполнять
очередью из таймаутов
 Строгая типизация TypeScript
позволяет довольно просто
портировать логику на клиент
 Реквесты на сервер не
должны блокировать UI
 Контрол не должен зависеть
от ответов сервера
 Интеграционные тесты
позволяют протестировать
стартовую инициализацию
 Применение клиентских
изменений на сервере также
покрывается тестами
ASPxRichEdit – «толстые» клиент и сервер
29
Go#Conferences
Синхронизация моделей без коллизий
30
Go#Conferences
Спасибо за внимание!
Продукты DevExpress можно попробовать на нашем
стенде и на http://devexpress.com
Ведущий разработчик команды ASP.NET
Решетников Роман
Developer Express Inc.
Roman.Reshetnikov@devexpress.com

Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET

  • 1.
    1 На примере текстовогоредактора ASPxRichEdit Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET Developer Express Inc. Роман Решетников Ведущий разработчик команды ASP.NET
  • 2.
    2 Go#Conferences Продукты DevExpress -для множества платформ
  • 3.
    3 Go#Conferences Продукты DevExpress -для всех устройств
  • 4.
  • 5.
  • 6.
    6 Go#Conferences ASPxRichEdit – процессорrich-text документов  Работа со всеми популярными rich-text форматами (DOCX, DOC, RTF, TXT, MHT, ODT, EPUB и т.д.)  Нативная работа с rich-text документами без потери содержимого  Поддержка стилей, настроек секций, форматирования текста и параграфов  Печать, экспорт в PDF  Знакомый UI вдохновленный продуктами MS Office
  • 7.
    7 Go#Conferences ASPxRichEdit – неочередной HTML редактор Rich-Text Editor HTML Editor Постраничная разбивка Настраиваемые поля Поддержка колонок Header и Footer Поддержка содержания Стили документа Форматирование текста Поддержка параграфов Принцип работы Полный контроль над документом Нативная браузерная функциональность Формат документа RTF, DOCX, DOC, ODT… HTML
  • 8.
    8 Go#Conferences RichEdit для десктопныхплатформ: WinForms, WPF  30+MB исходного кода (базового для всех платформ)  9000+ тестов базового кода  Более 8 лет разработки  Язык разработки C#
  • 9.
    9 Go#Conferences Выбираем инструменты  TypeScript Jasmine + Chutzpah Перенос логики на клиент Синхронизация с сервером Integration тесты  Генерация кода  Асинхронность в JS  Сервис/хандлер вместо привычных ASP.NET коллбеков  Синхронизируем реквесты, версионность  Тестируем синхронизацию Как перенести код с сервера на клиент?
  • 10.
    10 Инструменты TypeScript – язык,позволяющий работать со сложной типизированной моделью Тестирование клиентского кода
  • 11.
    11 Go#Conferences TypeScript для .NETразработчика  Строгая типизация – необходимость для приложений со сложными моделями  700+ классов в TS проекте  5000+ классов в .NET библиотеке  Компиляция в «чистый» .JS  Размер скомпиленного .js кода на 15% больше размера .ts.  Простая отладка js кода даже без .map файлов  Хорошая поддержка Visual Studio  Простой синтаксис подобный C# public class CharacterFormattingInfo { public string FontName { get; set; } public int Size { get; set; } public bool Bold { get; set; } public bool Italic { get; set; } public StrikeoutType StrikeoutType { get; set; } public UnderlineType UnderlineType { get; set; } public bool AllCaps { get; set; } … export class CharacterFormattingInfo { fontName: string; size: number; bold: boolean; italic: boolean; strikeoutType: StrikeoutType; underlineType: UnderlineType; allCaps: boolean; … C# код TypeScript код
  • 12.
    12 Go#Conferences Но не всеидеально в TypeScript   Отсутствует автоматическая линковка .ts файлов. Компилятору надо подсказывать в каком порядке их склеивать  Сложно выделить .ts файлы с тестами за пределы основного TypeScript кода  TS классы не линкуются между проектами Visual Studio  Медленный и не всегда корректный анализ ошибок компиляции в VS (на больших проектах)  Как вынести тесты в отдельный проект и не потерять связь между типами: <TypeScriptCompile Include="{Путь-до-базового-проекта}***.ts"> <Link>_referencesTS%(RecursiveDir)%(FileName)</Link> </TypeScriptCompile>
  • 13.
    13 Go#Conferences Покрываем клиентский кодтестами  Jasmine – удобный framework для BDD unit тестирования  Встроенный framework для быстрого создание mock функций  Простое тестирование timeout функций  «Читабельный» синтаксис  Совместим с TypeScript  Chutzpah – runner тестов в Visual Studio  Простой запуск тестов прямо в VS  Сам создает необходимое окружение для запуска теста в браузере  Поддерживает тесты написанные на TypeScript и Jasmine
  • 14.
    14 Перенос логики наклиент Перенос серверных типов на клиент Очереди процессов вместо асинхронности
  • 15.
    15 Go#Conferences Быстрый отклик –когда все на клиенте  Клиент работает независимо от сервера. Сервер только уведомляется о новом состоянии модели  Для продолжения работы с документом клиенту не нужен ответ сервера  UI не блокируется на время request/response  Модель отправляется на клиент частями  Вся модель разбивается на небольшие части – chunks  Вместе с рендером страницы отправляется первый chunk  Остальные chunks отсылаются на клиент асинхронно
  • 16.
    16 Go#Conferences Передача модели междусервером и клиентом Сервер Model→JSON Response JSON→Model Клиент Model→JSON Request JSON→Model
  • 17.
    17 Go#Conferences TextTemplates для генерацииJSON конвертеров RichEdit.Core DLL TextTemplate Engine • Type FindType(string typeName) • PropertyInfo[] GetProperties() Converters • .NET type to JSON • TS type to .NET public class ParagraphFormattingInfo { [JSONEnum(JSONParagraphFormattingProperty.Alignment)] public ParagraphAlignment Alignment { get; set; } [JSONEnum(JSONParagraphFormattingProperty.BackColor)] public Color BackColor { get; set; } [JSONEnum(JSONParagraphFormattingProperty.LeftIndent)] public int LeftIndent { get; set; } … public class ParagraphFormattingInfoExporter : IExporter<ParagraphFormattingInfo> { public void FillHashtable(Hashtable result, ParagraphFormattingInfo info) { result.Add(0, (int)info.Alignment); result.Add(1, info.BackColor.ToArgb()); result.Add(2, info.LeftIndent); … public void RestoreInfo(Hashtable source, ParagraphFormattingInfo info) { info.Alignment = (ParagraphAlignment)source[0]; info.BackColor = Color.FromArgb(source[1]); info.LeftIndent = Convert.ToInt32(source[2]); …
  • 18.
    18 Go#Conferences Асинхронное выполнение длительныхфункций  Длительные операции выполняются «асинхронно»  Цикличные алгоритмы упаковываются в вызовы множества «быстрых» примитивных функций  Быстрые функции вызываются поочередно из setTimeout функции с нулевым интервалом runFormating() { if(this.timerID) return; var asyncCalculating = () => { if(this.formatter.formatNext()) this.timerID = setTimeout(asyncCalculating, 0); else this.timerID = null; }; this.timerID = setTimeout(asyncCalculating, 0); } runFormatting() { while(this.formatter.formatNext()) { } }
  • 19.
    19 Синхронизация с сервером Модельстейтов и реквестов Синхронизация изменений от нескольких пользователей
  • 20.
    20 Go#Conferences Сессионная модель сервера Реквесты делаются к HttpHandler, но не к странице  Инстанс пользовательской сессии хранится в статичном Dictionary. Ключ отсылается на клиент  Сессия идентифицирует конкретную модель (документ), а не пользователя  Пользователя идентифицирует его ClientGuid (создается на открытии страницы)  Несколько человек могут открыть один и тот же документ и работать с одной сессией Клиент • Тело реквеста • ID рабочей сессии • ID клиента HttpHandler • Поиск рабочей сессии по ID • Передача ей ID клиента и тела реквеста Сессия • Обработка реквеста • Формирование ответа
  • 21.
    21 Go#Conferences Очередь реквестов  Реквестына сервер не блокируют UI контрола  Формирование очереди реквестов  Сформированные реквесты добавляются очередь  Очередь накапливается в течение N секунд  Защита от коллизий  Один и тот же реквест может быть отправлен несколько раз, пока сервер не подтвердит его принятие  Каждый реквест помечен своим идентификатором (версией документа)  Сервер не выполнит реквест с одним и тем же ID дважды
  • 22.
    22 Go#Conferences Путь к collaboration Две роли клиентов – Editor и Viewer  Роли клиентов распределяет сервер  Editor – клиент, последний изменивший актуальную модель  Viewer может стать редактором, если у него была последняя версия модели и он ее поменял  Viewer с устаревшей моделью – менять ее не может  Рассинхронизация возможна только на коротком интервале времени или при потере связи
  • 23.
    23 Integration тестирование Как протестироватькорректную инициализацию клиента Тестирование синхронизации клиента и сервера PhantomJS
  • 24.
  • 25.
    25 Go#Conferences Тестируем клиент исервер одновременно  Инициализация стартового состояния  Тестирование применения изменений клиента на сервере  Тестирование применения изменений состояния серверной модели к клиентской модели  Тестирование возможных коллизий при синхронизации состояний
  • 26.
    26 Go#Conferences Цикл интеграционного тестирования Серверныйкод теста Assert полученного состояния клиентской модели Применение клиентского JSON к серверной модели Assert нового состояния серверной модели PhantomJS с клиентским инстансом Чтение инициализационного JSON Выполнение клиентских операций Запись в console клиентского состояния модели Серверный код теста Создание серверного инстанса Заполнение модели
  • 27.
    27 Go#Conferences Пример интеграционного теста [Test] publicvoid TestParagraphProperties() { ChangeDocumentModel(); // предварительная настройка серверной модели string[] clientResults = RunClientSide( GetClientModelStateAction(), // записать Model в Output ExecuteClientCommandAction() // выполнить клиентскую команду, записать реквест в Output ); // clientResults[0] – JSON с клиентским состоянием модели (инициализация) // clientResults[1] – JSON с реквестом на изменение серверной модели AssertClientModelState(clientResults[0]); ApplyClientRequestToServerModel(clientResults[1]); AssertServerModelState(DocumentModel); }
  • 28.
    28 Go#Conferences Выбор инструментов Синхронизация и интеграционноетестирование Миграция кода на клиент  TypeScript – отличный инструмент для сложных клиентских проектов  Jasmine+Chutzpah – покрывают все задачи по тестированию клиентского кода  PhantomJS – помогает написать интеграционные тесты  Для переноса серверных типов на клиент можно использовать TextTemplates  Сложные вычисления можно разделить на атомарные операции и выполнять очередью из таймаутов  Строгая типизация TypeScript позволяет довольно просто портировать логику на клиент  Реквесты на сервер не должны блокировать UI  Контрол не должен зависеть от ответов сервера  Интеграционные тесты позволяют протестировать стартовую инициализацию  Применение клиентских изменений на сервере также покрывается тестами ASPxRichEdit – «толстые» клиент и сервер
  • 29.
  • 30.
    30 Go#Conferences Спасибо за внимание! ПродуктыDevExpress можно попробовать на нашем стенде и на http://devexpress.com Ведущий разработчик команды ASP.NET Решетников Роман Developer Express Inc. Roman.Reshetnikov@devexpress.com