0

我正在使用 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不再是 a 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并检查每个属性的存在和类型似乎很难看)

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

4

2 回答 2

0

以这种方式调用 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 ...
});
于 2022-01-02T20:19:18.803 回答
0

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);
    }
}));
于 2022-01-02T10:56:51.643 回答