ASP.NET Core — создание API, манипуляция данными, валидация

  • Михаил
  • 12 мин. на прочтение
  • 163
  • 20 Jun 2022
  • 20 Jun 2022

https://flash2048.com/post/asp-net-core-creating-apis-data-manipulation-validation

В продолжении прошлой статьи будем дальше рассматривать возможности создания API используя ASP.NET Core. Разберёмся каким образом можно работать с операциями добавления, изменения и удаления данных. А также научимся работать с валидацией.

Добавлю метод CreateUser, который будет позволять добавлять нового пользователя. Работу с базой данной всё еще не рассматриваем, поэтому манипуляции будут проводиться в классе UsersData. Проверку на корректность данных пока не добавляем.

[HttpPost]
public IActionResult CreateUser([FromBody] User user)
{
    if (user == null)
    {
        return BadRequest();
    }
    user.Id = UsersData.Users.Max(x=>x.Id)+1;
    UsersData.Users.Add(user);
    return CreatedAtRoute("GetUser", new {id = user.Id}, user);
}

Для добавления будем использовать HttpPost метод. Адрес будет совпадать с маршрутом заданным для контроллера, в данном случае это /api/users Здесь стоит обратить внимание на атрибут [FromBody], он показывает, что данные берутся из тела запроса. Например, как в данном случае, из переданных JSON данных. Можно дополнительно применять следующие атрибуты для определения источника данных:

[FromHeader] — данные берутся из заголовков запроса

public IActionResult GetContentType([FromHeader(Name = "Content-Type")] string contentType)
{
    return Content(contentType);
}

[FromQuery] — данные берутся из строки запроса. Например, для подобного запроса /api/users/1?useCache=false мы можем использовать метод вида:

[HttpGet("{id}", Name = "GetUser")]
public IActionResult GetUser(int id, [FromQuery] bool useCache = true)
{
    var user = GetUserData(id, useCache);
    if (user == null)
    {
        return NotFound("User not found");
    }
    return Ok(user);
}

[FromRoute] — данные берутся из значений маршрута. Как можно догадаться, значения заполняются, непосредственно, из самого маршрута. Как было для идентификатора пользователя в примере выше.
[FromForm] — данные берутся из полученных форм
[FromServices] — данные берутся из настроенного IOC контейнера.

Помимо использования данных атрибутов в самих методах, их можно применять к свойствам модели (к свойствам допускается применять не все атрибуты). Например:

[FromQuery]
public string Phone { get; set; }

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

[System.Runtime.Serialization.IgnoreDataMember]
public int Id { get; set; }

Тогда при сериализации данного поля оно будет исключено:

Использование CreatedAtRoute в Core MVC, пример Headers в ответе

Метод CreatedAtRoute

При использовании данного метода, вы получаете ответ с 201 кодом (Created). В Headers в параметр Location добавляется адрес метода. Вы можете задать имя метода, для формирования корректного URL, например, для CreatedAtRoute("GetUser", new {id = user.Id}, user) Используется метод GetUser, помеченный тегом [HttpGet("{id}", Name = "GetUser")]

Получение данных GET методом в Core MVC

Валидация данных

При передаче вами данных в API они могут быть не корректными и их нужно проверять. Делать это можно различными способами, рассмотрим один из самых распространённых — использование тегов и состояния ModelState С помощью тегов можно задавать различные ограничения, как на обязательность полей, так и на их размер и тип данных. Вот наиболее распространённые атрибуты, которые можно использовать:

  • [Required] — помечает поле как обязательное. Если поле не заполнено, модель считается не валидной.
  • [CreditCard] — проверяет данные на соответствие номеру кредитной карты.
  • [Compare] — позволяет сравнить помеченное поле с другим. Применяется, например, когда необходимо сравнить введённый пароль и его подтверждение.
  • [EmailAddress] — проверяет на соответствие email адресу
  • [Phone] — проверяет на соответствие телефону
  • [Range] — проверяет числовое значение на соответствие заданному диапазону. Позволяет задавать минимальное и максимально возможное допустимое значение.
  • [RegularExpression] — проверка соответствия полученных данные записанным регулярным выражением. Например, можно проверять соответствие какой-то определённой маске ввода, как номер телефона в определённом формате, или IP адрес.
  • [StringLength] — позволяет задать допустимую длину строки. Можно задать как максимальное, так и минимально допустимое значение.
  • [Url] — проверяет введённую строку на соответствие URL адресу.

В теле метода, состояние ModelState.IsValid определяет, валидна модель данных или нет. Например, при добавлении нового пользователя определить обязательность полей Name и Phone:

public class User
{
    public int Id { get; set; }
    [Required]
    public string Name;
    [Required]
    public string Phone { get; set; }
}

В методе CreateUser будем проверять состояние модели и если оно не валидно, возвращать BadRequest()

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

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

Ошибка валидации данных при добавлении новой сущности в API ASP.NET Core

Помимо автоматической обработки через теги, вы можете изменять состояние ModelState непосредственно в коде. Например, проверка на наличие телефона может выглядеть следующим образом:

if (string.IsNullOrEmpty(user.Phone))
{
    ModelState.AddModelError("Phone", "Телефонный номер является обязательным полем!");
}
if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

Результат можно увидеть ниже:

Добавление ошибки валидации модели через код в API ASP.NET Core

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

Выбор подходящего HTTP метода

Если мы говорим про манипуляции данными, то для грамотно построенного API важно, какой тип HTTP запроса можно использовать. Большинство возможностей можно реализовать используя принцип аббревиатуры CRUD (Create, Read, Update, Delete) При таком подходе функции API можно помечать аттрибутами:

C[HttpPost] — для добавления новой сущности.

R[HttpGet] — для чтения данных сущности.

U[HttpPut] — для обновление данных существующей сущности

D[HttpDelete] — для удаление сущности

Также, можно использовать [HttpPatch] — обычно используется, когда обновляются отдельные данные сущности явным образом. Необходимый синтаксис определён в спецификации, пример можно увидеть здесь. В запросе используются данные вида:

{ "op": "add", "path": "/a/b/c", "value": "foo" }

Где:

  • "op" – определяет тип операции. Например, add, replace, test
  • "path" – определяет свойство, которое необходимо изменить.
  • "value" – определяет новое значение для указанного поля.

Также, при манипуляции данными, порой бывает полезно указать несколько доступных типов HTTP запросов. Например, использовав один метод для добавления новых данных и для обновления существующих. Для этого используется атрибут [AcceptVerbs]

[AcceptVerbs("POST", "PUT")]
public IActionResult CreateUser([FromBody] User user)
{
    if (user == null)
    {
        return BadRequest();
    }
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    //Manipulations with datas
    
    return CreatedAtRoute("GetUser", new {id = user.Id}, user);
}

Итог:

В ASP.NET Core присутствует необходимый функционал для создания качественных и сложных API. Для тех кто работал с прошлыми версиями ASP.NET не возникнет никаких сложностей, присутствуют необходимые атрибуты для валидации данных, можно использовать состояние ModelState. Для всех методов можно указать какой тип HTTP запроса можно использовать, обращаясь к ему, задавая только один, используя атрибут Http[Verb] или перечислив все разрешённые в [AcceptVerbs]

Приятного программирования.