如何自定义ASP.NET REST端点错误请求(400)的响应体内容?
问题描述
我有一个REST API,端点代码如下:
[Route("create")] [HttpPost] public object CreateStuff ([FromBody] MyParams args) { //... }
为简化说明,假设MyParams是仅包含一个枚举类型字段的对象:
public enum SomeType { One = 1, Two = 2, } public class MyParams { public SomeType Test {get; set;} }
使用如下请求体调用该端点可正常工作:
{ "Test": "Two" }
但如果传入无效值(例如"Test": "Invented value"),接口会返回400 Bad Request响应,响应体如下:
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "00-c44995191a9c99604f9944c2b1542245-caa753444c07f89e-00", "errors": { "args": [ "The args field is required." ], "$.Test": [ "The JSON value could not be converted to System.Nullable`1[SomeType]. Path: $.Test| LineNumber: 2 | BytePositionInLine: 27." ] } }
请问是否可以将该响应体修改为自定义内容?
解决方案
当然可以自定义这个响应体,以下是几种实用的实现方式:
1. 自定义枚举模型绑定器
针对枚举类型编写专属绑定逻辑,捕获转换失败场景并返回自定义错误信息:
public class EnumModelBinder<TEnum> : IModelBinder where TEnum : struct, Enum { public Task BindModelAsync(ModelBindingContext bindingContext) { var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult == ValueProviderResult.None) { return Task.CompletedTask; } var value = valueProviderResult.FirstValue; if (!Enum.TryParse<TEnum>(value, ignoreCase: true, out var result)) { var validValues = string.Join(", ", Enum.GetNames<TEnum>()); bindingContext.ModelState.TryAddModelError( bindingContext.ModelName, $"字段 {bindingContext.ModelName} 传入无效值,允许的值为:{validValues}" ); return Task.CompletedTask; } bindingContext.Result = ModelBindingResult.Success(result); return Task.CompletedTask; } }
在MyParams的枚举字段上绑定该实现:
public class MyParams { [ModelBinder(typeof(EnumModelBinder<SomeType>))] public SomeType Test {get; set;} }
2. 全局自定义验证过滤器
创建全局过滤器统一拦截模型验证错误,替换为自定义响应格式:
public class CustomValidationFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { var customErrors = new Dictionary<string, List<string>>(); foreach (var key in context.ModelState.Keys) { var errors = context.ModelState[key].Errors .Select(e => { if (e.ErrorMessage.Contains("JSON value could not be converted")) { var targetProperty = context.ActionArguments.Values .FirstOrDefault()?.GetType() .GetProperty(key); if (targetProperty != null && targetProperty.PropertyType.IsEnum) { var validValues = string.Join(", ", Enum.GetNames(targetProperty.PropertyType)); return $"字段 {key} 无效,允许的值:{validValues}"; } } return e.ErrorMessage; }) .ToList(); customErrors[key] = errors; } var customResponse = new { code = "PARAM_VALID_FAILED", message = "请求参数验证不通过", errorDetails = customErrors }; context.Result = new JsonResult(customResponse) { StatusCode = StatusCodes.Status400BadRequest }; } } public void OnActionExecuted(ActionExecutedContext context) { } }
在Program.cs中注册全局过滤器:
builder.Services.AddControllers(options => { options.Filters.Add<CustomValidationFilter>(); });
3. 配置API行为自定义响应
通过ConfigureApiBehaviorOptions直接覆盖默认的无效模型响应逻辑:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }) .ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = context => { var customErrors = context.ModelState .Where(kv => kv.Value.Errors.Any()) .ToDictionary( kv => kv.Key, kv => kv.Value.Errors.Select(e => { if (e.Exception is JsonException jsonEx && jsonEx.Message.Contains("converted to")) { var enumTypeName = jsonEx.Path.Split('.').Last(); var enumType = Type.GetType($"你的命名空间.{enumTypeName}"); if (enumType != null && enumType.IsEnum) { var validValues = string.Join(", ", Enum.GetNames(enumType)); return $"无效枚举值,允许的值:{validValues}"; } } return e.ErrorMessage; }).ToList() ); var customResponse = new { status = 400, message = "参数错误", errors = customErrors }; return new JsonResult(customResponse) { StatusCode = 400 }; }; });
内容的提问来源于stack exchange,提问作者Kaisadilla
相关产品推荐
相关产品推荐

