You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 00:29:52