|
@@ -1,5 +1,9 @@
|
|
|
# API. REST API. Создание сервера ASP.NET Core.
|
|
# API. REST API. Создание сервера ASP.NET Core.
|
|
|
|
|
|
|
|
|
|
+* [API. REST API.](#api-rest-api)
|
|
|
|
|
+* [Swagger](#open-api-swagger)
|
|
|
|
|
+* [Создание сервера ASP.NET Core.](#создание-сервера-aspnet-core)
|
|
|
|
|
+
|
|
|
## API. REST API.
|
|
## API. REST API.
|
|
|
|
|
|
|
|
Взято [отсюда](https://cloud.yandex.ru/docs/glossary/rest-api)
|
|
Взято [отсюда](https://cloud.yandex.ru/docs/glossary/rest-api)
|
|
@@ -131,6 +135,17 @@ 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)
|
|
|
|
|
+
|
|
|
|
|
+**Swagger** - это фреймворк для спецификации *RESTful API*. Его прелесть заключается в том, что он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы.
|
|
|
|
|
+
|
|
|
|
|
+**ASP.NET** поддерживает генерацию спецификации и конечной точки для использования этой спецификации.
|
|
|
|
|
+
|
|
|
|
|
+Для поддержки Swagger нужно установить NuGet пакеты:
|
|
|
|
|
+
|
|
|
|
|
+* Microsoft.OpenApi
|
|
|
|
|
+* Swashbuckle.AspNetCore
|
|
|
|
|
+
|
|
|
## Создание сервера ASP.NET Core.
|
|
## Создание сервера ASP.NET Core.
|
|
|
|
|
|
|
|
### Маршрутизация в ASP.NET
|
|
### Маршрутизация в ASP.NET
|
|
@@ -152,12 +167,23 @@ REST API — самый популярный сегодня стандарт в
|
|
|
|
|
|
|
|
#### Основы маршрутизации
|
|
#### Основы маршрутизации
|
|
|
|
|
|
|
|
-В следующем коде приведен базовый пример маршрутизации.
|
|
|
|
|
|
|
+В следующем коде приведен базовый пример маршрутизации. Заодно в комментариях расписана структура.
|
|
|
|
|
|
|
|
```cs
|
|
```cs
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+сюда дописываются дополнительные сервисы, всё что дальше начинается с "builder.Services"
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
var app = builder.Build();
|
|
var app = builder.Build();
|
|
|
|
|
|
|
|
|
|
+/*
|
|
|
|
|
+тут запускаются так называемые "middleware": swagger, авторизация и т.п. процедуры, которые должны быть выполнены до обработки конечных точек
|
|
|
|
|
+
|
|
|
|
|
+они начинаются с "app.Use"
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
app.MapGet("/", () => "Hello World!");
|
|
app.MapGet("/", () => "Hello World!");
|
|
|
|
|
|
|
|
app.Run();
|
|
app.Run();
|
|
@@ -167,7 +193,7 @@ app.Run();
|
|
|
|
|
|
|
|
* При отправке HTTP-запроса **GET** в корневой URL-адрес `/`:
|
|
* При отправке HTTP-запроса **GET** в корневой URL-адрес `/`:
|
|
|
* Выполняется делегат запроса.
|
|
* Выполняется делегат запроса.
|
|
|
- * В ответ HTTP записывается Hello World!.
|
|
|
|
|
|
|
+ * В ответ HTTP записывается "Hello World!".
|
|
|
* Если метод запроса не является **GET** или если корневой URL-адрес не `/`, сопоставление маршрута не выполняется и возвращается сообщение об ошибке HTTP 404.
|
|
* Если метод запроса не является **GET** или если корневой URL-адрес не `/`, сопоставление маршрута не выполняется и возвращается сообщение об ошибке HTTP 404.
|
|
|
|
|
|
|
|
Маршрутизация использует пару ПО промежуточного слоя: UseRouting и UseEndpoints.
|
|
Маршрутизация использует пару ПО промежуточного слоя: UseRouting и UseEndpoints.
|
|
@@ -175,7 +201,7 @@ app.Run();
|
|
|
* **UseRouting** добавляет соответствие маршрута в конвейер ПО промежуточного слоя. Это ПО промежуточного слоя обращается к набору конечных точек, определенных в приложении, и выбирает наиболее подходящее на основе запроса.
|
|
* **UseRouting** добавляет соответствие маршрута в конвейер ПО промежуточного слоя. Это ПО промежуточного слоя обращается к набору конечных точек, определенных в приложении, и выбирает наиболее подходящее на основе запроса.
|
|
|
* **UseEndpoints** добавляет выполнение конечной точки в конвейер ПО промежуточного слоя. Он запускает делегат, связанный с выбранной конечной точкой.
|
|
* **UseEndpoints** добавляет выполнение конечной точки в конвейер ПО промежуточного слоя. Он запускает делегат, связанный с выбранной конечной точкой.
|
|
|
|
|
|
|
|
-Приложениям обычно не требуется вызывать **UseRouting** или **UseEndpoints**. WebApplicationBuilder настраивает конвейер ПО промежуточного слоя, который создает программу-оболочку для ПО промежуточного слоя, добавленное в `Program.cs` с использованием **UseRouting** и **UseEndpoints**. Но приложения могут изменять порядок, в котором выполняются **UseRouting** и **UseEndpoints**, вызывая эти методы явным образом. Например, следующий код явным образом вызывает **UseRouting**:
|
|
|
|
|
|
|
+Приложениям обычно не требуется вызывать **UseRouting** или **UseEndpoints**. **WebApplicationBuilder** настраивает конвейер ПО промежуточного слоя, который создает программу-оболочку для ПО промежуточного слоя, добавленное в `Program.cs` с использованием **UseRouting** и **UseEndpoints**. Но приложения могут изменять порядок, в котором выполняются **UseRouting** и **UseEndpoints**, вызывая эти методы явным образом. Например, следующий код явным образом вызывает **UseRouting**:
|
|
|
|
|
|
|
|
```cs
|
|
```cs
|
|
|
app.Use(async (context, next) =>
|
|
app.Use(async (context, next) =>
|
|
@@ -269,7 +295,7 @@ users/{id:int:min(1)}
|
|
|
|
|
|
|
|
#### Параметры пути и поиска (querystring)
|
|
#### Параметры пути и поиска (querystring)
|
|
|
|
|
|
|
|
-ASP.NET достаточно умный, чтобы вытащить из запроса все параметры, независимо от того где в URL они расположены. Параметры из **пути**, **строки запроса** и **тела запроса** в итоге попадают в коллекцию параметров делегата:
|
|
|
|
|
|
|
+**ASP.NET** достаточно умный, чтобы вытащить из запроса все параметры, независимо от того где в URL они расположены. Параметры из **пути**, **строки запроса** и **тела запроса** в итоге попадают в коллекцию параметров делегата:
|
|
|
|
|
|
|
|
Например, есть такой запрос:
|
|
Например, есть такой запрос:
|
|
|
|
|
|
|
@@ -352,8 +378,27 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
|
|
|
|
|
1. Подключение БД
|
|
1. Подключение БД
|
|
|
|
|
|
|
|
|
|
+ <!-- TODO прикрутить в контекст? -->
|
|
|
|
|
+
|
|
|
Используйте тот же подход, что и для обычного приложения: [Создание подключения к БД MySQL.](./cs_mysql_connection3.md)
|
|
Используйте тот же подход, что и для обычного приложения: [Создание подключения к БД MySQL.](./cs_mysql_connection3.md)
|
|
|
|
|
|
|
|
|
|
+1. Сразу настроим **Swagger**:
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ builder.Services.AddEndpointsApiExplorer();
|
|
|
|
|
+ builder.Services.AddSwaggerGen();
|
|
|
|
|
+
|
|
|
|
|
+ ...
|
|
|
|
|
+
|
|
|
|
|
+ app.UseSwagger();
|
|
|
|
|
+ app.UseSwaggerUI(c =>
|
|
|
|
|
+ {
|
|
|
|
|
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
|
|
|
|
+ });
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ [Выше](#основы-маршрутизации) было расписано где должны располагаться эти команды.
|
|
|
|
|
+
|
|
|
1. Добавление *конечной точки* для получения списка продукции (**C****_Read_****UD**):
|
|
1. Добавление *конечной точки* для получения списка продукции (**C****_Read_****UD**):
|
|
|
|
|
|
|
|
Добавьте ещё один **MapGet** с конечной точной `/product`:
|
|
Добавьте ещё один **MapGet** с конечной точной `/product`:
|
|
@@ -365,10 +410,24 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
{
|
|
{
|
|
|
return context.Products.ToList();
|
|
return context.Products.ToList();
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ .WithTags("CRUD продукции")
|
|
|
|
|
+ .Produces<Product>(StatusCodes.Status200OK);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
- т.е. при запросе `GET http://хост:порт/product` мы получим список продукции
|
|
|
|
|
|
|
+ Метод **WithTags** используется для задания тега в Swagger (группировка запросов по какому-то признаку)
|
|
|
|
|
+
|
|
|
|
|
+ Метод **Produces** тоже для **Swagger**. Он описывает какого типа будет результат и с каким кодом ответа (таких методов может быть несколько в цепочке).
|
|
|
|
|
+
|
|
|
|
|
+ Можно запустить проект и перейти по ссылке `http://хост:порт/swagger/`. Должно получиться примерно такое (у меня уже чуть больше реализовано)
|
|
|
|
|
+
|
|
|
|
|
+ 
|
|
|
|
|
+
|
|
|
|
|
+ Т.е. есть описание конечной точки `GET /product`, возможных параметров запроса и возможных ответов. Причём можно нажать кнопку **"Try it out"**, заполнить, при необходимости параметры запроса, затем нажать кнопку **"Execute"** и получим ответ сервера!
|
|
|
|
|
+
|
|
|
|
|
+ 
|
|
|
|
|
+
|
|
|
|
|
+ Получим список продукции
|
|
|
|
|
|
|
|
```json
|
|
```json
|
|
|
[
|
|
[
|
|
@@ -399,6 +458,7 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
Можно настроить конвертер, чтобы он игнорировал циклические ссылки
|
|
Можно настроить конвертер, чтобы он игнорировал циклические ссылки
|
|
|
|
|
|
|
|
```cs
|
|
```cs
|
|
|
|
|
+ // это в код добавлять не нужно, оставил на всякий случай
|
|
|
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
|
|
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
|
|
|
{
|
|
{
|
|
|
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
|
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
|
@@ -470,7 +530,7 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
.ToList();
|
|
.ToList();
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
- Т.е. у нас для каждого экземпляра модели (`p`) создаётся новый экземпляр объекта (`new {...}`) в инициализаторе которого мы и прописываем поля, которые хотим видеть на выходе. При этом значение для `p.ProductType.TitleType` подтягивается динамически
|
|
|
|
|
|
|
+ Т.е. у нас для каждого экземпляра модели (`p` в делегате) создаётся новый экземпляр объекта (`new {...}`) в инициализаторе которого мы и прописываем поля, которые хотим видеть на выходе. При этом значение для `p.ProductType.TitleType` подтягивается динамически
|
|
|
|
|
|
|
|
```json
|
|
```json
|
|
|
[
|
|
[
|
|
@@ -519,7 +579,9 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
.Take(pageLen ?? 20)
|
|
.Take(pageLen ?? 20)
|
|
|
.ToList();
|
|
.ToList();
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ .WithTags("CRUD продукции")
|
|
|
|
|
+ .Produces<Product>(StatusCodes.Status200OK);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
1. Добавление новой продукции (**_Create_****RUD**)
|
|
1. Добавление новой продукции (**_Create_****RUD**)
|
|
@@ -536,48 +598,32 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
context.Add(postProduct);
|
|
context.Add(postProduct);
|
|
|
context.SaveChanges();
|
|
context.SaveChanges();
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ .WithTags("CRUD продукции")
|
|
|
|
|
+ .Produces(StatusCodes.Status401Unauthorized)
|
|
|
|
|
+ .Produces(StatusCodes.Status200OK);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+ Для **Swagger** я дописал `Produces(StatusCodes.Status401Unauthorized)`. Авторизацию мы прикрутим в следующей лекции, но сразу заложим возможные коды ответов.
|
|
|
|
|
+
|
|
|
Здесь у нас в параметрах делегата объект **Product**, который автоматически получен из тела запроса (ни в пути, ни в строке поиска параметров нет).
|
|
Здесь у нас в параметрах делегата объект **Product**, который автоматически получен из тела запроса (ни в пути, ни в строке поиска параметров нет).
|
|
|
|
|
|
|
|
- Пример запроса в формате плагина **REST Client** для **VSCode**:
|
|
|
|
|
|
|
+ Попробуйте выполнить этот запрос в **Swagger** (id типа продукции возьмите из своей базы):
|
|
|
|
|
|
|
|
- ```
|
|
|
|
|
- ### добавление продукта
|
|
|
|
|
- POST http://localhost:8080/product
|
|
|
|
|
- Content-Type: application/json
|
|
|
|
|
|
|
+ 
|
|
|
|
|
|
|
|
- {
|
|
|
|
|
- "Title": "post title",
|
|
|
|
|
- "ProductTypeId": 141,
|
|
|
|
|
- "ArticleNumber": "1234",
|
|
|
|
|
- "MinCostForAgent": 100.50
|
|
|
|
|
- }
|
|
|
|
|
- ```
|
|
|
|
|
|
|
+ - тело запроса в формате **JSON**, причём названия полей должны соответствовать **модели** *Product* (но не надо заполнять весь тот треш, который покажет **Swagger**, достаточно только тех полей, которые нужны для **таблицы** *Product*)
|
|
|
|
|
+ - должны быть указаны все обязательные поля (кроме `Id` - он автоматически создастся при добавлении), остальные желательно, но не обязательно.
|
|
|
|
|
|
|
|
- - метод запроса: `POST`
|
|
|
|
|
- - путь: `/product`
|
|
|
|
|
- - в заголовке нужно обязательно указать тип содержимого: `Content-Type: application/json`
|
|
|
|
|
- - тело запроса в формате **JSON**, причём названия полей должны соответствовать модели **Product**
|
|
|
|
|
- - должны быть указаны все обязательные поля (кроме `Id` - он автоматически создастся при добавлении), остальные желательно, но не обязательно
|
|
|
|
|
|
|
+ >Чтобы не было лишних полей, можно описать отдельный класс, например **ShortProduct**, в котором прописать только те поля, которые есть в таблице
|
|
|
|
|
|
|
|
-1. Изменение существующей продукции (**СR****_Update_****D**)
|
|
|
|
|
|
|
+ 
|
|
|
|
|
|
|
|
- **Id** изменяемой продукции будем передавать в пути запроса (это не обязательно, но как пример)
|
|
|
|
|
|
|
+ Если всё нормально, то код ответа будет 200, а содержимого мы никакого не возвращаем (если нужно, то тут можно вернуть вновь созданный объект, чтобы сразу знать его `id`).
|
|
|
|
|
|
|
|
- ```
|
|
|
|
|
- ### изменение продукта
|
|
|
|
|
- PUT http://localhost:8080/product/227
|
|
|
|
|
- Content-Type: application/json
|
|
|
|
|
|
|
+1. Изменение существующей продукции (**СR****_Update_****D**)
|
|
|
|
|
|
|
|
- {
|
|
|
|
|
- "Title": "post title",
|
|
|
|
|
- "ProductTypeId": 141,
|
|
|
|
|
- "ArticleNumber": "1234",
|
|
|
|
|
- "MinCostForAgent": 100.50
|
|
|
|
|
- }
|
|
|
|
|
- ```
|
|
|
|
|
|
|
+ **Id** изменяемой продукции будем передавать в пути запроса (это не обязательно, но как пример)
|
|
|
|
|
|
|
|
* используем метод `PUT`
|
|
* используем метод `PUT`
|
|
|
* в пути передаём параметр id продукции `/product/227`
|
|
* в пути передаём параметр id продукции `/product/227`
|
|
@@ -611,18 +657,19 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
// без этой строки ругается, видимо если мы задействовали
|
|
// без этой строки ругается, видимо если мы задействовали
|
|
|
// метод Results, то обязаны его использовать при любом результате
|
|
// метод Results, то обязаны его использовать при любом результате
|
|
|
return Results.Ok();
|
|
return Results.Ok();
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ .WithTags("CRUD продукции")
|
|
|
|
|
+ .Produces(StatusCodes.Status200OK)
|
|
|
|
|
+ .Produces(StatusCodes.Status401Unauthorized)
|
|
|
|
|
+ .Produces(StatusCodes.Status404NotFound);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+ В **Swagger** протестируйте сами, тут уже ничего нового нет.
|
|
|
|
|
+
|
|
|
1. Удаление существующей продукции (**СRU****_Delete_**)
|
|
1. Удаление существующей продукции (**СRU****_Delete_**)
|
|
|
|
|
|
|
|
Тут, в принципе, должно быть уже понятно (метод `DELETE` с указанием `id` продукции в пути):
|
|
Тут, в принципе, должно быть уже понятно (метод `DELETE` с указанием `id` продукции в пути):
|
|
|
|
|
|
|
|
- ```
|
|
|
|
|
- ### удаление продукта
|
|
|
|
|
- DELETE http://localhost:8080/product/232
|
|
|
|
|
- ```
|
|
|
|
|
-
|
|
|
|
|
Реализация **конечной точки**:
|
|
Реализация **конечной точки**:
|
|
|
|
|
|
|
|
```cs
|
|
```cs
|
|
@@ -643,7 +690,11 @@ param1 = 1, param2 = 2, pageNum = 10, pageLen =
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return Results.Ok();
|
|
return Results.Ok();
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ .WithTags("CRUD продукции")
|
|
|
|
|
+ .Produces(StatusCodes.Status200OK)
|
|
|
|
|
+ .Produces(StatusCodes.Status401Unauthorized)
|
|
|
|
|
+ .Produces(StatusCodes.Status404NotFound);
|
|
|
```
|
|
```
|
|
|
<!--
|
|
<!--
|
|
|
авторизация
|
|
авторизация
|