如何在.NET 7 Minimal API中实现完善的模型验证
解决Minimal API模型反序列化错误与验证的清晰反馈问题
核心问题
Minimal API中,当请求JSON的字段类型与C#模型不匹配时(比如把数字传给字符串类型字段),会直接抛出BadHttpRequestException,此时自定义的EndpointFilter不会执行,且默认错误信息无法定位具体出错字段。我们需要:
- 捕获反序列化阶段的具体错误,返回明确的字段级错误信息
- 确保业务级验证(如格式、范围检查)在模型正确绑定后能正常执行
解决方案
1. 优化反序列化错误处理中间件
通过捕获BadHttpRequestException,解析其内部的JsonException,提取具体的出错字段和错误详情,返回符合规范的ValidationProblem响应:
app.Use(async (context, next) => { try { await next(context); } catch (BadHttpRequestException ex) { if (ex.InnerException is JsonException jsonEx) { // 提取出错字段路径,去除开头的$符号 var fieldName = jsonEx.Path.TrimStart('$'); var errorMessage = jsonEx.Message.Split(Environment.NewLine)[0]; // 取最简洁的错误描述 await Results.ValidationProblem(new Dictionary<string, string[]> { { fieldName, new[] { errorMessage } } }).ExecuteAsync(context); return; } // 处理非Json反序列化的BadHttpRequestException await Results.ValidationProblem(new Dictionary<string, string[]> { { "RequestError", new[] { ex.Message } } }).ExecuteAsync(context); } });
2. 业务级验证结合EndpointFilter
当模型成功反序列化后,使用EndpointFilter进行业务规则验证(比如字符串格式、数值范围等),示例如下:
app.MapPost("/test", (CustomRequestObject request) => Results.Ok(request)) .AddEndpointFilter(async (context, next) => { var request = context.Arguments.OfType<CustomRequestObject>().FirstOrDefault(); if (request is not null && !string.IsNullOrEmpty(request.Identification)) { // 示例:验证Identification长度不超过10 if (request.Identification.Length > 10) { return Results.ValidationProblem(new Dictionary<string, string[]> { { nameof(CustomRequestObject.Identification), new[] { "标识长度不能超过10个字符" } } }); } } return await next(context); });
3. 完整Program.cs示例
var builder = WebApplication.CreateBuilder(args); // 配置System.Text.Json序列化选项(可选,根据需求调整) builder.Services.Configure<JsonOptions>(options => { options.SerializerOptions.PropertyNameCaseInsensitive = true; options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }); var app = builder.Build(); // 错误处理中间件,放在最前面 app.Use(async (context, next) => { try { await next(context); } catch (BadHttpRequestException ex) { if (ex.InnerException is JsonException jsonEx) { var fieldName = jsonEx.Path.TrimStart('$'); var errorMessage = jsonEx.Message.Split(Environment.NewLine)[0]; await Results.ValidationProblem(new Dictionary<string, string[]> { { fieldName, new[] { errorMessage } } }).ExecuteAsync(context); return; } await Results.ValidationProblem(new Dictionary<string, string[]> { { "RequestError", new[] { ex.Message } } }).ExecuteAsync(context); } }); // 带业务验证的接口 app.MapPost("/test", (CustomRequestObject request) => Results.Ok(request)) .AddEndpointFilter(async (context, next) => { var request = context.Arguments.OfType<CustomRequestObject>().FirstOrDefault(); if (request is not null && !string.IsNullOrEmpty(request.Identification)) { if (request.Identification.Length > 10) { return Results.ValidationProblem(new Dictionary<string, string[]> { { nameof(CustomRequestObject.Identification), new[] { "标识长度不能超过10个字符" } } }); } } return await next(context); }); app.Run(); public record CustomRequestObject(string? Identification);
效果说明
- 当请求体中
Identification为数字(如{"Identification":12})时,会返回:{ "errors": { "Identification": [ "无法将值转换为类型'System.String'。路径: $.Identification | 行号: 2 | 字节位置: 27。" ] }, "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "00-..." } - 当
Identification是超过10位的字符串时,会返回业务验证错误:{ "errors": { "Identification": [ "标识长度不能超过10个字符" ] }, "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "00-..." }
内容的提问来源于stack exchange,提问作者resonance
相关产品推荐
相关产品推荐

