Pull to refresh

Required или нет?

Работая над одним из проектов, который недавно переехал из Framework 4.8 на Core 9, обнаружил множество самых разных вариантов использования модификатора required и атрибута Required, примерно каждый второй из которых был использован неправильно. Я написал это коллегам и хочу поделиться этим здесь. Это не обязательные правила, но сильно упрощают работу с кодом.

Небольшое пояснение

Атрибут Required нужен для проверки входящих преимущественно строковых данных в эндпоинтах. Возвращает ошибку, если значение null или пустая строка для строк (если не отключено параметром AllowEmptyStrings). Работает в Runtime. Также применяется в Entity Framework в подходе code-first но с включением опции <Nullable> в csproj про эти случаи можно забыть, сделав код чище.

Модификатор required нужен для обязательного указания значений полей при создании класса. Работает в Compile-time.

Примеры использования

// имеем класс с required полем
public class Example
{
    public required string Name { get; set; }
}

// пытаемся создать экземпляр в коде
var example1 = new Example();  // будет ошибка при попытке сборки проекта
var example2 = new Example { Name = string.Empty };  // тут ошибки не будет

// Вывод: модификатор required нужен для разработчика
// имеем класс с полем, у которого атрибут Required
public class Example
{
    [Required]
    public string Name { get; set; }
}

// пытаемся создать экземпляр в коде
var example = new Example();  // проект спокойно собирается

// имеем эндпоинт в контроллере
public IActionResult PostMethod([FromBody] Example model) => Ok();

/* передаём в теле запроса:
{}
или
{"Name": null}
или
{"Name": ""}
или
{"Name": "   "}
Получаем BadRequest с текстом ошибки. */

// передаём в теле запроса: {"Name": "name"}. Получаем OK.

// Вывод: атрибут Required нужен для пользователя

Как стоит и не стоит использовать.

public class BadExample
{
    public required string Field1 { get; set; } // 1
    
    public required string? Field2 { get; set; } // 2
    
    [Required]
    public required string Field3 { get; set; } // 3

    [Required]
    public string? Field4 { get; set; } // 4

    [Required]
    public int Field5 { get; set; } // 5

    public required int Field6 { get; set; } = 10; // 6
      
    public required List<int> Field7 { get; set; } // 7
}
  1. Ошибка, если класс используется как входящий параметр в эндпоинте. Соответственно, не стоит использовать, если десериализуем в него.

  2. Либо required, либо nullable.

  3. Надо выбрать одно из двух в зависимости от места использования.

  4. Либо Required, либо nullable. Тут даже AllowEmptyStrings = true не поможет.

  5. Required используется для строк. Но есть нюанс (*).

  6. Не нужно использовать required со значением по умолчанию.

  7. Не стоит усложнять жизнь, если поле можно проинициализировать при создании класса.

public class GoodExample
{
    public required string Field1 { get; set; } // 1
    
    [Required]
    public string Field2 { get; set; } = null!; // 2
    
    public string? Field3 { get; set; } // 3

    public int Field4 { get; set; } // 4

    public List<string> Field5 { get; set; } = []; // 5
}
  1. Хорошо где угодно за пределами эндпоинтов и десериализации, а значение не может принимать null.

  2. То что нужно для эндпоинта.

  3. Поле nullable. Поэтому никаких required.

  4. Не используем атрибут Required с не строками. Но есть нюанс (*).

  5. Избегаем использование required, проинициализировав коллекцию.

* - если передаётся json, в котором явно указано значение null ({"Field4": null}), то использование атрибута Required вернёт BadRequest.
Если же в json поле было опущено, то будет присвоено значение по умолчанию.

Надеюсь, это поможет сделать код чище и избежать неоднозначностей.

Tags:
+1
Comments0

Articles