【问题标题】:ASP.NET Core 6.0 - Minimal APIs: What is the proper way to bind json body request to desired class?ASP.NET Core 6.0 - 最小 API:将 json 正文请求绑定到所需类的正确方法是什么?
【发布时间】:2022-01-03 04:21:50
【问题描述】:

我正在使用 ASP.NET Core 6.0 - Minimal APIs 开发一个非常简单的 REST API,对于 Post 方法之一,我需要验证请求的 json 正文。我为此使用了System.ComponentModel.DataAnnotations,代码运行良好:

using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/API_v1/Send", (PostRequest request) =>
{
    ICollection<ValidationResult> ValidationResults = null;
    if (Validate(request, out ValidationResults))
    {
        //request object is valid and has proper values
        //the rest of the logic...
    }
    return new { status = "failed"};
});

app.Run();

static bool Validate<T>(T obj, out ICollection<ValidationResult> results)
{
    results = new List<ValidationResult>();
    return Validator.TryValidateObject(obj, new ValidationContext(obj), results, true);
}


public class PostRequest
{
    [Required]
    [MinLength(1)]
    public string To { get; set; }
    [Required]
    [RegularExpression("chat|groupchat")]
    public string Type { get; set; }
    [Required]
    public string Message { get; set; }
}

当 json 请求中的某个字段的类型不正确时,我的代码出现问题;例如这个示例 json 正文(to 不再是 string):

{
    "to": 12,
    "type": "chat",
    "message": "Hi!"
}

会引发以下错误:

Microsoft.AspNetCore.Http.BadHttpRequestException: Failed to read parameter "PostRequest request" from the request body as JSON.
 ---> System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $.to | LineNumber: 1 | BytePositionInLine: 12.
 ---> System.InvalidOperationException: Cannot get the value of a token type 'Number' as a string.
   at System.Text.Json.Utf8JsonReader.GetString()
   at System.Text.Json.Serialization.Converters.StringConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[TValue](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[TValue](JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan`1 buffer, JsonSerializerOptions options, ReadStack& state, JsonConverter converterBase)
   at System.Text.Json.JsonSerializer.ContinueDeserialize[TValue](ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, JsonConverter converter, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.ReadAllAsync[TValue](Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(HttpRequest request, Type type, JsonSerializerOptions options, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(HttpRequest request, Type type, JsonSerializerOptions options, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass46_3.<<HandleRequestBodyAndCompileRequestDelegate>b__2>d.MoveNext()
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.Log.InvalidJsonRequestBody(HttpContext httpContext, String parameterTypeName, String parameterName, Exception exception, Boolean shouldThrow)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass46_3.<<HandleRequestBodyAndCompileRequestDelegate>b__2>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:5090
User-Agent: PostmanRuntime/7.26.8
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 62
Postman-Token: e31d3575-d2ec-49a7-9bef-04eaecf38a24

显然它不再可以将request 转换为PostRequest 类型的对象,但是处理这种情况的正确方法是什么? (将request 定义为object 类型并检查每个属性的存在和类型似乎很难看)

进一步说明:我现在想知道如何捕获上述错误。

【问题讨论】:

  • 除了FromBody,我不太清楚你想做什么。您的示例 JSON 与 PostRequest 中的声明不匹配。在这种特殊情况下,它只是一个属性的类型,但尚不清楚您期望请求有多损坏并且仍然能够处理它。
  • 如果body不能匹配PostRequest,那么显然是无效的。但是我怎样才能检测到上述情况呢? (我的意思是我只能在出现我不知道如何捕获的错误时知道它是无效的!)
  • 除了将其报告为错误之外,您还想如何处理无效请求?我建议当前的行为可能已经是最合适的了。如果您基本上想用自己的绑定系统替换内置绑定系统,则可能需要以较低级别的方式处理请求。
  • 是的,我想正确地向用户报告(如果不可能,甚至完全忽略请求),但我如何向用户报告?!用户现在唯一能看到的就是那个大错误!
  • 我不希望用户不在本地运行,在开发模式下会收到该错误 - 我希望这只是 ASP.NET Core 本地开发消息.从根本上说,这是对该端点的无效请求,因此 HTTP 400 响应比正文为“失败”的 200 更合适。但基本上,如果您不希望 ASP.NET Core 进行解析,那么您应该尝试只接受 HttpContext 参数而不是 PostRequest。 (我还没有尝试过,但我希望它会起作用。)

标签: c# asp.net-core .net-6.0 minimal-apis


【解决方案1】:

Asp.Net Core 提供了一种注册异常处理程序的方法:

app.UseExceptionHandler(c => c.Run(async context =>
{
    var exception = context.Features
        .Get<IExceptionHandlerFeature>()
        ?.Error;
    if (exception is not null)
    {
        var response = new { error = exception.Message };
        context.Response.StatusCode = 400;

        await context.Response.WriteAsJsonAsync(response);
    }
}));

【讨论】:

    【解决方案2】:

    以这种方式调用 Minimal API 是有原因的 - 与 MVC 相比,出于简单性和性能考虑,许多与绑定、模型状态等相关的便捷功能并不存在。

    也许有更方便的方法来完成您的任务,但例如您可以利用自定义绑定机制来结合 json 解析和验证:

    public class ParseJsonAndValidationResult<T>
    {
        public T? Result { get; init; }
        public bool Success { get; init; }
        // TODO - add errors
    
        public static async ValueTask<ParseJsonAndValidationResult<T>?> BindAsync(HttpContext context)
        {
            try
            {
                var result = await context.Request.ReadFromJsonAsync<T>(context.RequestAborted);
                var validationResults = new List<ValidationResult>();
                if (!Validator.TryValidateObject(result, new ValidationContext(result), validationResults, true))
                {
                    // TODO - add errors
                    return new ParseJsonAndValidationResult<T>
                    {
                        Success = false
                    };
                }
    
                return new ParseJsonAndValidationResult<T>
                {
                    Result = result,
                    Success = true
                };
            }
            catch (Exception ex)
            {
                // TODO - add errors
                return new ParseJsonAndValidationResult<T>
                {
                    Success = false
                };
            }
    
        }
    }
    

    而在MapPost

    app.MapPost("/API_v1/Send", (ParseJsonAndValidationResult<PostRequest> request) =>
    { 
        // analyze the result ...
    });
    

    【讨论】:

    • 非常感谢您的好评。我最终使用了 Json.NET Schema 库,方法是根据带注释的 PostRequest 类自动生成架构并使用该架构验证 HttpContext 的 json 主体。
    • @wiki 1) 如果您认为这是最佳答案,则应将其标记为最佳答案(也给它投票); 2)为什么不用一些代码示例添加您自己的答案,我相信这对其他人会有所帮助(对于开发人员学习 Web 开发更重要)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-07-23
    • 1970-01-01
    • 2019-06-02
    • 1970-01-01
    • 1970-01-01
    • 2021-05-02
    • 1970-01-01
    相关资源
    最近更新 更多