На Пути К Полной Печати С Помощью Typescript, Swashbuckle И Autorest



Введение В этой статье рассматривается, как реализовать обмен типизированными сообщениями между серверной частью на основе веб-API ASP.NET и интерфейсной частью, созданной с использованием TypeScript. Это становится особенно важно при работе над большими проектами и еще важнее, если команда распределена.

Например, когда Back-End и Front-End разработчики работают из разных мест, в разных часовых поясах и не всегда есть возможность связаться и что-либо обсудить.

В этом случае отслеживание изменений — кропотливая работа, которая может быть чревата множеством малозаметных ошибок.

Для автора статьи, как человека, пришедшего во Front-End разработку из WPF и Silverlight, большой проблемой было отсутствие статической типизации.

Сколько раз вместо добавления «2» и «2» добавляли «2» и «Функция, возвращающая 2» или передавали объект DOM вместо его оболочки jQuery. Появление статических анализаторов кода, таких как JSLint, несколько облегчило проблему, но настоящим прорывом для нас, особенно в командной разработке, стал TypeScript.

На пути к полной печати с помощью TypeScript, Swashbuckle и AutoRest



Суть проблемы

TypeScript — это язык, позволяющий добиться статической типизации, хотя некоторые называют это «иллюзией» ( habrahabr.ru/post/258957 , habrahabr.ru/post/272055 ).

Интересно, что критики особенно выделяют работу с Back-End как обычно небезопасный по типу сценарий.

Однако суть проблемы в том, что при написании Front-End-приложений на JavaScript раньше и на TypeScript в настоящее время у нас нет таких инструментов для работы с метаданными и автогенерации клиентского кода, как когда-то было в WCF.

Метаданные

Если посмотреть на опыт WPF+WCF, то в этом плане все достаточно хорошо.

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

С другой стороны, они опять же попадают в некий клиент, который превращает их в типизированные.

Чтобы не писать руками такого клиента и не пытаться отлавливать многочисленные ошибки, существуют метаданные.

В мире .

NET в 90% случаев вообще не требуется никакой работы ни по их созданию, ни по их обработке.

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

Также в один клик вы генерируете клиента и в результате получаете обмен типизированными сообщениями.

При разработке одностраничного приложения на JavaScript/TypeScript WCF заменяется веб-API. Одно время было несколько удивительно, почему не было возможности генерировать метаданные для Web API «из коробки» (не считая справочных страниц метаданными).

Видимо ответ в том, что основным получателем данных от Web API был код JavaScript, набирать текст в котором не имеет смысла.

Однако теперь у нас TypeScript, а не JavaScript, и желание оперировать типизированными данными снова становится актуальным.

Сейчас очень популярным форматом метаданных является OpenAPI/Swagger. Неудивительно, что появляются возможности генерировать метаданные и документацию в этом формате.

Далее мы продемонстрируем процесс организации типизированного взаимодействия.

Короче говоря, мы выполним следующие шаги:

  1. Подключим и настроим библиотеку Swashbuckle
  2. Создание документации/метаданных
  3. Убедимся, что сгенерированный файл удобно хранить в системе контроля версий.

  4. Подключим АвтоРест
  5. Создадим клиентские модели
  6. Давайте попробуем их.



Головоломка

github.com/domaindrivendev/Swashbuckle Во-первых, мы хотим сгенерировать метаданные.

Итак, предположим, у нас есть Web API, и в нем есть контроллер, отвечающий за работу с сотрудниками.

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

/// <summary> /// Gets all employees. /// </summary> /// <remarks> /// Gets the list of all employees. /// </remarks> /// <returns> /// The list of employees. /// </returns> [Route("api/employees")] [HttpGet] public Employee[] GetEmployees() { return new[] { new Employee { Id = 1, Name = "John Doe" }, new Employee { Id = 2, Name = "Jane Doe" } }; }

Как видите, возвращается массив типизированных объектов типа «Сотрудник».

