ModelState不显示所有错误

时间:2019-09-25 09:27:17

标签: c# asp.net-core

我有一个带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)"
    ]
}

2 个答案:

答案 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

然后您将得到描述所有错误的响应:

enter image description here

答案 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; }