Пользовательское сообщение об ошибке ASP.NET Core Web API, когда тип данных неправильный

У меня есть контроллер, который принимает данные из тела запроса.

[HttpPatch("update-name")]
public async Task<IActionResult> UpdateName([FromBody] UpdateNameData data)
{
    // ...
}
public class UpdateNameData
{
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; } = string.Empty;
}

Когда я отправляю { "name": 1 }, API возвращает эту ошибку:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "data": [
      "The data field is required."
    ],
    "$.name": [
      "The JSON value could not be converted to System.String. Path: $.name | LineNumber: 1 | BytePositionInLine: 10."
    ]
  },
  "traceId": "00-4cb41bb3f51e5253c5591ead5fccf96a-ea1c751ebed4be5b-00"
}

Как я могу сделать так, чтобы, когда пользователь отправляет неправильный тип, он возвращал собственное сообщение об ошибке?

Итак, вы также пытаетесь отобразить сообщение об ошибке типа, например. даже если ввод не пустой, а вместо текста, если они вводят целое число, поэтому вы хотели бы их предупредить, верно?

Md Farid Uddin Kiron 24.04.2024 02:47

Да, точно. Я хочу удалить ошибку JSON и заменить ее чем-то вроде «Имя должно иметь строковый тип».

HelloWorld 24.04.2024 14:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
413
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Новейшее ОБНОВЛЕНИЕ

Глобально напишите общий преобразователь для всех типов с неправильным значением типа, вам понадобится специальный, как показано ниже:

Примечание. Тип C# является множественным, в этом примере я просто использую int, string и double.

public class GenericConverter<T> : JsonConverter<T> 
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (typeof(T) == typeof(string))
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                return (T)(object)reader.GetString();
            }
            else
            {
                throw new JsonException($"String expected, received {reader.TokenType}.");
                    
            }
        }
        else if (typeof(T) == typeof(int))
        {
            try
            {
                if (reader.TryGetInt32(out int intValue))
                {
                    return (T)(object)intValue;
                }
            }
            catch (Exception)
            {

                throw new JsonException($"Integer expected, received {reader.TokenType}.");

            }                
        }
        else if (typeof(T) == typeof(double))
        {
            try
            {
                if (reader.TryGetDouble(out double doubleValue))
                {
                    return (T)(object)doubleValue;
                }
            }
            catch (Exception)
            {

                throw new JsonException($"Double expected, received {reader.TokenType}.");

            }
        }
        // Add additional type conversions as needed
        else
        {
            throw new NotSupportedException($"Conversion to type {typeToConvert.Name} is not supported.");
        }
        throw new NotSupportedException($"Conversion to type {typeToConvert.Name} is not supported.");

    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}
public class GenericConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        // Return true if the typeToConvert is the target type for which you want to apply the converter
        return typeToConvert == typeof(string) || typeToConvert == typeof(int) || typeToConvert == typeof(double);
    }

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        // Create an instance of the generic converter with the appropriate type argument
        Type converterType = typeof(GenericConverter<>).MakeGenericType(typeToConvert);
        return (JsonConverter)Activator.CreateInstance(converterType);
    }
}

Зарегистрируйте конвертер, как показано ниже:

builder.Services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new GenericConverterFactory());
});

«Поле данных обязательно».

Вы неправильно понимаете значение сообщения об ошибке. Это означает, что требуется UpdateNameData data.

Подробное объяснение того, почему вы получаете это сообщение об ошибке, заключается в том, что вы отправляете неправильное значение типа данных, и система привязки модели не может привязать это значение к модели. Таким образом, внутренний параметр получит модель null. А из .NET 6 свойство non-nullable должно быть обязательным, иначе ModelState будет недействительным. Вот почему вы получаете ошибку The data field is required..

Если вы добавите ?, который может обнулить параметр:[FromBody] UpdateNameData? data, то вы увидите, что он получает только одно сообщение об ошибке, поскольку невозможно преобразовать в строку.

Что касается свойства Name, оно не получит пользовательскую ошибку проверки, поскольку вы отправляете поле, а просто отправляете значение неправильного типа.

Если вы отправите пустой json:{}, вы можете получить желаемое сообщение об ошибке, поскольку вы не отправляете поле Name в строке json.

ОБНОВЛЕНИЕ 1

