Бекенд на C# Web Api
Controllers, Actions, Services
В C# WebAPI контроллеры — это классы, которые обрабатывают HTTP-запросы и возвращают ответы. Они предоставляют интерфейс для взаимодействия с вашим приложением через REST API. Контроллеры наследуются от класса ControllerBase и используются для обработки различных типов запросов, таких как GET, POST, PUT, DELETE и другие.
В контексте ASP.NET Core Web API, Actions (действия) — это методы, которые обрабатывают HTTP-запросы и возвращают HTTP-ответы. Эти методы определяются в контроллерах и отвечают за логику взаимодействия с клиентами API, такие как получение данных, создание новых записей, обновление или удаление ресурсов.


Имя класса контроллера должно заканчиваться словом Controller. Сам класс должен иметь атрибуты ApiController и Route. Пример контроллера представлен ниже.
using Microsoft.AspNetCore.Mvc;
namespace DemoWebApi.Controllers
{
[ApiController]
[Route("api/account")]
public class AccountController : ControllerBase
{
[HttpPost("registration")]
public async Task<IActionResult> Login()
{ ... }
[HttpGet("login/refresh")]
public async Task<IActionResult> Refresh()
{ ... }
}
}В этом контроллере объявлено два метода (Action'а). В атрибуте Route указан маршрут до контроллера. В результате, данный контроллер описывает следующий API:
POST https://example-service.ru/api/account/registration
GET https://example-service.ru/api/account/login/refresh
Существует несколько способов передачи параметров. Ниже приведено несколько вариантов того, как action'ы описывают получение параметров.
using Entities.Models;
using Microsoft.AspNetCore.Mvc;
namespace DemoWebApi.Controllers
{
[ApiController]
[Route("api/profile/{playerId}")]
public class ProfileController : ControllerBase
{
// /api/profile/player123
[HttpGet]
public async Task<PlayerProfile> GetPlayerProfile(string playerId)
{ ... }
// /api/profile/player123?count=10&type=all
[HttpGet]
public async Task<PlayerProfile> GetAchivements(string playerId, [FromQuery] string type, [FromQuery] int count)
{ ... }
// /api/profile/player123/upload-avatar/avatar123
[HttpPut("upload-avatar/{avatarId}")]
public async Task<IActionResult> UploadAvatar(string playerId, string avatarId)
{ ... }
// /api/profile/player123/update
[HttpPost("update")]
public async Task<IActionResult> Update(string playerId, [FromBody] PlayerProfileDto profile)
{ ... }
}
}Из Route контроллера (все методы) — в фигурных скобках указано название параметра. Этот параметр передается во все action'ы контроллера.
Query параметры (метод GetAchivements) — с помощью атрибута
FromQueryобозначаются параметры, которые передаются в URL строке после знака "?".Из Route Action'а (метод UploadAvatar) — в фигурных скобках указывается название параметра, которое передается только в текущий action.
Из тела запроса (метод Update) — с помощью атрибута
FromBodyобозначаются параметры, которые передаются в теле запроса. Можно создавать кастомные структуры и указывать их для передачи. Такие структуры должны передаваться в формате json.
В большинстве случаев достаточно будет возвращать тип IActionResult. Это интерфейс, позволяющий возвращать один из следующих предопределенных типов:
OK() — возвращает успешный HTTP-ответ с кодом 200 (OK).
BadRequest() — возвращает код 400 (Bad Request), если запрос некорректен.
NotFound() — возвращает код 404 (Not Found), если ресурс не найден.
NoContent() — возвращает код 204 (No Content) без содержимого.
Created(), CreatedAtRoute(), CreatedAtAction() — возвращает код 201 (Created) с URI созданного ресурса.
Unauthorized() — возвращает код 401 (Unauthorized), если у пользователя нет доступа.
Forbid() — возвращает код 403 (Forbidden), если запрос запрещен.
StatusCode() — возвращает код, который передается в параметры метода.
Ниже приведен пример action'а, который проверяет входящие параметры и возвращает Ok или BadRequest.
[HttpPost("update")]
public async Task<IActionResult> Update(string playerId, [FromBody] PlayerProfileDto profile)
{
if (profile == null)
return BadRequest("Profile object is null");
if (ModelState.IsValid == false)
return BadRequest("Invalid profile object");
_service.Register(profile);
return Ok();
}Если нужно вернуть какой-то объект, то можно передать его в метод Ok(), либо воспользоваться возвращаемым типом ActionResult<T> или кастомным типом. При использовании кастомного типа не получится воспользоваться методами Ok, BadRequest и др. Ниже приведен пример трех одинаковых методов, которые по разному возвращают значения: с помощью IActionResult, с помощью ActionResult<T> и с помощью кастомного типа.
// Использование IActionResult
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginCredentialsDto loginData)
{
var loginTokens = await _service.Login(loginData);
if (loginTokens == null)
return BadRequest("Invalid credentials");
return Ok(loginTokens);
}
// Использование ActionResult<T> (аналогично IActionResult)
[HttpPost("login")]
public async Task<ActionResult<LoginTokensDto>> Login([FromBody] LoginCredentialsDto loginData)
{
var loginTokens = await _service.Login(loginData);
if (loginTokens == null)
return BadRequest("Invalid credentials");
return Ok(loginTokens);
}
// Использование кастомного типа (нельзя пользоваться методами Ok, BadRequest и т.п.)
[HttpPost("login")]
public async Task<LoginTokensDto> Login([FromBody] LoginCredentialsDto loginData)
{
var loginTokens = await _service.Login(loginData);
//if (loginTokens == null)
// return BadRequest("Invalid credentials"); // ошибка: нельзя вернуть BadRequest
return loginTokens; // возвращаем сразу объект
}Вся основная логика обработки запросов и работы с базой данных должна происходить в сервисах. Сервисы — это классы, которые предоставляют конкретные функциональные возможности и используются для выполнения бизнес-логики, работы с данными, взаимодействия с внешними системами и других задач. Сервисы позволяют разделить ответственность, что делает код более модульным и облегчает повторное использование.
Контроллеры должны иметь ссылку на экземпляр сервиса. Эта ссылка передается в конструктор с помощью механизма внедрения зависимости (DI).
[ApiController]
[Route("api/profile/{playerId}")]
public class ProfileController : ControllerBase
{
private readonly IProfileService service;
// получаем зависимость в конструкторе
public ProfileController(IProfileService service)
{
service = service;
}
[HttpPost("test")]
public async Task<IActionResult> Test(string playerId)
{
var result = await _service.SomeMethod(playerId); // используем сервис
return Ok(result);
}
}Класс контроллера должен быть максимально чистым и в нем не должно быть никаких лишних методов кроме
Action'ов.Сами
Action'ытакже должны быть максимально чистыми и не реализовывать никакой сложной логики. Вся бизнес логика должна находиться в других проектах (Service, Repository). ЗадачаAction'а— обработка HTTP-запросов, проверка моделей, отслеживание ошибок и возврат ответов. В большинстве случаев рекомендуется возвращатьIActionResult.Используйте асинхронный код. Все
Action'ыдолжны возвращатьTask<T>, методы сервисов и запросы к базе данных также должны быть по возможности асинхронными.Если нужно передать список параметров, либо число передаваемых параметров большое, то вынесете их в отдельный тип и передавайте в теле запроса.
Разбивайте контроллеры логически на разные классы и проектируйте чистый и понятный API.
Для именования маршрутов и endpoint'ов используйте существительные, а не глаголы, а также используйте kebab-case (слова разделяются дефисами).
Tutorials Teacher — Web Api Controllers
Code Maze — ASP.NET Core Web API Best Practices
Автор документа: Артём Ветик