Запустив наш проект, мы можем запросить список сотрудников: http://localhost:1234/api/employees Давайте теперь подключим библиотеку Swashbuckle. Существует два пакета NuGet: Swashbuckle.Core и Swashbuckle. Разница между ними в том, что первый является ядром и содержит весь код, творящий чудеса, а второй, в свою очередь, является просто надстройкой, устанавливающей загрузчик, настраивающий ядро.

Это написано в документации github.com/domaindrivendev/Swashbuckle#iis-hosted Мы предпочитаем устанавливать Core и писать код конфигурации самостоятельно, потому что.

его удобнее использовать повторно позже.

Давайте установим его

PM> Install-Package Swashbuckle.Core

зарегистрируйтесь с помощью WebActivatorEx

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(FullyTypedExample.WebApi.SwaggerConfig), "RegisterGlobal")]

а также напишите код конфигурации

/// <summary> /// Configures Swagger. /// </summary> /// <param name="config"> /// The Swagger configuration. /// </param> public static void ConfigureSwagger(SwaggerDocsConfig config) { config.SingleApiVersion("v1", "FullyTypedExample.WebApi"); config.IncludeXmlComments(GetXmlCommentsPathForControllers()); config.IncludeXmlComments(GetXmlCommentsPathForModels()); config.GroupActionsBy(apiDescription => apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName); config.OrderActionGroupsBy(Comparer<string>.

Default); config.PrettyPrint(); }

Здесь все довольно просто: сначала мы задаем версию и заголовок нашего API. Далее мы говорим, что вам нужно включить комментарии xml для контроллеров и моделей.

Настраиваем порядок и группировку действий внутри документа swagger. Отдельно хотелось бы отметить опцию PrettyPrint. Он включает форматирование JSON для документа Swagger. Эта опция полезна для хранения документации в системе контроля версий в будущем и удобного просмотра ее изменений с помощью любой программы просмотра различий.

Теперь вы можете запустить проект и увидеть интерфейс Swagger. http://localhost:1234/swagger

На пути к полной печати с помощью TypeScript, Swashbuckle и AutoRest

Далее вы можете посмотреть сам документ swagger в виде JSON. http://localhost:1234/swagger/docs/v1 Теперь нам нужно поместить сформированную документацию в систему контроля версий.

Поскольку Swashbuckle использует под капотом Microsoft IApiExplorer, для создания файла swagger вам обязательно придется запустить веб-API (подробнее об этом здесь).

github.com/domaindrivendev/Swashbuckle/issues/559 ).

То есть каждый раз, когда вы захотите создать документацию, вам придется запускать веб-API и копировать swagger/docs в файл вручную.

Конечно, хочется чего-то более автоматизированного.

Мы решили эту проблему, запустив веб-API как самостоятельное приложение, отправив запрос на конечную точку, не поддерживающую Swagger, и записав ответ в файл.

Вот тут-то и пригодилось повторное использование кода конфигурации Swashbuckle. Выглядит все это примерно так:

/// <summary> /// Generate Swagger JSON document. /// </summary> /// <param name="filePath"> /// The file path where to write the generated document. /// </param> private static void GenerateSwaggerJson(string filePath) { // Start OWIN host using (TestServer server = TestServer.Create<WebApiHostStartup>()) { HttpResponseMessage response = server.CreateRequest("/swagger/docs/v1").

GetAsync().

Result; string result = response.Content.ReadAsStringAsync().

Result; string path = Path.GetFullPath(filePath); File.WriteAllText(path, result); } }

Давайте запустим все это сейчас:

nuget.exe restore ".

\FullyTypedExample.sln" "C:\Program Files (x86)\MSBuild\12.0\bin\MSBuild.exe" ".

\FullyTypedExample.WebApi.SelfHosted\FullyTypedExample.WebApi.SelfHosted.proj" /v:minimal ".

