ModelState не показывает все ошибки - PullRequest
0 голосов
/ 25 сентября 2019

У меня есть ASP NET Core 2.1 API с ActionFilter (автоматическая проверка ModelState подавлена), и когда возникает ошибка привязки - например, недопустимая строка для привязки к guid - Model State содержит только ошибки привязки, но недругие ошибки - обязательный атрибут или MaxLength и т. д.Это что-то ожидается?И еще более важный вопрос: есть ли способ получить все ошибки состояния модели за одну поездку?

Фильтр My Action (глобальный):

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

Метод действия в контроллере

[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."
    ]
}

И когда действительны направляющие идентификатора раздела:

[
    {
        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 голосов
/ 27 сентября 2019

Я выложу код, который решает мою проблему.Он основан на ответе минус.Итак, наконец, я использовал пользовательский JsonConverter для Guids, который работает нормально.

Пользовательский 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; }
0 голосов
/ 26 сентября 2019

при возникновении ошибки привязки - например, недопустимая строка для привязки к guid - Состояние модели содержит только ошибки привязки, но не других ошибок - требуется атрибут или MaxLength и т. Д.Это что-то ожидаемое?

Это не правда.Настоящая причина в том, что вы обрабатываете JSON полезную нагрузку .Сначала полезная нагрузка json должна быть десериализована в ICollection<SkillBindDto>, а затем валидатор может ее проверить.

При обработке вашей полезной нагрузки с недопустимым свойством GUID десериализация завершается неудачно слишком рано , поэтому больше не будет привязываться / проверяться для других свойств.

Есть ли способ получить все ошибки состояния модели за одну поездку?

Как я уже говорил выше, проблемапроисходит из-за сбоя 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
    }
}

Easy Walkaround

В качестве более простого обхода вы можете вместо этого отправить полезную нагрузку формы application/json.

Форма полезной нагрузки кодируется в виде пар ключ-значение, разделенных символом «&», с ключом «=» между ключом и значением, которое выглядит как строка запроса.Когда вы отправляете HTML <form /> в старые времена, вы фактически отправляете полезную нагрузку формы.

Frist, удалите атрибут [FromBody] в своем действии:

[HttpPost()]
public async Task<ActionResult<IReadOnlyCollection<SkillDto>>> Create(<strike>[FromBody]</strike>ICollection<SkillBindDto> skills, CancellationToken cancellationToken)
{
    ...
}

, а затем отправьте полезную нагрузку в формате application/x-www-form-urlencoded.Вы можете использовать new FormData(formElementId) для создания такой формы данных.Я создаю вспомогательную функцию, которая отображает JSON для данных формы:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...