|
|
@@ -24,13 +24,13 @@
|
|
|
|
|
|
Одним из таких общепринятых стандартов является **REST**, что расшифровывается как **Representational State Transfer** — передача репрезентативного состояния.
|
|
|
|
|
|
-**REST API** — это не один конкретный протокол взаимодействия, а архитектурный стиль. Он описывает, как разработчику следует спроектировать интерфейс для взаимодействия своего приложения с другими. Если продолжить аналогию с естественным языком, то REST API описывает грамматику. Принципы и ограничения REST API были определены в 2000 году Роем Филдингом, одним из создателей протокола HTTP. Говорят, что *если интерфейс взаимодействия приложения соответствует принципам REST API, он является RESTful*.
|
|
|
+**REST API** — это не один конкретный протокол взаимодействия, а архитектурный стиль. Он описывает, как разработчику следует спроектировать интерфейс для взаимодействия своего приложения с другими. Если продолжить аналогию с естественным языком, то **REST API** описывает грамматику. Принципы и ограничения **REST API** были определены в 2000 году Роем Филдингом, одним из создателей протокола **HTTP**. Говорят, что *если интерфейс взаимодействия приложения соответствует принципам REST API, он является RESTful*.
|
|
|
|
|
|
-Для доступа клиентских приложений к данным, хранящимся на сервере, в REST API используются такие методы как **POST**, **GET**, **PUT** и **DELETE**. При этом обмен сообщениями осуществляется обычно по протоколу HTTP(S).
|
|
|
+Для доступа клиентских приложений к данным, хранящимся на сервере, в **REST API** используются такие методы как **POST**, **GET**, **PUT** и **DELETE**. При этом обмен сообщениями осуществляется обычно по протоколу **HTTP(S)**.
|
|
|
|
|
|
-Главная особенность REST API — обмен сообщениями без сохранения состояния. Каждое сообщение самодостаточное и содержит всю информацию, необходимую для его обработки. Сервер не хранит результаты предыдущих сессий с клиентскими приложениями. Это обеспечивает гибкость и масштабируемость серверной части, позволяет поддерживать асинхронные взаимодействия и реализовывать алгоритмы обработки любой сложности. Кроме того, такой формат взаимодействия является универсальным — он не зависит от технологий, используемых на клиенте и на сервере, и не привязывает разработчиков к определенному провайдеру.
|
|
|
+Главная особенность **REST API** — обмен сообщениями без сохранения состояния. Каждое сообщение самодостаточное и содержит всю информацию, необходимую для его обработки. Сервер не хранит результаты предыдущих сессий с клиентскими приложениями. Это обеспечивает гибкость и масштабируемость серверной части, позволяет поддерживать асинхронные взаимодействия и реализовывать алгоритмы обработки любой сложности. Кроме того, такой формат взаимодействия является универсальным — он не зависит от технологий, используемых на клиенте и на сервере, и не привязывает разработчиков к определенному провайдеру.
|
|
|
|
|
|
-Из-за того, что приходится каждый раз заново передавать все данные для обработки запроса, объем сообщений увеличивается. Чтобы сохранить при этом высокую скорость обмена, данные передаются в максимально сжатом формате. Чаще всего REST API использует формат **JSON**, более лаконичный чем **XML**.
|
|
|
+Из-за того, что приходится каждый раз заново передавать все данные для обработки запроса, объем сообщений увеличивается. Чтобы сохранить при этом высокую скорость обмена, данные передаются в максимально сжатом формате. Чаще всего **REST API** использует формат **JSON**, более лаконичный чем **XML**.
|
|
|
|
|
|
### Как это работает
|
|
|
|
|
|
@@ -49,9 +49,9 @@
|
|
|
|
|
|
Реже используются методы **LIST**, **UPDATE**, **PATCH** и др.
|
|
|
|
|
|
->Так как в большинстве случаев REST API предназначен для получения данных из базы, то эти методы являются аналогами **CRUD** для данных.
|
|
|
+>Так как в большинстве случаев **REST API** предназначен для получения данных из базы, то эти методы являются аналогами **CRUD** для данных.
|
|
|
|
|
|
-Допустим, ваш интернет-магазин работает со сторонней службой доставки и обращается к ее серверу с помощью методов REST API.
|
|
|
+Допустим, ваш интернет-магазин работает со сторонней службой доставки и обращается к ее серверу с помощью методов **REST API**.
|
|
|
|
|
|
* Чтобы передать в службу доставки информацию о новом заказе, отправляется запрос с методом **POST**.
|
|
|
* Когда диспетчер службы доставки передает заказ курьеру, статус заказа меняется — для этого используется метод **POST** или **PUT**.
|
|
|
@@ -61,13 +61,13 @@
|
|
|
|
|
|
### Структура запроса
|
|
|
|
|
|
-Запрос REST API от клиента к серверу всегда состоит из следующих элементов:
|
|
|
+Запрос **REST API** от клиента к серверу всегда состоит из следующих элементов:
|
|
|
|
|
|
* **Конечная точка (endpoint)** — адрес, по которому отправляется запрос.
|
|
|
|
|
|
Один и тот же объект (ресурс) может иметь несколько конечных точек.
|
|
|
|
|
|
- Например, чтобы отправить запрос на сервер службы доставки Best Delivery, может использоваться конечная точка best-delivery.com/orders. Чтобы посмотреть список заказов, можно использовать конечную точку best-delivery.com/orders/list, а чтобы разместить новый заказ — best-delivery.com/orders/create.
|
|
|
+ Например, чтобы отправить запрос на сервер службы доставки `Best Delivery`, может использоваться конечная точка `best-delivery.com/orders`. Чтобы посмотреть список заказов, можно использовать конечную точку `best-delivery.com/orders/list`, а чтобы разместить новый заказ — `best-delivery.com/orders/create`.
|
|
|
|
|
|
* **Параметры** — делятся на параметры пути и параметры запроса.
|
|
|
|
|
|
@@ -96,60 +96,55 @@
|
|
|
|
|
|
### Структура ответа
|
|
|
|
|
|
-После выполнения REST API запроса сервер вернет клиентскому приложению ответ. Он включает **код ответа**, **заголовки** и **тело ответа**.
|
|
|
+После выполнения **REST API** запроса сервер вернет клиентскому приложению ответ. Он включает **код ответа**, **заголовки** и **тело ответа**.
|
|
|
|
|
|
* Как и в запросе, **заголовки** в ответе также определяют формат передаваемых данных, спецификацию и версию протокола обмена, и другие сведения, которые помогут клиентскому приложению правильно прочитать и понять ответ.
|
|
|
* **Тело ответа** — это информация, которую запрашивал клиент. Ответ тоже чаще всего передается в формате **JSON**. Но тело ответа может быть и пустым.
|
|
|
* **Код ответа** — это признак успешности выполнения запроса. Для унификации используются стандартные коды ответа (HTTP). Они представляют собой трехзначные числа. Ответы, начинающиеся с цифры 1, обозначаются 1xx, и т.п.
|
|
|
|
|
|
-Ответы вида 1хх — информационные.
|
|
|
+Ответы вида `1хх` — информационные.
|
|
|
|
|
|
-Ответы вида 2хх говорят об успешном выполнении запроса. Например:
|
|
|
+Ответы вида `2хх` говорят об успешном выполнении запроса. Например:
|
|
|
|
|
|
-* 200 – ОК. Если клиентом были запрошены какие-либо данные, то они находятся в заголовке или теле сообщения.
|
|
|
-* 201 – OK. Создан новый ресурс.
|
|
|
+* `200` – ОК. Если клиентом были запрошены какие-либо данные, то они находятся в заголовке или теле сообщения.
|
|
|
+* `201` – OK. Создан новый ресурс.
|
|
|
|
|
|
-Ответы вида 3xx обозначают перенаправление или необходимость уточнения. Например:
|
|
|
+Ответы вида `3xx` обозначают перенаправление или необходимость уточнения. Например:
|
|
|
|
|
|
-* 300 — на отправленный запрос есть несколько вариантов ответа. Чтобы получить нужный вариант, клиент должен уточнить запрос.
|
|
|
-* 301 — запрашиваемый адрес перемещен.
|
|
|
-* 307 — запрашиваемый адрес временно перемещен.
|
|
|
+* `300` — на отправленный запрос есть несколько вариантов ответа. Чтобы получить нужный вариант, клиент должен уточнить запрос.
|
|
|
+* `301` — запрашиваемый адрес перемещен.
|
|
|
+* `307` — запрашиваемый адрес временно перемещен.
|
|
|
|
|
|
-Ответы вида 4хх говорят о том, что при выполнении запроса возникла ошибка, и это ошибка на стороне клиента. Например:
|
|
|
+Ответы вида `4хх` говорят о том, что при выполнении запроса возникла ошибка, и это ошибка на стороне клиента. Например:
|
|
|
|
|
|
-* 400 – Bad Request. Запрос некорректный.
|
|
|
-* 401 – Unauthorized. Запрос требует аутентификации пользователя.
|
|
|
-* 403 – Forbidden. Доступ к сервису запрещен.
|
|
|
-* 404 – Not found. Ресурс не найден.
|
|
|
+* `400` – Bad Request. Запрос некорректный.
|
|
|
+* `401` – Unauthorized. Запрос требует аутентификации пользователя.
|
|
|
+* `403` – Forbidden. Доступ к сервису запрещен.
|
|
|
+* `404` – Not found. Ресурс не найден.
|
|
|
|
|
|
-Ответы вида 5хх говорят об ошибке на стороне сервера. Например:
|
|
|
+Ответы вида `5хх` говорят об ошибке на стороне сервера. Например:
|
|
|
|
|
|
-* 503 — сервис недоступен.
|
|
|
-* 504 — таймаут (превышено допустимое время обработки запроса).
|
|
|
+* `503` — сервис недоступен.
|
|
|
+* `504` — таймаут (превышено допустимое время обработки запроса).
|
|
|
|
|
|
### Описание API
|
|
|
|
|
|
-Как уже говорилось выше, REST API — это архитектурный подход, а не конкретный протокол. Каждое приложение или сервис может иметь свой API, разработанный в соответствии со стандартами и лучшими отраслевыми практиками. Такая свобода обеспечивает большую гибкость и широту возможностей. Но чтобы сторонние разработчики могли воспользоваться разнообразными возможностями вашего сервиса, API должен быть хорошо задокументирован.
|
|
|
+Как уже говорилось выше, **REST API** — это архитектурный подход, а не конкретный протокол. Каждое приложение или сервис может иметь свой **API**, разработанный в соответствии со стандартами и лучшими отраслевыми практиками. Такая свобода обеспечивает большую гибкость и широту возможностей. Но чтобы сторонние разработчики могли воспользоваться разнообразными возможностями вашего сервиса, **API** должен быть хорошо задокументирован.
|
|
|
|
|
|
-Отраслевым стандартом описания REST API является спецификация документирования **OpenAPI** (ранее она называлась **Swagger**). В настоящее время используется версия OpenAPI Specification 3.0, а также немного устаревшая, но еще актуальная версия 2.0.
|
|
|
+Отраслевым стандартом описания **REST API** является спецификация документирования **OpenAPI** (ранее она называлась **Swagger**). В настоящее время используется версия **OpenAPI Specification 3.0**, а также немного устаревшая, но еще актуальная версия 2.0.
|
|
|
|
|
|
### Альтернативы и применение REST API
|
|
|
|
|
|
-REST API — самый популярный сегодня стандарт взаимодействия приложений, хотя не первый и не единственный.
|
|
|
+**REST API** — самый популярный сегодня стандарт взаимодействия приложений, хотя не первый и не единственный.
|
|
|
|
|
|
-Первым широко распространенным стандартом стал **SOAP** (Simple Object Access Protocol). Но SOAP-сообщения довольно громоздки (как минимум потому, что используют формат XML, а не более лаконичный JSON), что стало особенно неудобно с распространением мобильного интернета. SOAP изначально предназначался для описания вызова удаленных процедур — RPC (Remote Procedure Call), когда клиентское приложение выполняет функцию или процедуру на сервере, а сервер отправляет результат обратно клиенту.
|
|
|
+Первым широко распространенным стандартом стал **SOAP** (Simple Object Access Protocol). Но **SOAP**-сообщения довольно громоздки (как минимум потому, что используют формат **XML**, а не более лаконичный **JSON**), что стало особенно неудобно с распространением мобильного интернета. **SOAP** изначально предназначался для описания вызова удаленных процедур — **RPC** (Remote Procedure Call), когда клиентское приложение выполняет функцию или процедуру на сервере, а сервер отправляет результат обратно клиенту.
|
|
|
|
|
|
## Open API (Swagger)
|
|
|
|
|
|
-**OpenApi (Swagger)** - это фреймворк для спецификации *RESTful API*. Его прелесть заключается в том, что он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы.
|
|
|
+**OpenApi (Swagger)** - это фреймворк для спецификации **RESTful API**. Его прелесть заключается в том, что он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы.
|
|
|
|
|
|
**ASP.NET** поддерживает генерацию спецификации и конечной точки для использования этой спецификации.
|
|
|
|
|
|
-Для поддержки **Swagger** нужно установить **NuGet** пакеты:
|
|
|
-
|
|
|
-* Microsoft.OpenApi
|
|
|
-* Swashbuckle.AspNetCore
|
|
|
-
|
|
|
>[Подробнее про OpenApi (Swagger)](https://learn.microsoft.com/ru-ru/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-7.0)
|
|
|
|
|
|
## Создание сервера ASP.NET Core.
|
|
|
@@ -158,7 +153,7 @@ REST API — самый популярный сегодня стандарт в
|
|
|
|
|
|
>Взято из [официальной документации Microsoft](https://learn.microsoft.com/ru-ru/aspnet/core/fundamentals/routing?view=aspnetcore-7.0#endpoints), поэтому язык несколько тяжеловесный.
|
|
|
|
|
|
-Маршрутизация обеспечивает сопоставление входящих HTTP-запросов и их распределение по исполняемым конечным точкам приложения. [Конечные точки](#конечные-точки) — это блоки исполняемого кода обработки запросов приложения. Конечные точки определяются в приложении и настраиваются при его запуске. Процесс сопоставления конечных точек может извлекать значения из URL-адреса запроса и предоставлять эти значения для обработки запроса.
|
|
|
+Маршрутизация обеспечивает сопоставление входящих **HTTP**-запросов и их распределение по исполняемым конечным точкам приложения. [Конечные точки](#конечные-точки) — это блоки исполняемого кода обработки запросов приложения. Конечные точки определяются в приложении и настраиваются при его запуске. Процесс сопоставления конечных точек может извлекать значения из URL-адреса запроса и предоставлять эти значения для обработки запроса.
|
|
|
|
|
|
>Напомню состав URL-адреса
|
|
|
>
|
|
|
@@ -167,7 +162,7 @@ REST API — самый популярный сегодня стандарт в
|
|
|
>```
|
|
|
>где:
|
|
|
* `http://` - протокол запроса, обычно используются **HTTP** и/или **HTTPS**
|
|
|
-* `доменное.имя:порт` - адрес сервера, порт можно не указывать
|
|
|
+* `доменное.имя:порт` - адрес сервера, порт по-умолчанию `80` для HTTP или `443` для HTTPS
|
|
|
* `/какой/то/путь` - путь (PATH) запроса
|
|
|
* `?ключ=значение&ещё=чтонибудь` - параметры запроса (querystring) в виде списка `ключ=значение`, разделитель `&`. Могут отсутствовать, начинаются со знака `?`.
|
|
|
|
|
|
@@ -185,7 +180,7 @@ var builder = WebApplication.CreateBuilder(args);
|
|
|
var app = builder.Build();
|
|
|
|
|
|
/*
|
|
|
-тут запускаются так называемые "middleware": swagger, авторизация и т.п. процедуры, которые должны быть выполнены до обработки конечных точек
|
|
|
+тут запускаются так называемые "middleware": swagger, авторизация и т.п. процедуры, которые должны быть выполнены ДО обработки конечных точек
|
|
|
|
|
|
они начинаются с "app.Use"
|
|
|
*/
|
|
|
@@ -200,9 +195,9 @@ app.Run();
|
|
|
* При отправке HTTP-запроса **GET** в корневой URL-адрес `/`:
|
|
|
* Выполняется делегат запроса.
|
|
|
* В ответ HTTP записывается "Hello World!".
|
|
|
-* Если метод запроса не является **GET** или если корневой URL-адрес не `/`, сопоставление маршрута не выполняется и возвращается сообщение об ошибке HTTP 404.
|
|
|
+* Если метод запроса не является **GET** или если корневой URL-адрес не `/`, сопоставление маршрута не выполняется и возвращается сообщение об ошибке HTTP `404`.
|
|
|
|
|
|
-Маршрутизация использует пару ПО промежуточного слоя: UseRouting и UseEndpoints.
|
|
|
+Маршрутизация использует пару ПО промежуточного слоя: **UseRouting** и **UseEndpoints**.
|
|
|
|
|
|
* **UseRouting** добавляет соответствие маршрута в конвейер ПО промежуточного слоя. Это ПО промежуточного слоя обращается к набору конечных точек, определенных в приложении, и выбирает наиболее подходящее на основе запроса.
|
|
|
* **UseEndpoints** добавляет выполнение конечной точки в конвейер ПО промежуточного слоя. Он запускает делегат, связанный с выбранной конечной точкой.
|
|
|
@@ -231,12 +226,12 @@ app.MapGet("/", () => "Hello World!");
|
|
|
|
|
|
#### Конечные точки
|
|
|
|
|
|
-Для определения конечной точки используется метод **MapGet** (а также **MapPost**, **MapPut**, **MapDelete** и т.п, т.е. после префикса Map указывается метод HTTP). Конечная точка — это то, что можно:
|
|
|
+Для определения конечной точки используется метод **MapGet** (а также **MapPost**, **MapPut**, **MapDelete** и т.п, т.е. после префикса **Map** указывается метод HTTP). Конечная точка — это то, что можно:
|
|
|
|
|
|
* выбрать путем сопоставления URL-адреса и метода HTTP;
|
|
|
* выполнить путем запуска делегата.
|
|
|
|
|
|
-Конечные точки, которые могут быть сопоставлены и выполнены приложением, настраиваются в UseEndpoints. Например, **MapGet**, **MapPost** и [аналогичные методы](https://learn.microsoft.com/ru-ru/dotnet/api/microsoft.aspnetcore.builder.endpointroutebuilderextensions?view=aspnetcore-7.0) подключают делегаты запросов к системе маршрутизации. Для подключения функций платформы **ASP.NET Core** к системе маршрутизации можно использовать дополнительные методы.
|
|
|
+Конечные точки, которые могут быть сопоставлены и выполнены приложением, настраиваются в **UseEndpoints**. Например, **MapGet**, **MapPost** и [аналогичные методы](https://learn.microsoft.com/ru-ru/dotnet/api/microsoft.aspnetcore.builder.endpointroutebuilderextensions?view=aspnetcore-7.0) подключают делегаты запросов к системе маршрутизации. Для подключения функций платформы **ASP.NET Core** к системе маршрутизации можно использовать дополнительные методы.
|
|
|
|
|
|
* MapRazorPages для Razor Pages
|
|
|
* MapControllers для контроллеров
|
|
|
@@ -255,12 +250,12 @@ app.MapGet(
|
|
|
|
|
|
Любой URL-путь, начинающийся с `/hello/`, после которого следует набор буквенных символов. `:alpha` применяет ограничение маршрута, которое соответствует только буквенным символам. Ограничения маршрута описаны далее в этой статье.
|
|
|
|
|
|
-Второй сегмент URL-пути, {name:alpha}:
|
|
|
+Второй сегмент URL-пути, `{name:alpha}`:
|
|
|
|
|
|
* привязан к параметру `name`;
|
|
|
* Записывается и хранится в [HttpRequest.RouteValues](https://learn.microsoft.com/ru-ru/dotnet/api/microsoft.aspnetcore.http.httprequest.routevalues?view=aspnetcore-7.0).
|
|
|
|
|
|
-Если человеческим языком, то в пути запроса может быть один или несколько параметров, которые автоматически добавляются в параметры делегата (лямбда-функции)
|
|
|
+Если человеческим языком, то в **пути** запроса может быть один или несколько параметров, которые автоматически добавляются в параметры делегата (лямбда-функции)
|
|
|
|
|
|
#### Ограничения маршрута
|
|
|
|
|
|
@@ -268,30 +263,30 @@ app.MapGet(
|
|
|
|
|
|
>Предупреждение
|
|
|
>
|
|
|
->Не используйте ограничения для проверки входных данных. Если для проверки входных данных используются ограничения, недопустимые входные данные приводят к ошибке 404 ("Не найдено"). Недопустимые входные данные должны привести к ошибке 400 ("Неверный запрос") с соответствующим сообщением об ошибке. Ограничения маршрутов следует использовать для разрешения неоднозначности похожих маршрутов, а не для проверки входных данных определенного маршрута.
|
|
|
+>Не используйте ограничения для проверки входных данных. Если для проверки входных данных используются ограничения, недопустимые входные данные приводят к ошибке `404` ("Не найдено"). Недопустимые входные данные должны привести к ошибке `400` ("Неверный запрос") с соответствующим сообщением об ошибке. Ограничения маршрутов следует использовать для разрешения неоднозначности похожих маршрутов, а не для проверки входных данных определенного маршрута.
|
|
|
|
|
|
В приведенной ниже таблице показаны примеры ограничения маршрутов и их ожидаемое поведение.
|
|
|
|
|
|
ограничение | Пример | Примеры совпадений | Примечания
|
|
|
------------|--------|--------------------|-----------
|
|
|
-int | {id:int} | 123456789, -123456789 | Соответствует любому целому числу
|
|
|
-bool | {active:bool} | true, FALSE | Соответствует true или false. Без учета регистра
|
|
|
-datetime | {dob:datetime} | 2016-12-31, 2016-12-31 7:32pm | Соответствует допустимому значению DateTime для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
-decimal | {price:decimal} | 49.99, -1,000.01 | Соответствует допустимому значению decimal для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
-double | {weight:double} | 1.234, -1,001.01e8 | Соответствует допустимому значению double для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
-float | {weight:float} | 1.234, -1,001.01e8 | Соответствует допустимому значению float для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
-guid | {id:guid} | CD2C1638-1638-72D5-1638-DEADBEEF1638 | Соответствует допустимому значению Guid
|
|
|
-long | {ticks:long} | 123456789, -123456789 | Соответствует допустимому значению long
|
|
|
-minlength(value) | {username:minlength(4)} | Rick | Строка должна содержать не менее 4 символов
|
|
|
-maxlength(value) | {filename:maxlength(8)} | MyFile | Строка должна содержать не более 8 символов
|
|
|
-length(length) | {filename:length(12)} | somefile.txt | Длина строки должна составлять ровно 12 символов
|
|
|
-length(min,max) | {filename:length(8,16)} | somefile.txt | Строка должна содержать от 8 до 16 символов
|
|
|
-min(value) | {age:min(18)} | 19 | Целочисленное значение не меньше 18
|
|
|
-max(value) | {age:max(120)} | 91 | Целочисленное значение не больше 120
|
|
|
-range(min,max) | {age:range(18,120)} | 91 | Целочисленное значение от 18 до 120
|
|
|
-alpha | {name:alpha} | Rick | Строка должна состоять из одной буквы или нескольких (a-z) без учета регистра.
|
|
|
-regex(expression) | {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} | 123-45-6789 | Строка должна соответствовать регулярному выражению. См. советы по определению регулярного выражения.
|
|
|
-required | {name:required} | Rick | Определяет обязательное наличие значения, не относящегося к параметру, во время формирования URL-адреса
|
|
|
+int | `{id:int}` | 123456789, -123456789 | Соответствует любому целому числу
|
|
|
+bool | `{active:bool}` | true, FALSE | Соответствует true или false. Без учета регистра
|
|
|
+datetime | `{dob:datetime}` | 2016-12-31, 2016-12-31 7:32pm | Соответствует допустимому значению DateTime для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
+decimal | `{price:decimal}` | 49.99, -1,000.01 | Соответствует допустимому значению decimal для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
+double | `{weight:double}` | 1.234, -1,001.01e8 | Соответствует допустимому значению double для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
+float | `{weight:float}` | 1.234, -1,001.01e8 | Соответствует допустимому значению float для инвариантного языка и региональных параметров. См. предупреждение выше.
|
|
|
+guid | `{id:guid}` | CD2C1638-1638-72D5-1638-DEADBEEF1638 | Соответствует допустимому значению Guid
|
|
|
+long | `{ticks:long}` | 123456789, -123456789 | Соответствует допустимому значению long
|
|
|
+minlength(value) | `{username:minlength(4)}` | Rick | Строка должна содержать не менее 4 символов
|
|
|
+maxlength(value) | `{filename:maxlength(8)}` | MyFile | Строка должна содержать не более 8 символов
|
|
|
+length(length) | `{filename:length(12)}` | somefile.txt | Длина строки должна составлять ровно 12 символов
|
|
|
+length(min,max) | `{filename:length(8,16)}` | somefile.txt | Строка должна содержать от 8 до 16 символов
|
|
|
+min(value) | `{age:min(18)}` | 19 | Целочисленное значение не меньше 18
|
|
|
+max(value) | `{age:max(120)}` | 91 | Целочисленное значение не больше 120
|
|
|
+range(min,max) | `{age:range(18,120)}` | 91 | Целочисленное значение от 18 до 120
|
|
|
+alpha | `{name:alpha}` | Rick | Строка должна состоять из одной буквы или нескольких (a-z) без учета регистра.
|
|
|
+regex(expression) | `{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}` | 123-45-6789 | Строка должна соответствовать регулярному выражению. См. советы по определению регулярного выражения.
|
|
|
+required | `{name:required}` | Rick | Определяет обязательное наличие значения, не относящегося к параметру, во время формирования URL-адреса
|
|
|
|
|
|
К одному параметру может применяться несколько ограничений, разделённых двоеточием. Например, следующее ограничение ограничивает параметр целочисленным значением 1 или больше:
|
|
|
|
|
|
@@ -344,250 +339,174 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
|
|
|
Мы для своего АПИ будем использовать минималистичный вариант (он проще).
|
|
|
|
|
|
-1. Создайте новое приложение `ASP.NET Core Web Application`:
|
|
|
+1. Создайте новое приложение `Web-API ASP.NET Core`
|
|
|
+
|
|
|
+ >Количество и настройка шаблонов могут отличаться в разных версиях Visual Studio. Далее рассматривается Visual Studio 2022
|
|
|
|
|
|
- 
|
|
|
+ Минималистичное АПИ можно создать любым шаблоном, но второй шаблон (AOT) не совместим с **Dapper**, поэтому используем первый шаблон, отключив не нужные нам опции:
|
|
|
|
|
|
- Убедитесь, что параметр `Type` установлен в значение `Empty`
|
|
|
+ 
|
|
|
|
|
|
- В созданном проекте нам интересен файл `Program.cs` - в нём создаются *конечные точки*
|
|
|
+1. Структура проекта
|
|
|
|
|
|
- ```cs
|
|
|
- var builder = WebApplication.CreateBuilder(args);
|
|
|
- var app = builder.Build();
|
|
|
+ 
|
|
|
|
|
|
- app.MapGet("/", () => "Hello World!");
|
|
|
+ В созданном проекте нам интересны файлы `Program.cs`, `*.http` и `launchSettings.json`
|
|
|
+
|
|
|
+ * `Program.cs` - основной код приложения, в нём создаются *конечные точки*
|
|
|
|
|
|
- app.Run();
|
|
|
- ```
|
|
|
-
|
|
|
- Метод **MapGet** как раз и создаёт *конечную точку* `/` для метода **GET**, т.е. GET-запросы с *конечной точкой* `/` будут обработаны делегатом (лямбда выражением) `() => "Hello World!"`
|
|
|
+ В файле уже есть `рыба` - проект с эмуляцией сервиса "погода"
|
|
|
|
|
|
- Если запустить проект, то сервер откроет два порта для прослушивания входящих соединений (для HTTP и HTTPS)
|
|
|
+ ```cs
|
|
|
+ var builder = WebApplication.CreateBuilder(args);
|
|
|
|
|
|
- ```
|
|
|
- info: Microsoft.Hosting.Lifetime[14]
|
|
|
- Now listening on: https://localhost:7116
|
|
|
- info: Microsoft.Hosting.Lifetime[14]
|
|
|
- Now listening on: http://localhost:5177
|
|
|
- ```
|
|
|
+ // Add services to the container.
|
|
|
|
|
|
- И откроет страницу в браузере
|
|
|
+ var app = builder.Build();
|
|
|
|
|
|
- При создании веб-приложения, автоматически создается файл `Properties/launchSettings.json` с указанием портов, на которых отвечает это приложение.
|
|
|
+ // Configure the HTTP request pipeline.
|
|
|
|
|
|
- Но можно переопределить порт в методе **Run**:
|
|
|
-
|
|
|
- ```cs
|
|
|
- app.Run("http://localhost:8080");
|
|
|
- ```
|
|
|
-
|
|
|
-1. Подключение БД
|
|
|
-
|
|
|
- <!-- TODO прикрутить в контекст? -->
|
|
|
-
|
|
|
- Используйте тот же подход, что и для обычного приложения: [Создание подключения к БД MySQL.](./cs_mysql_connection3.md)
|
|
|
-
|
|
|
-1. Сразу настроим **Swagger**:
|
|
|
-
|
|
|
- ```cs
|
|
|
- builder.Services.AddEndpointsApiExplorer();
|
|
|
- builder.Services.AddSwaggerGen();
|
|
|
+ var summaries = new[]
|
|
|
+ {
|
|
|
+ "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
|
|
+ };
|
|
|
|
|
|
- ...
|
|
|
+ app.MapGet("/weatherforecast", () =>
|
|
|
+ {
|
|
|
+ var forecast = Enumerable.Range(1, 5).Select(index =>
|
|
|
+ new WeatherForecast
|
|
|
+ (
|
|
|
+ DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
|
|
+ Random.Shared.Next(-20, 55),
|
|
|
+ summaries[Random.Shared.Next(summaries.Length)]
|
|
|
+ ))
|
|
|
+ .ToArray();
|
|
|
+ return forecast;
|
|
|
+ });
|
|
|
+
|
|
|
+ app.Run();
|
|
|
+
|
|
|
+ internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
|
|
|
+ {
|
|
|
+ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
|
+ }
|
|
|
+ ```
|
|
|
|
|
|
- app.UseSwagger();
|
|
|
- app.UseSwaggerUI(c =>
|
|
|
- {
|
|
|
- c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
|
|
- });
|
|
|
- ```
|
|
|
+ Если запустить проект, то сервер откроет порт для прослушивания входящих соединений (для HTTP)
|
|
|
+
|
|
|
+ ```
|
|
|
+ info: Microsoft.Hosting.Lifetime[14]
|
|
|
+ Now listening on: http://localhost:5256
|
|
|
+ info: Microsoft.Hosting.Lifetime[0]
|
|
|
+ Application started. Press Ctrl+C to shut down.
|
|
|
+ ```
|
|
|
|
|
|
- [Выше](#основы-маршрутизации) было расписано где должны располагаться эти команды.
|
|
|
+ И откроет страницу в браузере
|
|
|
|
|
|
-1. Добавление *конечной точки* для получения списка продукции (**C****_Read_****UD**):
|
|
|
+ * `launchSettings.json`
|
|
|
|
|
|
- Добавьте ещё один **MapGet** с _конечной точкой_ `/product`:
|
|
|
+ При создании веб-приложения, автоматически создается файл `Properties/launchSettings.json` с указанием портов, на которых отвечает это приложение.
|
|
|
|
|
|
- ```cs
|
|
|
- app.MapGet("/product", () =>
|
|
|
- {
|
|
|
- using (var context = new esmirnovContext())
|
|
|
+ ```json
|
|
|
{
|
|
|
- return context.Products.ToList();
|
|
|
+ "$schema": "http://json.schemastore.org/launchsettings.json",
|
|
|
+ "iisSettings": {
|
|
|
+ "windowsAuthentication": false,
|
|
|
+ "anonymousAuthentication": true,
|
|
|
+ "iisExpress": {
|
|
|
+ "applicationUrl": "http://localhost:49863",
|
|
|
+ "sslPort": 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ "profiles": {
|
|
|
+ "http": {
|
|
|
+ "commandName": "Project",
|
|
|
+ "dotnetRunMessages": true,
|
|
|
+ // тут можно отключить запуск браузера
|
|
|
+ "launchBrowser": true,
|
|
|
+ "launchUrl": "weatherforecast",
|
|
|
+ // тут можно изменить порт
|
|
|
+ "applicationUrl": "http://localhost:5153",
|
|
|
+ "environmentVariables": {
|
|
|
+ "ASPNETCORE_ENVIRONMENT": "Development"
|
|
|
+ }
|
|
|
+ },
|
|
|
+ "IIS Express": {
|
|
|
+ "commandName": "IISExpress",
|
|
|
+ "launchBrowser": true,
|
|
|
+ "launchUrl": "weatherforecast",
|
|
|
+ "environmentVariables": {
|
|
|
+ "ASPNETCORE_ENVIRONMENT": "Development"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- })
|
|
|
- .WithTags("CRUD продукции")
|
|
|
- .Produces<Product[]>(StatusCodes.Status200OK);
|
|
|
- ```
|
|
|
+ ```
|
|
|
|
|
|
- Метод **WithTags** используется для задания тега в **Swagger** (группировка запросов по какому-то признаку)
|
|
|
+ Но можно переопределить порт в методе **Run**:
|
|
|
|
|
|
- Метод **Produces** тоже для **Swagger**. Он описывает какого типа будет результат (если тела ответа не предполагется, то и тип можно не указывать) и с каким кодом ответа. Таких методов может быть несколько в цепочке.
|
|
|
+ ```cs
|
|
|
+ app.Run("http://localhost:8080");
|
|
|
+ ```
|
|
|
|
|
|
- Можно запустить проект и перейти по ссылке `http://хост:порт/swagger/`. Должно получиться примерно такое (у меня уже чуть больше реализовано)
|
|
|
+ * `<Название проекта>.http`
|
|
|
|
|
|
- 
|
|
|
+ ```
|
|
|
+ @api4_HostAddress = http://localhost:5153
|
|
|
|
|
|
- Т.е. есть описание конечной точки `GET /product`, возможных параметров запроса и возможных ответов. Причём можно нажать кнопку **"Try it out"**, заполнить, при необходимости параметры запроса, затем нажать кнопку **"Execute"** и получим ответ сервера!
|
|
|
+ GET {{api4_HostAddress}}/weatherforecast/
|
|
|
+ Accept: application/json
|
|
|
|
|
|
- 
|
|
|
+ ###
|
|
|
+ ```
|
|
|
|
|
|
- Получим список продукции
|
|
|
-
|
|
|
- ```json
|
|
|
- [
|
|
|
- {
|
|
|
- "id": 128,
|
|
|
- "title": "Колесо R18 Кованый",
|
|
|
- "productTypeId": 139,
|
|
|
- "articleNumber": "241659",
|
|
|
- "description": null,
|
|
|
- "image": "\\products\\tire_15.jpg",
|
|
|
- "productionPersonCount": 4,
|
|
|
- "productionWorkshopNumber": 10,
|
|
|
- "minCostForAgent": 111.00,
|
|
|
- "productType": null,
|
|
|
- "productCostHistories": [],
|
|
|
- "productMaterials": [],
|
|
|
- "productSales": []
|
|
|
- },
|
|
|
- ...
|
|
|
- ```
|
|
|
+ Текстовый файл с описанием конечных точек. Плагины в **VS** и **VSCode** распознают расширение `.http` и позволяют выполнять запросы в один клик
|
|
|
|
|
|
- В принципе этого может быть уже достаточно на демо-экзамене, если в ТЗ явно не указано какие именно поля должен возвращать запрос.
|
|
|
+1. Подключение БД
|
|
|
|
|
|
- Но мы помним, что обычный **LINQ**-запрос получает данные только из одной сущности, поэтому поле *productType* не заполнено, и поля *productCostHistories*, _productMaterials_, _productSales_ вообще не относятся к таблице, а являются виртуальными свойствами модели.
|
|
|
+ Используйте тот же подход, что и для обычного приложения: [Создание подключения к БД MySQL.](./cs_mysql_connection3.md)
|
|
|
|
|
|
- Для решения первой проблемы мы использовали метод _Include_, но при использовании такого подхода у нас получится обратная связь от типов продукции к продукции и JSON-конвертер выдаст исключение (можете сами проверить на практике)
|
|
|
+1. Добавление *конечной точки* для получения списка продукции (**C****_Read_****UD**):
|
|
|
|
|
|
- Можно настроить конвертер, чтобы он игнорировал циклические ссылки
|
|
|
+ Удалите код, реализующий сервер погоды. Пустой проект должен быть таким:
|
|
|
|
|
|
```cs
|
|
|
- // это в код добавлять не нужно, оставил на всякий случай
|
|
|
- builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
|
|
|
- {
|
|
|
- options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
|
|
- options.SerializerOptions.WriteIndented = true;
|
|
|
- });
|
|
|
- ```
|
|
|
+ var builder = WebApplication.CreateBuilder(args);
|
|
|
+ var app = builder.Build();
|
|
|
|
|
|
- но итоговый результат получается слишком подробный:
|
|
|
+ // сюда будем добавлять свои конечные точки
|
|
|
|
|
|
- ```json
|
|
|
- [
|
|
|
- {
|
|
|
- "id": 128,
|
|
|
- "title": "Колесо R18 Кованый",
|
|
|
- "productTypeId": 139,
|
|
|
- "articleNumber": "241659",
|
|
|
- "description": null,
|
|
|
- "image": "\\products\\tire_15.jpg",
|
|
|
- "productionPersonCount": 4,
|
|
|
- "productionWorkshopNumber": 10,
|
|
|
- "minCostForAgent": 111.00,
|
|
|
- "productType": {
|
|
|
- "id": 139,
|
|
|
- "titleType": "Колесо",
|
|
|
- "defectedPercent": 0,
|
|
|
- "products": [
|
|
|
- null,
|
|
|
- {
|
|
|
- "id": 139,
|
|
|
- "title": "Колесо R15 Кованый",
|
|
|
- "productTypeId": 139,
|
|
|
- "articleNumber": "376388",
|
|
|
- "description": null,
|
|
|
- "image": "",
|
|
|
- "productionPersonCount": 3,
|
|
|
- "productionWorkshopNumber": 4,
|
|
|
- "minCostForAgent": 9439.00,
|
|
|
- "productType": null,
|
|
|
- "productCostHistories": [],
|
|
|
- "productMaterials": [],
|
|
|
- "productSales": []
|
|
|
- },
|
|
|
- ...
|
|
|
- ]
|
|
|
- },
|
|
|
- "productCostHistories": [],
|
|
|
- "productMaterials": [],
|
|
|
- "productSales": []
|
|
|
+ app.Run();
|
|
|
```
|
|
|
|
|
|
- Нам в списке продукции не нужны данные о массиве продуктов для каждого типа продукции.
|
|
|
+ >Не забудьте добавить в проект зависимости **MySQLConnector** и **Dapper** и создать поставщик данных:
|
|
|
+ >
|
|
|
+ >```cs
|
|
|
+ >var dbDataProvider = new DBDataProvider();
|
|
|
+ >```
|
|
|
|
|
|
- Мы можем выбрать не все поля модели, а только нужные нам методом _Select_:
|
|
|
+ Добавьте **MapGet** с _конечной точкой_ `/product`:
|
|
|
|
|
|
```cs
|
|
|
- return context.Products
|
|
|
- .Select(p => new {
|
|
|
- id = p.Id,
|
|
|
- title = p.Title,
|
|
|
- productTypeId = p.ProductTypeId,
|
|
|
- articleNumber = p.ArticleNumber,
|
|
|
- description = p.Description,
|
|
|
- image = p.Image,
|
|
|
- productionPersonCount = p.ProductionPersonCount,
|
|
|
- productionWorkshopNumber = p.ProductionWorkshopNumber,
|
|
|
- minCostForAgent = p.MinCostForAgent,
|
|
|
- productTypeTitle = p.ProductType.TitleType
|
|
|
- })
|
|
|
- .ToList();
|
|
|
+ app.MapGet("/product", () =>
|
|
|
+ {
|
|
|
+ return dbDataProvider.getProduct();
|
|
|
+ });
|
|
|
```
|
|
|
|
|
|
- Т.е. у нас для каждого экземпляра модели (`p` в делегате) создаётся новый экземпляр объекта (`new {...}`) в инициализаторе которого мы и прописываем поля, которые хотим видеть на выходе. При этом значение для `p.ProductType.TitleType` подтягивается динамически
|
|
|
+ Можно запустить проект и либо открыть ссылку `http://localhost:<порт>/product` в браузере, либо отредактировать `.http` файл и выполнить запрос в нём:
|
|
|
|
|
|
- ```json
|
|
|
- [
|
|
|
- {
|
|
|
- "id": 128,
|
|
|
- "title": "Колесо R18 Кованый",
|
|
|
- "productTypeId": 139,
|
|
|
- "articleNumber": "241659",
|
|
|
- "description": null,
|
|
|
- "image": "\\products\\tire_15.jpg",
|
|
|
- "productionPersonCount": 4,
|
|
|
- "productionWorkshopNumber": 10,
|
|
|
- "minCostForAgent": 111.00,
|
|
|
- "productTypeTitle": "Колесо"
|
|
|
- },
|
|
|
- ```
|
|
|
+ 
|
|
|
|
|
|
- Ну и шлифанём этот метод, добавив обработку **querystring**
|
|
|
+ В принципе этого может быть уже достаточно на демо-экзамене, если в ТЗ явно не указано какие именно поля должен возвращать запрос.
|
|
|
|
|
|
- Запрос теперь может включать номер и размер страницы:
|
|
|
+ Добавим обработку **querystring**, чтобы запрос поддерживал номер и размер страницы:
|
|
|
|
|
|
```cs
|
|
|
- app.MapGet(
|
|
|
- "/product",
|
|
|
- (int? pageNum, int? pageLen) =>
|
|
|
+ app.MapGet("/product", (int? pageNum) =>
|
|
|
{
|
|
|
- using (var context = new esmirnovContext())
|
|
|
- {
|
|
|
- var query = context.Products
|
|
|
- .Select(p => new
|
|
|
- {
|
|
|
- id = p.Id,
|
|
|
- title = p.Title,
|
|
|
- productTypeId = p.ProductTypeId,
|
|
|
- articleNumber = p.ArticleNumber,
|
|
|
- description = p.Description,
|
|
|
- image = p.Image,
|
|
|
- productionPersonCount = p.ProductionPersonCount,
|
|
|
- productionWorkshopNumber = p.ProductionWorkshopNumber,
|
|
|
- minCostForAgent = p.MinCostForAgent,
|
|
|
- productTypeTitle = p.ProductType.TitleType
|
|
|
- });
|
|
|
-
|
|
|
- return query
|
|
|
- .Skip(((pageNum ?? 1) - 1) * (pageLen ?? 20))
|
|
|
- .Take(pageLen ?? 20)
|
|
|
- .ToList();
|
|
|
- }
|
|
|
- })
|
|
|
- .WithTags("CRUD продукции")
|
|
|
- .Produces<Product[]>(StatusCodes.Status200OK);
|
|
|
+ return dbDataProvider.getProduct(pageNum ?? 1);
|
|
|
+ });
|
|
|
```
|
|
|
|
|
|
1. Добавление новой продукции (**_Create_****RUD**)
|
|
|
@@ -595,82 +514,51 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
Добавьте **MapPost** с конечной точной `/product`:
|
|
|
|
|
|
```cs
|
|
|
- app.MapPost(
|
|
|
- "/product",
|
|
|
- (Product postProduct) =>
|
|
|
+ app.MapPost("/product", (Product newProduct) =>
|
|
|
{
|
|
|
- using (var context = new esmirnovContext())
|
|
|
- {
|
|
|
- context.Add(postProduct);
|
|
|
- context.SaveChanges();
|
|
|
- }
|
|
|
- })
|
|
|
- .WithTags("CRUD продукции")
|
|
|
- .Produces(StatusCodes.Status401Unauthorized)
|
|
|
- .Produces(StatusCodes.Status200OK);
|
|
|
+ dbDataProvider.saveProduct(newProduct);
|
|
|
+ });
|
|
|
```
|
|
|
|
|
|
- Для **Swagger** я дописал `Produces(StatusCodes.Status401Unauthorized)`. Авторизацию мы прикрутим в следующей лекции, но сразу заложим возможные коды ответов.
|
|
|
-
|
|
|
- Здесь у нас в параметрах делегата объект **Product**, который автоматически получен из тела запроса (ни в пути, ни в строке поиска параметров нет).
|
|
|
+ Здесь у нас в параметрах делегата объект _newProduct_, который автоматически получен из тела запроса (ни в пути, ни в строке поиска параметров нет).
|
|
|
|
|
|
- Попробуйте выполнить этот запрос в **Swagger** (id типа продукции возьмите из своей базы):
|
|
|
+ Попробуйте добавить и выполнить этот запрос в `.http` (id типа продукции возьмите из своей базы):
|
|
|
|
|
|
- 
|
|
|
+ ```http
|
|
|
+ ### Добавление продукта
|
|
|
+ POST {{api3_HostAddress}}/product
|
|
|
+ Content-Type: application/json
|
|
|
|
|
|
- - тело запроса в формате **JSON**, причём названия полей должны соответствовать **модели** *Product* (но не надо заполнять весь тот треш, который покажет **Swagger**, достаточно только тех полей, которые нужны для **таблицы** *Product*)
|
|
|
- - должны быть указаны все обязательные поля (кроме `Id` - он автоматически создастся при добавлении), остальные желательно, но не обязательно.
|
|
|
+ {
|
|
|
+ "ID": 0,
|
|
|
+ "Title": "Новое колесо",
|
|
|
+ "ProductTypeID": 1,
|
|
|
+ "ArticleNumber": "123"
|
|
|
+ }
|
|
|
|
|
|
- >Чтобы не было лишних полей, можно описать отдельный класс, например **ShortProduct**, в котором прописать только те поля, которые есть в таблице
|
|
|
+ ### конец тела запроса
|
|
|
+ ```
|
|
|
|
|
|
- 
|
|
|
+ - тело запроса в формате **JSON**, причём названия полей должны соответствовать **модели** **Product** (но не надо заполнять вычисляемые поля, достаточно только тех полей, которые нужны для **таблицы** **Product**)
|
|
|
+ - должны быть указаны все обязательные поля, остальные желательно, но не обязательно.
|
|
|
|
|
|
- Если всё нормально, то код ответа будет 200, а содержимого мы никакого не возвращаем (если нужно, то тут можно вернуть вновь созданный объект, чтобы сразу знать его `id`).
|
|
|
+ Если всё нормально, то код ответа будет `200`, а содержимого мы никакого не возвращаем (если нужно, то тут можно вернуть вновь созданный объект, чтобы сразу знать его `id`).
|
|
|
|
|
|
1. Изменение существующей продукции (**СR****_Update_****D**)
|
|
|
|
|
|
- **Id** изменяемой продукции будем передавать в пути запроса (это не обязательно, но как пример)
|
|
|
-
|
|
|
* используем метод `PUT`
|
|
|
- * в пути передаём параметр id продукции `/product/227`
|
|
|
|
|
|
Реализация **конечной точки**:
|
|
|
|
|
|
```cs
|
|
|
- app.MapPut(
|
|
|
- "/product/{id:int}",
|
|
|
- (int id, Product postProduct) =>
|
|
|
+ app.MapPut("/product",
|
|
|
+ (Product editProduct) =>
|
|
|
{
|
|
|
- using (var context = new esmirnovContext())
|
|
|
- {
|
|
|
- // сначала проверяем есть ли такой продукт в базе
|
|
|
- var product = context.Products.Find(id);
|
|
|
- if (product == null)
|
|
|
- return Results.NotFound();
|
|
|
-
|
|
|
- // тут можно приделать проверку данных
|
|
|
-
|
|
|
- // переписываем данные из тела запроса в модель
|
|
|
- product.Title = postProduct.Title;
|
|
|
- product.ProductionWorkshopNumber = postProduct.ProductionWorkshopNumber;
|
|
|
-
|
|
|
- // ...
|
|
|
-
|
|
|
- context.Update(product);
|
|
|
- context.SaveChanges();
|
|
|
- }
|
|
|
-
|
|
|
- // без этой строки ругается, видимо если мы задействовали
|
|
|
- // метод Results, то обязаны его использовать при любом результате
|
|
|
- return Results.Ok();
|
|
|
+ dbDataProvider.saveProduct(editProduct);
|
|
|
})
|
|
|
- .WithTags("CRUD продукции")
|
|
|
- .Produces(StatusCodes.Status200OK)
|
|
|
- .Produces(StatusCodes.Status401Unauthorized)
|
|
|
- .Produces(StatusCodes.Status404NotFound);
|
|
|
```
|
|
|
|
|
|
- В **Swagger** протестируйте сами, тут уже ничего нового нет.
|
|
|
+ В `.http` протестируйте сами, тут уже ничего нового нет.
|
|
|
|
|
|
1. Удаление существующей продукции (**СRU****_Delete_**)
|
|
|
|
|
|
@@ -683,31 +571,15 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
"/product/{id:int}",
|
|
|
(int id) =>
|
|
|
{
|
|
|
- using (var context = new esmirnovContext())
|
|
|
- {
|
|
|
- var product = context.Products.Find(id);
|
|
|
- if (product == null)
|
|
|
- return Results.NotFound();
|
|
|
-
|
|
|
- // тут тоже нужна логика
|
|
|
-
|
|
|
- context.Products.Remove(product);
|
|
|
- context.SaveChanges();
|
|
|
- }
|
|
|
-
|
|
|
- return Results.Ok();
|
|
|
+ dbDataProvider.removeProduct(id);
|
|
|
})
|
|
|
- .WithTags("CRUD продукции")
|
|
|
- .Produces(StatusCodes.Status200OK)
|
|
|
- .Produces(StatusCodes.Status401Unauthorized)
|
|
|
- .Produces(StatusCodes.Status404NotFound);
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
**Задание:**
|
|
|
|
|
|
-В следующих лекциях нам понадобится список матералов продукта, реализуйте конечную точку `GET /material/{productId:int}`, возвращающую список материалов указанного продукта.
|
|
|
+В следующих лекциях мы напишем для нашего **WPF** проекта **HttpDataProvider**, который будет получать данные через **АПИ**, вам нужно реализовать все конечные точки, которые нужны для реализации интерфейса **IDataProvider**.
|
|
|
|
|
|
Предыдущая лекция | | Следующая лекция
|
|
|
:----------------:|:----------:|:----------------:
|