\FullyTypedExample.WebApi.SelfHosted\bin\Debug\FullyTypedExample.WebApi.SelfHosted.exe" --swagger "swagger.json"

В итоге мы получили swagger-документ в виде JSON-файла и занесли его в систему контроля версий.

Теперь Front-End-разработчики из нашей распределенной команды могут легко отслеживать изменения в конечных точках.

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



/// <summary> /// Gets employee by id. /// </summary> /// <param name="employeeId"> /// The employee id. /// </param> /// <remarks> /// Gets the employee by specified id. /// </remarks> /// <returns> /// The <see cref="Employee"/>.

/// </returns> [Route("api/employees/{employeeId:int}")] public Employee GetEmployeeById(int employeeId) { return this.GetEmployees().

SingleOrDefault(x => x.Id == employeeId); }

И мы заново сгенерировали swagger.json. Посмотрим, что изменилось

На пути к полной печати с помощью TypeScript, Swashbuckle и AutoRest

Как видите, для этого действия появилась документация, которую легко просмотреть с помощью средства просмотра различий.

Благодаря опции PrettyPrint он форматируется и легко читается.



Авторест

Итак, первая часть нашей задачи выполнена — метаданные у нас есть.

Как мы теперь можем генерировать клиентскую часть, т.е.

типы данных (полученных от сервера) на стороне клиента? Надо сказать, что можно сгенерировать и сам код для запроса Web API, просто это немного сложнее и требует более трудоемкой работы по настройке генераторов кода или написанию своего.

Также многое зависит от того, какие библиотеки (будь то jQuery, SuperAgent или даже новый экспериментальный Fetch API Developer.mozilla.org/en/docs/Web/API/Fetch_API ) и подходы (Promises, Rx и т. д.), которые вы используете в своем клиентском коде.

Существуют следующие варианты генерации кода: 1. Генератор кода Swagger github.com/swagger-api/swagger-codegen Официальный инструмент от команды Swagger написан на Java и требует соответствующей инфраструктуры.

Также может работать в Docker. Правда, в нем отсутствует генерация JavaScript и, тем более, TypeScript. Хотя если вам нужно сгенерировать код, например, на Java, это ваш выбор.

Нас это не устроило по понятным причинам.

2. Библиотека Swagger JS. github.com/swagger-api/swagger-js Также официальный инструмент от команды Swagger. Уже теплее.

Написан на JS и соответственно генерирует JS-код. Устанавливается через npm или Bower. Инфраструктура нас устраивает, но генерации типов здесь, к сожалению, нет. 3. Swagger для JS и Typescript Codegen github.com/wcandillon/swagger-js-codegen Проект был опубликован чуть позже, чем мы начали развивать этот подход. Возможно, в ближайшем будущем это будет самое подходящее решение.

4. Напишите свой собственный генератор кода велосипеда.

В общем, почему бы и нет? Но сначала мы решили, что попробуем AutoRest, а если он не взлетит, или нас не устроит своими возможностями, напишем свой, с блэкджеком и.

Ну вы поняли.

5. Авторест github.com/Azure/autorest И, наконец, AutoRest от команды Microsoft Azure. Сейчас актуальная версия - 0.15.0, и если честно, непонятно, считается ли это полноценным релизом или нет, но отметки Pre, как и на предыдущих, не видно.

В общем, здесь все просто, мы установили и сразу сгенерировали нужные нам файлы *.

d.ts. Итак, давайте пройдем последний этап нашего пути, используя этот инструмент. Подключите AutoRest через NuGet:

PM> Install-Package AutoRest

Пакет не установлен ни в каком конкретном проекте; ссылка на него добавляется для всего решения.



<Эxml version="1.0" encoding="utf-8"?> <packages> <package id="AutoRest" version="0.15.0" /> </packages>

В пакете содержится консольное приложение AutoRest.exe, которое, собственно, и выполняет генерацию.

Для начала мы используем следующий скрипт

nuget.exe restore ".

\FullyTypedExample.sln" ".

