如何(以及在​​何处)针对数据库验证 API 输入

How (and where) to validate API input against a database

我正在创建一个 dotnet core 3.1 API 以在现有系统中工作。 [dbo].[MyTable] table 有两个具有 UNIQUE 约束的字段:[ID] 和 [HumanReadableID]。 [ID] 是一个 GUID,将在发布新资源时由 API 确定。 [HumanReadableID] 必须由用户确定,因此 API 在执行插入之前必须首先通过查询数据库来确保其唯一性。

我尝试使用自定义属性进行此验证,并在控制器方法接受的 DTO 上实现 IValidatableObject,但我无法将我的存储库注入这两个对象中的任何一个.

目前,控制器尝试使用存储库进行插入,如果 [HumanReadableID] 不唯一,则会抛出自定义异常,然后 . . .这是代码

[Route("[controller]")]
[ApiController]
public class MyThingsController : ControllerBase
{

   ///  Other methods

   [HttpPost(Name = "CreateMyThing")]
   public async Task<IActionResult> CreateMyThing(MyThingCreateDto myThingToCreate)
   {
       try
       {
            MyThingEntity entityToCreate = _mapper.Map<MyThingEntity>(myThingToCreate);
            MyThingEntity entityToReturn = await _myRepository.InsertThing(entityToCreate);
            MyThingDto myThingToReturn = _mapper.Map<MyThingDto>(entityToReturn);

            return CreatedAtRoute(
                "GetMyThing",
                new {id = myThingToReturn.Id},
                myThingToReturn);
       }
       catch (DuplicateHumanReadableIdException e)
       {
           ModelState.AddModelError("HumanReadableID",$"HumanReadableID {entityToCreate.HumanReadableId} is already used by site {e.IdOfThingWithHumanReadableId}");
           return Conflict(ModelState);
       }
   }
}

这部分有效,但由此产生的错误的内容类型为 application/json 而不是 application/problem+json。此外,这感觉就像控制器在这里承担了太多责任。

我使用 IValidatableObject 或 DataAnnotations 进行验证的实现是什么?如果做不到这一点,我必须进一步操作控制器的 ModelState 中的哪些内容才能使其发出符合标准的谴责?

如何实现IValidatableObject,您可以关注:

public class ValidatableModel : IValidatableObject
{
    public int Id { get; set; }
    public int HumanReadableID { get; set; }
    public string Name { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var service = validationContext.GetService<ICheckHumanReadableID>();
        var flag = service.IfExsitHumanId(((ValidatableModel)validationContext.ObjectInstance).HumanReadableID);
        if(flag)
        {
            yield return new ValidationResult(
               $"The HumanReadableID is not unique.",
               new[] { nameof(HumanReadableID) });
        }           
    }
}

型号:

public class Human
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

服务:

public interface ICheckHumanReadableID
{
    bool IfExsitHumanId(int id);
}

public class CheckHumanReadableID : ICheckHumanReadableID
{
    private readonly YourDbContext _context;
    public CheckHumanReadableID(YourDbContext context)
    {
        _context = context;
    }
    public bool IfExsitHumanId(int id)
    {
        var list = _context.Human.Select(a => a.Id).ToList();
        var flag = list.Contains(id);
        return flag;
    }
}

测试方法:

[HttpPost]
public async Task<ActionResult<ValidatableModel>> PostValidatableModel([FromForm]ValidatableModel validatableModel)
{
    if(ModelState.IsValid)
    {
        _context.ValidatableModel.Add(validatableModel);
        await _context.SaveChangesAsync();
        return CreatedAtAction("GetValidatableModel", new { id = validatableModel.Id }, validatableModel);

    }
    return BadRequest(ModelState);
}

数据库上下文:

public class YourDbContext: DbContext
{
    public YourDbContext(DbContextOptions<YourDbContext> options)
        : base(options)
    {
    }

    public DbSet<ValidatableModel> ValidatableModel { get; set; }
    public DbSet<Human> Human { get; set; }
}

Startup.cs:

services.AddDbContext<YourDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("YourConnectionString")));
services.AddScoped<ICheckHumanReadableID, CheckHumanReadableID>();

结果: