针对错误请求的 .NET Core Web Api 模式

.NET Core WepApi Pattern for Bad Requests

我正在使用 .NET Core 5 Web API,我正在尝试确定是否有比我当前处理 400 个错误请求的逻辑更好的模式。

类似这个问题- Best practice to return errors in ASP.NET Web API

下面是我的代码

    [HttpPost("register-customer")]
    [Produces("application/json")]
    [MapToApiVersion("1")]
    public async Task<ActionResult<IRegistrationStatus>> RegisterCustomer([FromBody] Customer customer)
    {
        try
        {
            if (customer == null)
                return StatusCode(400, "Request body is required");

            if (string.IsNullOrEmpty(customer.FirstName))
                return StatusCode(400, "First name is required");

            if (string.IsNullOrEmpty(customer.LastName))
                return StatusCode(400, "Last name is required");

            if (string.IsNullOrEmpty(customer.EmailAddress))
                return StatusCode(400, "Email address is required");

            if (customer.Roles == null || customer.Roles.Count == 0)
                return StatusCode(400, "At least one role is required");

            //call service to register
            return Ok(registrationStatus);
        }
        catch (Exception ex)
        {
            return StatusCode(500, ex.ToString());
        }
    }

所有代码都可以正常工作,但我想知道是否有更好的模式我可以为我要检查的每个道具使用多个 if 语句。例如,我还有其他 API 用于 RegisterEmployee,员工 class 是一个不同的模型,但它有一些相同的字段正在检查 400 - 我,e FirstName,LastName - 我不真的想改变模型以坐在后面说一个我想我可以做的人界面,然后员工和客户都会继承它——如果我这样做了,这对我的 APIs 的消费者来说可能是一个突破性的改变

您可以使用以下方式简化验证:

  • ApiController 控制器上的属性。它将强制 automatic HTTP 400 responses.
  • 在模型 dto 的成员上使用数据注释 class。强制 FirstName 不为空:
    public class Customer
    {
        [Required]
        public string FirstName { get; set; }

        public string LastName { get; set; }
    }

应用这两种技术,您可以省略 if 检查。

您应该使用 DataAnnotations 而不是一一检查每个字段。您可以 return 具有 400 的 ModelState。

其他解决方案之一是使用 Fluent Validation。这是一个可以在 Nuget Manager 上找到的包。

Api 控制器:

        [HttpPost]
        public async Task<ApiResponse> Create([FromBody] BPRPEmailDto bprpEmail)
        {
            BPRPEmail _bPRPEmailDto = _mapper.Map<BPRPEmail>(bprpEmail);

            ValidationResult result = _bprpEmailValidator.Validate(_bPRPEmailDto);
            if (!result.IsValid)
            {
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(error.ErrorCode, error.ErrorMessage);
                }
                throw new ApiProblemDetailsException(ModelState);
            }

            await _db.BPRPEmails.AddAsync(_bPRPEmailDto);
            await _db.SaveChangesAsync();
            return new ApiResponse("New record has been created in the database.", _mapper.Map<BPRPEmailDto>(_bPRPEmailDto), 201);
        }

这段代码有什么作用?控制器获取 BPRPEmailDto,然后将其映射到我们称为 BPRPEmail 的模型。之后它将此对象发送到 Fluent Validation。

 public class BPRPEmailValidator : AbstractValidator<BPRPEmail>
    {
        private readonly AppDbContext _db;

        public BPRPEmailValidator(AppDbContext db)
        {
            _db = db;
            RuleFor(x => x.Id);
            RuleFor(x => x.Email).NotEmpty().Must((model, Email) => BeUniqueEmail(model, Email)).WithMessage("An email should be unique in the database");
            RuleFor(x => x.Email).EmailAddress().WithMessage("Please enter a valid email adress");
            RuleFor(x => x.BPResponsiblePersonId).NotEmpty().WithMessage("An Email's ResponsiblePersonID is required in the database");
            RuleFor(x => x.BPResponsiblePersonId)
              .Must(BeRegisteredResponsiblePerson).WithMessage("An Email's ResponsiblePersonID should be a reqistered ResponsiblePerson in the database");
        }


        private bool BeUniqueEmail(BPRPEmail sCRPEmail, string email)
        {
            var dbEmail = _db.BPRPEmails.Where(x => x.Email == email).FirstOrDefault();
            if (dbEmail == null)
                return true;
            return sCRPEmail.Id == dbEmail.Id;
        }
        private bool BeRegisteredResponsiblePerson(int id)
        {
            if (id == 0)
                return true;
            if (_db.BPResponsiblePeople.Any(x => x.Id == id))
                return true;
            return false;
        }
    }

如果存在验证错误,我们将这些错误添加到 ModelState 中并抛出 ApiProblemDetailsException。在前端我们捕捉到这个响应

 try
        {
            BPRPEmailDto.ResponsiblePersonId = ResponsiblePersonId;
            var response = await BPResponsiblePeopleScreenUseCases.CreateEmailToBPRP(BPRPEmailDto);
            ShowNotification(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Insert", Detail = $"Email with id {response.Id} inserted successfully.", Duration = 4000 });
            isCreate = false;
            await _bprpEmailGrid.Reload();
        }
        catch (Exception e)
        {
            JsonElement JsonErrors = JsonSerializer.Deserialize<dynamic>(e.Message);
            if (JsonErrors.GetProperty("validationErrors").EnumerateArray().Count() != 0)
            {
                foreach (var error in JsonErrors.GetProperty("validationErrors").EnumerateArray())
                {
                    ShowNotification(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = error.GetProperty("name").ToString(), Detail = error.GetProperty("reason").ToString(), Duration = 4000 });
                }
            }
            else
            {
                ShowNotification(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "Error", Detail = JsonErrors.GetProperty("title").ToString(), Duration = 4000 });
            }
            await InsertRow(BPRPEmailDto);
        }

反序列化请求后,在 foreach 循环中我们可以向用户显示所有验证错误。

在我的示例中,我使用了 AutoMapper、AutoWrapper 和 Fluent Validation。我建议您学习如何使用 AutoMapper 和 AutoWrapper,但对于验证,您也可以使用 DataAnnotations。