У меня есть контроллер, который принимает данные из тела запроса.
[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"
}
Как я могу сделать так, чтобы, когда пользователь отправляет неправильный тип, он возвращал собственное сообщение об ошибке?
Да, точно. Я хочу удалить ошибку JSON и заменить ее чем-то вроде «Имя должно иметь строковый тип».
Глобально напишите общий преобразователь для всех типов с неправильным значением типа, вам понадобится специальный, как показано ниже:
Примечание. Тип 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.
Как я могу сделать так, чтобы, когда пользователь отправляет неправильный тип, он возвращался пользовательское сообщение об ошибке?
В 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, пожалуйста, прочти мой новый ответ.
Привет, спасибо за подробный ответ. У меня только один вопрос: есть ли способ полностью автоматизировать это, чтобы мне не приходилось писать случаи для каждого типа или менять сообщение JsonConverter по умолчанию?
Привет @iKingNinja, проверь мой новый ответ ОБНОВЛЕНИЕ. Я использую общий JsonConverter
Итак, вы также пытаетесь отобразить сообщение об ошибке типа, например. даже если ввод не пустой, а вместо текста, если они вводят целое число, поэтому вы хотели бы их предупредить, верно?