C# 如何(以及在何处)根据数据库验证API输入

C# 如何(以及在何处)根据数据库验证API输入,c#,validation,asp.net-core,asp.net-apicontroller,C#,Validation,Asp.net Core,Asp.net Apicontroller,我正在创建一个dotnet core 3.1 API,以便在现有系统中工作。[dbo].[MyTable]表有两个具有唯一约束的字段:[ID]&[HumanReadableID]。[ID]是在发布新资源时由API确定的GUID。[HumanReadableID]必须由用户确定,因此API必须首先通过在执行插入之前查询数据库来确保其唯一性 我曾尝试使用自定义属性进行此验证,并在controller方法接受的DTO上实现IValidatableObject,但我无法将我的存储库注入这两个对象中的任何

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

我曾尝试使用自定义属性进行此验证,并在controller方法接受的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);
       }
   }
}
[路由(“[控制器]”)]
[ApiController]
公共类控制器:ControllerBase
{
///其他方法
[HttpPost(Name=“CreateMyThing”)]
公共异步任务CreateMyThing(MythingCreatedToMythingToCreate)
{
尝试
{
MyThingEntity entityToCreate=\u mapper.Map(myThingToCreate);
MyThingEntity entityToReturn=wait_myRepository.InsertThing(entityToCreate);
MyThingDto myThingToReturn=\u mapper.Map(entityToReturn);
返回CreatedAtRoute(
“获取神话”,
新建{id=myThingToReturn.id},
神话(回归);
}
捕获(DuplicateHumanReadableIdeException e)
{
ModelState.AddModelError(“HumanReadableID”,$“HumanReadableID{entityToCreate.HumanReadableID}已被站点{e.IdOfThingWithHumanReadableId}使用”);
返回冲突(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 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);
}
[HttpPost]

公共异步任务

我更喜欢将
IValidatableObject
定义为“对象数据本身有效”。根据这个定义,对象本身是有效的-我们只是不能保存它。我还认为DTO应该是POCO,你永远不应该在这里使用依赖注入。@ChristophLütjen谢谢。事实上,我也有同样的感受(尽管我不知道如何用语言表达这些感受,所以也谢谢你)。那么你会说这种检查属于控制器吗?我仍然觉得我开始的范例可能会失控,在捕获各种自定义异常的基础上改变回报。对于PUT和PATCH动词,也必须实现同样的逻辑。关于“控制器是否在正确的位置”,我认为通常是“视情况而定”。如果你说“动作就是我处理用例的地方”,是的,为什么不呢。您可以添加门面服务或考虑结果对象来简化操作(
var result=myservicecc.Insert(obj);返回此.myconvertineralResultObjectToErrordToHelper(结果)
-但是你也可以定义用例应该在控制器之外处理,例如中介模式哦!我认为这里的关键是,我可以在实现
IValidatableObject
时通过
验证上下文从IoC容器中获取依赖项!就是这样。如果我的回答对你有帮助,请告诉我所以接受作为答案?参考:。谢谢。
[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; }
}
services.AddDbContext<YourDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("YourConnectionString")));
services.AddScoped<ICheckHumanReadableID, CheckHumanReadableID>();