通过流利的 API 验证视图模型是否仅在 api 中传递一次
Validate if view model is only being passed once in an api through fluent API
我正在使用 Fluent API 来验证我 API 中的负载。 Street Address/MailingAddress/Lockbox Address 是域模型中的三个不同属性。我想在我的验证中确保街道地址和邮寄地址只传递一次。
正确的有效载荷
{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"MailingAddress": {
"city":"NewYork",
"address":"q2453"
},
"LockBoxAddress": {
"city":"Miami",
"address":"q23888"
}
}
领域模型
public string id{get;set;}
public string name{get;set;}
public Address streetAddress{get;set;}
public Address MailingAddress{get;set;}
public Address LockboxAddress{get;set;}
不正确的负载
{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"streetAddress": {
"city":"NewYork",
"address":"q2453"
}
}
我希望上面的有效负载出错,说你不能传递多个街道地址,我正在使用 Fluent API
流利API
RuleFor(x => x.streetAddress).Count(x =>x < 2).When(x => x.streetAddress!= null);
没有 属性 来获取模型的计数。有什么想法吗?
- 您无法通过 Fluent 验证 API。因为只有一个
streetAddress
属性在你的域模型有效负载反序列化之后。另一方面,您的代码 RuleFor(x => x.streetAddress)
returns IRuleBuilderInitial<XModel, Address>
而不是 IRuleBuilderInitial<XModel, IList<Address>>
。为了实现你的目标,你应该确保你的验证发生在之前 FluentValidation,即反序列化时验证有效负载。
- 有时客户端可能会发送带有重复密钥的 json,如果您使用
ASP.NET Core 2.1
或 ASP.NET 2.2
,默认情况下 不会失败。
如何解决
要拒绝此类负载,请确保您的 Newtonsoft.Json
版本为 12.0.1
或更高版本 。如果您不确定,只需在 *.csproj
:
中添加这样的参考
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
然后创建自定义模型绑定器来处理重复键:
public class RejectDuplicatedKeysModelBinder : IModelBinder
{
private JsonLoadSettings _loadSettings = new JsonLoadSettings(){ DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error };
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var modelName = bindingContext.BinderModelName ?? bindingContext.OriginalModelName ?? bindingContext.FieldName ?? String.Empty;
var modelType = bindingContext.ModelType;
var req = bindingContext.HttpContext.Request;
var raw = req.Body;
if (raw == null) {
bindingContext.ModelState.AddModelError(modelName, "invalid request body stream");
return Task.CompletedTask;
}
JsonTextReader reader = new JsonTextReader(new StreamReader(raw));
try {
var json = (JObject)JToken.Load(reader, this._loadSettings);
var o = json.ToObject(modelType);
bindingContext.Result = ModelBindingResult.Success(o);
}
catch (JsonReaderException e) {
var msg = $"wrong property with key='{e.Path}': {e.Message}";
bindingContext.ModelState.AddModelError(modelName, msg);
bindingContext.Result = ModelBindingResult.Failed();
}
catch(Exception e) {
bindingContext.ModelState.AddModelError(modelName, e.ToString()); // you might want to custom the error info
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
通过这种方式,重复的错误信息将被添加到ModelState
。
测试用例
让我们创建一个用于测试的操作方法:
[HttpPost]
public IActionResult Test([ModelBinder(typeof(RejectDuplicatedKeysModelBinder))]XModel model){
if(! this.ModelState.IsValid){
var problemDetails = new ValidationProblemDetails(this.ModelState)
{
Status = StatusCodes.Status400BadRequest,
};
return BadRequest(problemDetails);
}
return new JsonResult(model);
}
当 json 有重复的键时,我们会得到如下错误:
我正在使用 Fluent API 来验证我 API 中的负载。 Street Address/MailingAddress/Lockbox Address 是域模型中的三个不同属性。我想在我的验证中确保街道地址和邮寄地址只传递一次。
正确的有效载荷
{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"MailingAddress": {
"city":"NewYork",
"address":"q2453"
},
"LockBoxAddress": {
"city":"Miami",
"address":"q23888"
}
}
领域模型
public string id{get;set;}
public string name{get;set;}
public Address streetAddress{get;set;}
public Address MailingAddress{get;set;}
public Address LockboxAddress{get;set;}
不正确的负载
{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"streetAddress": {
"city":"NewYork",
"address":"q2453"
}
}
我希望上面的有效负载出错,说你不能传递多个街道地址,我正在使用 Fluent API
流利API
RuleFor(x => x.streetAddress).Count(x =>x < 2).When(x => x.streetAddress!= null);
没有 属性 来获取模型的计数。有什么想法吗?
- 您无法通过 Fluent 验证 API。因为只有一个
streetAddress
属性在你的域模型有效负载反序列化之后。另一方面,您的代码RuleFor(x => x.streetAddress)
returnsIRuleBuilderInitial<XModel, Address>
而不是IRuleBuilderInitial<XModel, IList<Address>>
。为了实现你的目标,你应该确保你的验证发生在之前 FluentValidation,即反序列化时验证有效负载。 - 有时客户端可能会发送带有重复密钥的 json,如果您使用
ASP.NET Core 2.1
或ASP.NET 2.2
,默认情况下 不会失败。
如何解决
要拒绝此类负载,请确保您的 Newtonsoft.Json
版本为 12.0.1
或更高版本 。如果您不确定,只需在 *.csproj
:
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
然后创建自定义模型绑定器来处理重复键:
public class RejectDuplicatedKeysModelBinder : IModelBinder
{
private JsonLoadSettings _loadSettings = new JsonLoadSettings(){ DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error };
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var modelName = bindingContext.BinderModelName ?? bindingContext.OriginalModelName ?? bindingContext.FieldName ?? String.Empty;
var modelType = bindingContext.ModelType;
var req = bindingContext.HttpContext.Request;
var raw = req.Body;
if (raw == null) {
bindingContext.ModelState.AddModelError(modelName, "invalid request body stream");
return Task.CompletedTask;
}
JsonTextReader reader = new JsonTextReader(new StreamReader(raw));
try {
var json = (JObject)JToken.Load(reader, this._loadSettings);
var o = json.ToObject(modelType);
bindingContext.Result = ModelBindingResult.Success(o);
}
catch (JsonReaderException e) {
var msg = $"wrong property with key='{e.Path}': {e.Message}";
bindingContext.ModelState.AddModelError(modelName, msg);
bindingContext.Result = ModelBindingResult.Failed();
}
catch(Exception e) {
bindingContext.ModelState.AddModelError(modelName, e.ToString()); // you might want to custom the error info
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
通过这种方式,重复的错误信息将被添加到ModelState
。
测试用例
让我们创建一个用于测试的操作方法:
[HttpPost]
public IActionResult Test([ModelBinder(typeof(RejectDuplicatedKeysModelBinder))]XModel model){
if(! this.ModelState.IsValid){
var problemDetails = new ValidationProblemDetails(this.ModelState)
{
Status = StatusCodes.Status400BadRequest,
};
return BadRequest(problemDetails);
}
return new JsonResult(model);
}
当 json 有重复的键时,我们会得到如下错误: