#c# #.net-core #asp.net-core-webapi
#c# #.net-ядро #asp.net-core-webapi
Вопрос:
Я создаю проект .NET Core WEB API вместе с Entity Framework, используя подход Code First. У меня проблемы с проверкой входных данных из запросов, поскольку проверка ModelState всегда верна.
Мое приложение состоит из 3 уровней.
- Уровень доступа к данным
- Уровень бизнес-логики
- .NET Core API
Пример DataModel в DAL:
public class Group
{
[Key]
[Required]
public long GroupId { get; set; }
[Required]
public string Name { get; set; }
[Required(AllowEmptyStrings = false)]
public string Description { get; set; }
public DateTime CreationDate { get; set; }
public bool IsActive { get; set; }
}
Соответствующий DTO:
public class GroupDto
{
public long GroupId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Метод контроллера:
[HttpPost]
public IActionResult Post([FromBody] GroupDto groupDto)
{
Group group = _mapper.Map<Group>(groupDto);
if (!ModelState.IsValid)
{
return BadRequest();
}
_groupService.Add(group);
groupDto = _mapper.Map<GroupDto>(group);
return Ok(groupDto);
}
Насколько я понимаю, в текущем состоянии ModelState.isValid всегда будет возвращать true, поскольку GroupDto не имеет никаких проверок, выполняемых с помощью DataAnnotations.
Как DTO должны быть проверены? Я бы хотел избежать повторения одних и тех же проверок в двух местах. Должен ли быть создан дополнительный пользовательский DtoValidator или я что-то упускаю, и есть способ выполнить эти проверки.
Комментарии:
1. Вам нужно будет повторить правила. DTO не обязательно будет иметь идентичные правила, что и база данных. Подумайте, преобразует ли ваше приложение пользовательский ввод во внутреннее значение, доступное только для базы данных. Подумайте, не имеет ли ваш DTO сопоставления 1: 1 с базовыми полями объектов.
Ответ №1:
Проверка состояния модели будет происходить в передаваемой модели, которая в вашем случае является GroupDto. То, что вы в конечном итоге сопоставляетесь с классом Group, не имеет никакого отношения к тому, как работает проверка. Вам нужно будет повторить атрибуты проверки в DTO. Это дублирует код, но также позволяет вам настраивать правила, поскольку вы можете захотеть, а можете и не захотеть, чтобы в DTO был точно такой же набор. Примером этого является ваш первичный ключ. Для создания (POST) вы не обязательно хотите, чтобы groupId был обязательным полем для передачи контроллеру, поскольку база данных может автоматически генерировать это поле (в зависимости от ваших настроек).
Если вы используете ASP.Net В ядре 2.1 или более поздней версии вы также можете применить атрибут [ApiController] к классу контроллера, и он автоматически применит правила проверки модели. Это устранило необходимость вручную проверять ModelState.Является действительным. Система автоматически вернет 400 ошибочных запросов, если модель недействительна. ( https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.2#annotation-with-apicontroller-attribute )
Комментарии:
1. Я совершенно не знал об этой функциональности [ApiController], спасибо за это! Из того, что вы упомянули, я понимаю, что я должен добавить соответствующие аннотации данных к моим классам DTO, и это было бы первым шагом проверки. Но как и где должна происходить проверка DataModel? Потенциально это может произойти в BLL / Services, когда они получают объекты такого рода, правильно ли я понимаю? Если это так, должен ли я создать методы проверки для этих объектов или есть что-нибудь похожее на ModelState. Есть ли конструкция VALID, которую я мог бы выполнить для этих объектов?
2. Если вы не используете путь к контроллеру Api 2.1, тогда просто используйте ! ModelState. Проверка правильности в контроллере, как у вас в вашем примере выше. Вы можете переместить вызов Map ниже проверки ModelState, поскольку вам не нужно выполнять Map, если ModelState не является допустимым. Если у вас есть атрибуты проверки в вашем Dto, проверка того, является ли ModelState VALID, — это все, что нужно сделать. Нет необходимости делать это ниже в BLL. Если вам нужна гибкость при выполнении проверки на многих уровнях вашего приложения, часто проще реализовать ее с помощью отдельной библиотеки, такой как FluentValidation.
3. Я добавляю [Обязательно] в DTO, но это не работает. У вас есть какой-нибудь пример?
Ответ №2:
DTO и модели — это разные вещи, поэтому атрибут DataAnnotation должен быть создан в них обоих. Объект, проверенный «ModelState.isValid» — это входные данные функции, над которой вы работаете. Таким образом, в вашем коде проверяемой моделью является GroupDto.
[HttpPost]
public IActionResult Post([FromBody] GroupDto groupDto)
{
Group group = _mapper.Map<Group>(groupDto);
if (!ModelState.IsValid) // this line will check the validation inside GroupDto
В любом случае, чтобы сделать «проверку модели» чистой в контроллере, я бы предложил использовать пользовательский атрибут ValidateModelAttribute, подобный этому.
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace MyWeb.App.Filter
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( HttpActionContext actionContext )
{
if( !actionContext.ModelState.IsValid )
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState );
// Path to the error in request content will be sent to the client, so helpful!
}
}
}
}
Затем вы можете записать проверку на контроллере следующим образом.
[Route( "" )]
[HttpPost]
[ValidateModel] // here you attach the ValidateModelAttribute
[ResponseType( typeof( GroupDto ) )] // Make sure the model is written in Swagger UI
public IHttpActionResult Create( InputDto inputDto )
{
_groupService.Add(group);
groupDto = _mapper.Map<GroupDto>(group);
return Ok(groupDto);
}
Надеюсь, это ответ на вопрос.