Как я могу сделать так, чтобы, когда пользователь отправляет неправильный тип, он возвращался пользовательское сообщение об ошибке?

В ASP.NET Core 2.1 и более поздних версиях добавлен атрибут [ApiController], который автоматически обрабатывает ошибки проверки модели, возвращая BadRequestObjectResult с переданным ModelState.

Простое решение состоит в том, что вы удаляете [ApiController] и полностью возвращаете собственное сообщение об ошибке:

if (!ModelState.IsValid)
{
    return BadRequest(new { ErrorMessage = "Cannot deserialize the string" });
}

Сообщение об ошибке типа The JSON value could not be converted to System.String. xxxxx — это встроенная ошибка. Тип ответа по умолчанию для ответов HTTP 400 — класс ValidationProblemDetails. Итак, мы создадим собственный класс, который наследует класс ValidationProblemDetails и определим наши собственные сообщения об ошибках.

Для вашего текущего сообщения об ошибке: The JSON value could not be converted to System.String. Path: $.name | LineNumber: 1 | BytePositionInLine: 10.

public class CustomBadRequest : ValidationProblemDetails
{
    public CustomBadRequest(ActionContext context)
    {
        ConstructErrorMessages(context);
        Type = context.HttpContext.TraceIdentifier;
    }

    private void ConstructErrorMessages(ActionContext context)
    {
       //the build-in error message you get
        var myerror = "The JSON value could not be converted to System.String. Path: $.name | LineNumber: 1 | BytePositionInLine: 10.";
        foreach (var keyModelStatePair in context.ModelState)
        {
            var key = keyModelStatePair.Key;
            var errors = keyModelStatePair.Value.Errors;
            if (errors != null && errors.Count > 0)
            {
                if (errors.Count == 1)
                {
                    var errorMessage = GetErrorMessage(errors[0]);
                    if (errorMessage == myerror)
                    {
                        Errors.Add(key, new[] { "The Name must be string" });
                    }
                    else
                    {
                        Errors.Add(key, new[] { errorMessage });
                    }

                }
                else
                {
                    var errorMessages = new string[errors.Count];
                    for (var i = 0; i < errors.Count; i++)
                    {
                        errorMessages[i] = GetErrorMessage(errors[i]);
                        if (errorMessages[i] == myerror)
                        {
                            errorMessages[i] = "The Name must be string";
                        }
                    }

                    Errors.Add(key, errorMessages);
                }
            }
        }
    }

    string GetErrorMessage(ModelError error)
    {
        return string.IsNullOrEmpty(error.ErrorMessage) ?
            "The input was not valid." :
        error.ErrorMessage;
    }
}

Программа.cs:

builder.Services.AddControllers().AddXmlSerializerFormatters().ConfigureApiBehaviorOptions(options =>
{
    options.InvalidModelStateResponseFactory = context =>
    {
        var problems = new CustomBadRequest(context);

        return new BadRequestObjectResult(problems);
    };
});

Другой способ, если вы не хотите вручную устанавливать сообщение об ошибке, вы можете настроить JsonConverter:

public class CustomStringConverter : JsonConverter<string>
{
    public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        try
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                // If the token is already a string, read and return it
                return reader.GetString();
            }
            else
            {
                // Handle other token types or unexpected situations
                throw new JsonException("Invalid token type. Expected a string.");
            }
        }
        catch (JsonException ex)
        {
            // Custom error message for JSON serialization failure
            throw new JsonException("Error converting value to string");
        }
    }

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("Name", value);
        writer.WriteEndObject();
    }
}

Настройте файл Program.cs:

builder.Services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new CustomStringConverter());
});

Хорошо, я это понимаю, но мой вопрос был о том, как изменить сообщение об ошибке с «Невозможно преобразовать значение JSON» на собственное.

HelloWorld 24.04.2024 14:44

Привет @HelloWorld, пожалуйста, прочти мой новый ответ.

Rena 26.04.2024 04:32

Привет, спасибо за подробный ответ. У меня только один вопрос: есть ли способ полностью автоматизировать это, чтобы мне не приходилось писать случаи для каждого типа или менять сообщение JsonConverter по умолчанию?

iKingNinja 28.04.2024 13:26

Привет @iKingNinja, проверь мой новый ответ ОБНОВЛЕНИЕ. Я использую общий JsonConverter

Rena 29.04.2024 04:06

Другие вопросы по теме