我有一个带ActionFilter的ASP NET Core 2.1 API(抑制了自动进行ModelState验证),并且当发生绑定错误时(例如,无效的字符串绑定到guid),Model State仅包含绑定错误,而没有错误其他错误-必需的属性或MaxLength等。这是预期的吗?还有一个更重要的问题:是否有办法一次获得所有模型状态错误?
“我的操作”过滤器(全局):
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
绑定模型:
public class SkillBindDto
{
[Required(ErrorMessage = ValidationMessages.FieldRequired)]
[MinLength(1, ErrorMessage = ValidationMessages.FieldInvalidMinLength)]
public string Name { get; set; }
public string Info { get; set; }
[Required(ErrorMessage = ValidationMessages.FieldRequired)]
public Guid SectionId { get; set; }
public string[] Tags { get; set; }
}
控制器中的Action方法
[HttpPost()]
public async Task<ActionResult<IReadOnlyCollection<SkillDto>>> Create([FromBody]ICollection<SkillBindDto> skills, CancellationToken cancellationToken)
{
List<SkillDto> result = await _skillService.CreateSkillsAsync(skills, cancellationToken);
return result;
}
和两个示例: 当请求的正文为:
[
{
SectionId : "0c2d3928-aff2-44da-blaaah-blaaah", - this is invalid guid
Name : "",
Info : "Test Info",
Tags : ["tag 1", "tag 2"]
},
{
SectionId : "0c2d3928-aff2-44da-blaaah-blaaah", - this is invalid guid
Name : "",
Info : "Test Info 2",
Tags : ["tag 3", "tag 2"]
}
]
我收到以下答复:
{
"[0].SectionId": [
"Error converting value \"0c2d3928-aff2-44da-blaaah-blaaah\" to type 'System.Guid'. Path '[0].SectionId', line 3, position 51."
],
"[1].SectionId": [
"Error converting value \"0c2d3928-aff2-44da-blaaah-blaaah\" to type 'System.Guid'. Path '[1].SectionId', line 9, position 51."
]
}
当“区段ID”指导有效时:
[
{
SectionId : "0c2d3928-aff2-44da-5d98-08d727c1a8b0",
Name : "",
Info : "Test Info",
Tags : ["tag 1", "tag 2"]
},
{
SectionId : "0c2d3928-aff2-44da-5d98-08d727c1a8b0",
Name : "",
Info : "Test Info",
Tags : ["tag 3", "tag 2"]
}
]
结果是:
{
"[0].Name": [
"Field Name is not provided but it is required",
"Field Name is under minimum length. Lenght must be not less than 1 character(s)"
],
"[1].Name": [
"Field Name is not provided but it is required",
"Field Name is under minimum length. Lenght must be not less than 1 character(s)"
]
}
答案 0 :(得分:0)
,当发生绑定错误时-例如,无效的字符串不能绑定到guid-模型状态仅包含绑定错误,而其他错误均不包含-必需属性或MaxLength等。这是预期的东西吗?
那不是事实。真正的原因是您正在处理 JSON有效负载。必须首先将json有效负载反序列化到ICollection<SkillBindDto>
中,然后 validator 可以对其进行验证。
在使用无效的GUID
属性处理有效负载时,反序列化失败太早,因此将不再进行绑定/验证< / strong>以获取其他属性。
是否有一种方法可以一次获得所有模型状态错误?
如上所述,由于JSON Deserailziation失败,因此出现了问题。
如果要使用JSON格式,请告诉MVC如何处理无效属性。例如,创建一个自定义JsonConverter
:
public class MyCustomConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dt= reader.Value;
... // because the processing rules depends on your business,
... // I can't write all the codes here.
... // e.g.
... // create a null(object) or default(value) if invalid
}
}
遍历更加容易,您可以发送表单有效负载而不是application/json
。
form payload被编码为键值对,并用'&'分隔,键和值之间有一个'=',看起来像查询字符串。过去提交HTML <form />
时,实际上是在发送表单有效内容。
第一个拳头,在操作中删除[FromBody]
属性:
[HttpPost()] public async Task<ActionResult<IReadOnlyCollection<SkillDto>>> Create([FromBody]ICollection<SkillBindDto> skills, CancellationToken cancellationToken) { ... }
然后以application/x-www-form-urlencoded
格式发送有效负载。您可以使用new FormData(formElementId)
来构造这种形式的有效载荷。我创建了一个将JSON映射到form-data的辅助函数:
function createFormPayload(name,o){
var payload = {};
function _objectNotNull(value){
return value !== null && typeof value === "object";
}
function _create(prefix,obj) {
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var key = "";
if(prefix){
key = isNaN(prop)? key = prefix + "." + prop : key = prefix + ".[" + prop + "]";
}else{
key = isNaN(prop)? key = prop : key = "[" + prop + "]";
}
var value = obj[prop];
if(_objectNotNull(value))
_create(key, value);
else
payload[key]=value;
}
}
};
_create(name,o);
return payload;
}
现在我们可以将skills
发送到表单数据对象:
var skills= [
{
"SectionId" : "0c2d3928-aff2-44da-blaaah-blaaah",
"Name" : "",
"Info" : "Test Info",
"Tags" : ["tag 1", "tag 2"]
}, {
"SectionId" : "0c2d3928-aff2-44da-blaaah-blaaah",
"Name" : "",
"Info" : "Test Info 2",
"Tags" : ["tag 3", "tag 2"]
}
];
var data = createFormPayload("",skills) ;
$.ajax({
type: "POST",
url: "/xxx/create",
data: data,
success: function(r){
console.log(r);
},
});
发送上述技能时,实际有效载荷为:
POST https://localhost:5001/Home/Create
Content-Type: application/x-www-form-urlencoded
[0].SectionId=0c2d3928-aff2-44da-blaaah-blaaah&[0].Name=&[0].Info=Test Info&[0].Tags[1]=tag1&[0].Tags[2]=tag2&[1].SectionId=0c2d3928-aff2-44da-blaaah-blaaah&[1].Name=&[1].Info=TestInfo2&[1].Tags[1]=tag3&[1].Tags[2]=tag 2
然后您将得到描述所有错误的响应:
答案 1 :(得分:0)
我将发布解决我的问题的代码。它基于itminus答案。 所以最后我为Guid使用了一个自定义的JsonConverter,效果很好。
自定义JsonConverter-如果无法将字符串从有效负载转换为Guid,则将corespondent字段设置为空的Guid值,因此有效负载将被序列化并可以继续进行其他验证:
public sealed class JsonConverterDefaultGuid : JsonConverter<Guid>
{
public override Guid ReadJson(JsonReader reader, Type objectType, Guid existingValue, bool hasExistingValue, JsonSerializer serializer)
{
string value = (string)reader.Value;
return Guid.TryParse(value, out var result) ? result : Guid.Empty;
}
public override void WriteJson(JsonWriter writer, Guid value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}
下一步-创建自定义验证属性,请检查装饰的属性是否具有默认值并添加模型状态错误:
public class NotDefaultAttribute : ValidationAttribute
{
public const string DefaultErrorMessage = "The {0} field must not have default value";
public NotDefaultAttribute() : base(DefaultErrorMessage)
{
}
public NotDefaultAttribute(string errorMessage) : base(errorMessage)
{
}
public override bool IsValid(object value)
{
return !value.Equals(value.GetDefaultValue());
}
}
该属性使用扩展方法为任何对象生成默认值:
public static object GetDefaultValue(this object obj)
{
var objType = obj.GetType();
if (objType.IsValueType)
return Activator.CreateInstance(objType);
return null;
}
所有这些都是这样使用的:
[JsonConverter(typeof(JsonConverterDefaultGuid))]
[NotDefault(ValidationMessages.FieldInvalid)]
public Guid SectionId { get; set; }