API на ASP.NET

Настройка запросов в ASP.NET API: атрибуты HttpGet/HttpPost, маршруты, query-параметры и свои методы контроллера

В прошлой лекции мы с вами сгенерировали рабочее API. Он генерирует 5 дефолтных запросов для каждой таблицы. Но если у нас появляется нужда в создании своих запросов или изменении существующих, нам нужно понять как работают контроллеры.

Структура контроллера

Возьмем API с прошлой пары и откроем контроллер человека — HumenController. Начнем сверху.

namespace MyTestApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class HumenController : ControllerBase
{
private readonly ExampleDBContext _context;
public HumenController(ExampleDBContext context)
{
_context = context;
}
// ...
}
}

Первое что мы видим — штуки в квадратных скобках. Они называются атрибуты. Подобные вещи мы видели, когда писали тесты. Напомню, атрибуты — надстройки над кодом, которые определяют её поведение. Так:

  • Route — говорит по какому URL будет доступно это API.
  • ApiController — говорит, что это не просто класс, а класс для API.

Потом мы видим переменную _context. В ней хранится вся БД и через неё мы можем обращаться ко всем табличкам как коллекциям (List<Human>, List<Colour> и прочее). А чтобы переменная не была пустой, ей автоматически дается значение в public HumenController.

Атрибут HttpGet и возврат данных

Каждый запрос — отдельный метод с нужным атрибутом. Например GET запрос.

// GET: api/Humen
[HttpGet]
public async Task<ActionResult<IEnumerable<Human>>> GetHumans()
{
if (_context.Humans == null)
{
return NotFound();
}
return await _context.Humans.ToListAsync();
}

Он помечен атрибутом HttpGet и возвращает коллекцию с Human, которая потом автоматом преобразуется в JSON. Название метода тут не играет роли.

Внутри из переменной _context метод спокойно берёт таблицу Humans и возвращает все данные из неё. Вернуть метод может не только данные, но и какой-нибудь статус-код, как например, 404 — NotFound.

(Таких методов кстати очень много — Ok, BadRequest, NoContent и прочее. Какой они возвращают статус-код вы можете узнать, наведясь на сам метод.)

IntelliSense у return NotFound(); — подсказка «NotFoundResult ControllerBase.NotFound() … Creates an NotFoundResult that produces a Status404NotFound response» — Status404NotFound подчёркнуто

GET по ID и параметры в маршруте

Чуть ниже есть GET запрос по Id. В комментарии показано как к нему нужно обращаться — api/Humen/5.

// GET: api/Humen/5
[HttpGet("{id}")]
public async Task<ActionResult<Human>> GetHuman(int id)
{
if (_context.Humans == null)
{
return NotFound();
}
var human = await _context.Humans.FindAsync(id);
if (human == null)
{
return NotFound();
}
return human;
}

Здесь мы уже видим отличия. Кроме как кода, который ищет запись по id и, если он её нашел, возвращает её (а если не нашел — статус-код 404), мы ещё видим, что в атрибуте HttpGet появились круглые скобки с "{id}" внутри, и появилась переменная int id в методе. Как они связаны?

Когда в запросе (не важно, HttpGet, HttpPost, HttpPut и прочее) появляются круглые скобки с текстом внутри, они перезаписывают обращение к этому методу. В этом случае мы уже видим, что к дефолтному api/Humen добавляется число.

Swagger: голубая карточка GET для маршрута api/Humen с параметром id в фигурных скобках

Если я хочу поменять обращение, я сделаю вот так:

[HttpGet("getbyid/{id}")]
public async Task<ActionResult<Human>> GetHuman(int id)

Запрос будет выглядеть вот так:

Swagger: голубая карточка GET для маршрута api/Humen/getbyid с параметром id в фигурных скобках

Я поставлю слеш перед getbyid:

[HttpGet("/getbyid/{id}")]

И то, что было до этого (api/Humen) полностью уберется.

Swagger: голубая карточка GET для маршрута /getbyid с параметром id — теперь без префикса api/Humen

Также заметим, что в этих фигурных скобках написано название нашей переменной. Соответственно, если я её поменяю, то и в запросе её также нужно поменять.

Атрибут HttpGet с маршрутом /getbyid и параметром superHumanId в фигурных скобках, и метод GetHuman(int superHumanId) — оранжевая стрелка между переменными

Обращение всё равно каким было, так и останется — https://localhost:порт/getbyid/5.

Swagger: GET для маршрута /getbyid с параметром superHumanId в фигурных скобках

Query-параметры

А вот если я вовсе уберу этот {переменная} из моего пути, тогда обращение поменяется.

[HttpGet("/getbyid/")]
public async Task<ActionResult<Human>> GetHuman(int superHumanId)

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

Swagger: GET /getbyid с полем superHumanId integer query со значением 3, Request URL https://localhost:7242/getbyid?superHumanId=3 — подсвечено оранжевой рамкой

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

  • https://youtu.be/dQw4w9WgXcQ — это обычная ссылка на видео.
  • https://youtu.be/dQw4w9WgXcQ?t=43 — это ссылка с таймкодом — необязательная вещь, открывает всю ту же страницу, но если данные были переданы, тогда происходит что-то дополнительно.

В случае, если ссылка та же, но мы хотим вывести вообще другое, лучше использовать ссылка/данные. Если ввод этих данных опционален, мы делаем ссылка?переменная=данные. Ещё полезно, когда переменных много.

SaveChangesAsync для изменений

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

Код _context.Humans.Remove(human); await _context.SaveChangesAsync(); — оранжевое подчёркивание SaveChangesAsync

Эта строчка должна быть в конце каждого запроса, если мы как-либо изменяем данные. Так они сохранятся в БД.

Свой запрос в контроллере

На основе этих знаний мы можем создать свои методы в контроллере — свои запросы. Вот скажем я хочу сделать запрос, который возьмет всех людей, которые любят какой-то цвет, который я им передам. Если я хочу получить данные, ничего зашифрованного отправлять не собираюсь, это будет Get запрос. Создам такой метод где-то ниже. Метод будет мне возвращать коллекцию с людьми, так как людей, которые любят, например, зеленый цвет, может быть много.

[HttpGet]
public async Task<ActionResult<List<Human>>> GetHumansByColour()
{
}

Добавлю интовую переменную, в которую я буду передавать айдишник цвета.

GetHumansByColour(int colourId)

В HttpGet напишу путь до этого запроса.

[HttpGet("GetByColourId/{colourId}")]
public async Task<ActionResult<List<Human>>> GetHumansByColour(int colourId)
{
}

И напишу код.

public async Task<ActionResult<List<Human>>> GetHumansByColour(int colourId)
{
if (_context.Humans == null) // если записей в таблице нет вовсе
{
return NotFound(); // вернуть 404
}
var humans = _context.Humans.ToList().Where(x => // ищу все записи в таблице,
x.ColourId == colourId); // где ColourId равен тому, что я передала
if (humans == null || humans?.Count() == 0) // если и тут ничего нет
{
return NotFound(); // вернуть 404
}
return Ok(humans); // возвращаю лист с статусом 200 — всё ок
}

Запущу API чтобы посмотреть, как будет работать мой метод. Во-первых я сразу вижу, что мой метод появился в Swagger.

Раздел Humen в Swagger: GET/POST/GET/PUT/DELETE стандартные эндпоинты, плюс снизу выделенный оранжевыми скобками GET для маршрута api/Humen/GetByColourId с параметром colourId

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

Swagger: GET для маршрута api/Humen/GetByColourId с параметром colourId равным 1, Request URL https://localhost:7242/api/Humen/GetByColourId/1, Response body содержит id 3, name «Василиса», colourId 1

Полный код примера

Controllers/HumenController.cs — стандартные запросы плюс свой GetByColourId:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyTestApi.Models;
namespace MyTestApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class HumenController : ControllerBase
{
private readonly ExampleDBContext _context;
public HumenController(ExampleDBContext context)
{
_context = context;
}
// GET: api/Humen
[HttpGet]
public async Task<ActionResult<IEnumerable<Human>>> GetHumans()
{
if (_context.Humans == null)
return NotFound();
return await _context.Humans.ToListAsync();
}
// GET: api/Humen/5
[HttpGet("{id}")]
public async Task<ActionResult<Human>> GetHuman(int id)
{
if (_context.Humans == null)
return NotFound();
var human = await _context.Humans.FindAsync(id);
if (human == null)
return NotFound();
return human;
}
// DELETE: api/Humen/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteHuman(int id)
{
var human = await _context.Humans.FindAsync(id);
if (human == null)
return NotFound();
_context.Humans.Remove(human);
await _context.SaveChangesAsync();
return NoContent();
}
// Свой запрос: GET api/Humen/GetByColourId/2
[HttpGet("GetByColourId/{colourId}")]
public async Task<ActionResult<List<Human>>> GetHumansByColour(int colourId)
{
if (_context.Humans == null)
return NotFound();
var humans = _context.Humans.ToList().Where(x => x.ColourId == colourId);
if (humans == null || humans?.Count() == 0)
return NotFound();
return Ok(humans);
}
}
}
просмотров