\packages\AutoRest.0.15.0\tools\AutoRest.exe" -Input "swagger.json" -CodeGenerator NodeJS move "Generated\models\index.d.ts" ".

\FullyTypedExample.HtmlApp\models.d.ts"

На вход мы предоставляем наш ранее сгенерированный swagger.json, а на выходе получаем models\index.d.ts — файл с моделями.

Копируем его в клиентский проект. Теперь в TypeScript у нас есть следующее описание модели:

/** * @class * Initializes a new instance of the Employee class. * @constructor * Represents the employee. * @member {number} id Gets or sets the employee identifier. * * @member {string} name Gets or sets the employee name. * */ export interface Employee { id: number; name: string; }

Давайте попробуем:

public makeRequest() { this.repository.getEmployees() .

then((employees) => { // Generate html using tempalte string this.table.innerHTML = employees.reduce<string>((acc, x) => { return `${acc}<tr><td>${x.id}</td><td>${x.name}</td></tr>`; }, ''); }); }

Здесь мы получаем доступ к полям идентификатора и имени модели.

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

Если мы попытаемся получить доступ к несуществующему полю возраста, наш код TS не скомпилируется.

Если поле в API, к которому мы обращались ранее, исчезнет, наш код снова не скомпилируется.

Если будут добавлены новые поля, мы сразу увидим это, используя тот же diff. Кроме того, мы автоматически получаем документацию JSDoc на основе метаданных.

В общем, все преимущества статической типизации очевидны.



Тип ответа

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

Например, это может быть полезно, если у вас есть устаревший код, работающий с нетипизированными наборами данных; или если вы вернете IHttpActionResult от контроллеров.

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



/// <summary> /// Gets all departments. /// </summary> /// <remarks> /// Gets the list of all departments. /// </remarks> /// <returns> /// The list of departments. /// </returns> [Route("api/departments")] [HttpGet] [ResponseType(typeof(DepartmentsResponse))] public DataSet GetDepartments() { var dataTable = new DataTable("Departments"); dataTable.Columns.Add("Id", typeof(int)); dataTable.Columns.Add("Name", typeof(string)); dataTable.Rows.Add(1, "IT"); dataTable.Rows.Add(2, "Sales"); var dataSet = new DataSet(); dataSet.Tables.Add(dataTable); return dataSet; }

получить типизированные модели на стороне клиента

/** * @class * Initializes a new instance of the Department class. * @constructor * Represents the department. * @member {number} id Gets or sets the department identifier. * * @member {string} name Gets or sets the department name. * */ export interface Department { id: number; name: string; }



Проблемы

Во-первых, размер файла models.d.ts со временем увеличивается.

Пока мы еще не работали над его разделением на несколько поддел, но это, несомненно, необходимо будет сделать.

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

Поле LAST_NAME из кода C# будет сгенерировано в Swagger как lasT_NAME, а в TypeScrpt — как lasTNAME.

/// <summary> /// Gets or sets the last name. /// </summary> [Required] // ReSharper disable once InconsistentNaming public string LAST_NAME { get; set; }



"lasT_NAME": { "description": "Gets or sets the last name.", "type": "string" }



export interface Employee { id: number; name: string; firstName: string; lasTNAME: string; }

Обратите внимание, что большинство мелких проблем легко решаются с помощью настройки и не заслуживают особого упоминания.



Заключение

Такой подход позволил нам организовать обмен типизированными сообщениями.

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

Приятным бонусом стало удобное ручное тестирование API со встроенным REST-клиентом и возможность генерировать полезную нагрузку на лету по схеме.

Использование этого подхода также помогло улучшить взаимодействие между разработчиками Back-End и Front-End. Рабочий пример можно увидеть здесь.

github.com/EBTrussia/full-typed-example Теги: #typescript #JavaScript #swagger #autorest #api #documentation #rest #restful api #client-server #JavaScript #.

NET #api #typescript

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.