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
}Ошибка, если класс используется как входящий параметр в эндпоинте. Соответственно, не стоит использовать, если десериализуем в него.
Либо
required, либоnullable.Надо выбрать одно из двух в зависимости от места использования.
Либо
Required, либоnullable. Тут дажеAllowEmptyStrings = trueне поможет.Requiredиспользуется для строк. Но есть нюанс (*).Не нужно использовать
requiredсо значением по умолчанию.Не стоит усложнять жизнь, если поле можно проинициализировать при создании класса.
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
}Хорошо где угодно за пределами эндпоинтов и десериализации, а значение не может принимать
null.То что нужно для эндпоинта.
Поле
nullable. Поэтому никакихrequired.Не используем атрибут
Requiredс не строками. Но есть нюанс (*).Избегаем использование
required, проинициализировав коллекцию.
* - если передаётся json, в котором явно указано значение null ({"Field4": null}), то использование атрибута Required вернёт BadRequest.
Если же в json поле было опущено, то будет присвоено значение по умолчанию.
Надеюсь, это поможет сделать код чище и избежать неоднозначностей.













