如何(以及在何处)针对数据库验证 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>();
结果:
我正在创建一个 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>();
结果: