当反序列化因缺少必填字段而失败时,有没有比 500 响应更好的方法在 ASP.NET 中返回验证错误?

Is there a better way to return validation errors in ASP.NET than a 500 response when deserialization fails due to missing required fields?

提问人:BVernon 提问时间:11/16/2023 最后编辑:marc_sBVernon 更新时间:11/18/2023 访问量:93

问:

要设置上下文,请执行以下操作:

  1. 我有一个yaml oapi文件
  2. 我使用 OpenAPI 生成器创建 C# 类
  3. 某些属性具有属性[IsRequired = true]

如果请求中未包含必需属性,则反序列化将失败,并返回 http 500 响应。在这种情况下,我的代码甚至永远不会被命中。

我相信,如果问题是缺少字段,应该给出 400 响应,理想情况下,应该包括对问题字段的描述。

我想到的这个问题的第一个解决方案是简单地删除属性并自行验证,这就是我目前正在做的事情。但是,我不喜欢这个解决方案,因为如果 oapi 规范更新,那么我将想要再次运行 OpenAPI 生成器,这会将其恢复到原始状态。此外,这样做会导致我丢失有关应用程序中需要哪些字段的元数据,这在进行我自己的验证时实际上很有用。[IsRequired = true]

  1. 有没有合适的“Microsoft”方法来处理这个问题?
  2. 如果没有,开发人员通常是否可以采用任何标准或准标准的方式来处理这个问题?

这似乎是一个相当常见的场景,我很难相信每个人都满足于让服务器返回 500 个错误,仅仅是因为序列化程序的默认行为是在缺少必填字段时抛出异常......这一步发生在你的代码被命中之前,所以你对此无能为力。

或者我有没有办法拦截反序列化过程来处理此类异常?

C# asp.net azure-functions 反序列化 httpresponse

评论


答:

1赞 RithwikBojja 11/16/2023 #1

如果请求中未包含必需属性,则反序列化将失败,并返回 500 响应。在这种情况下,我的代码甚至永远不会被命中。 我相信,如果问题是缺少字段,则应给出 400 响应,理想情况下,应包括对问题字段的描述

是的,这是默认设置,当您不发送必需的参数时会出现 500 错误。

为了避免这种情况,我使用自定义异常处理,如下所示:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;

namespace FunctionApp83
{
    public static class Function1
    {
        public class RithwikModel
        {
            [Required(ErrorMessage = "The 'name' field is required.")]
            public string Id { get; set; }

        }

        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            try
            {
                string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
                RithwikModel res = JsonConvert.DeserializeObject<RithwikModel>(requestBody);

                var validationResults = new List<ValidationResult>();
                if (!Validator.TryValidateObject(res, new ValidationContext(res), validationResults, true))
                {
                    var errors = new BadRequestObjectResult(validationResults);
                    return errors;
                }

                string responseMessage = $"Hello Rithwik, {res.Id}. This HTTP triggered function executed successfully.";
                return new OkObjectResult(responseMessage);
            }
            catch (Exception ex)
            {
                log.LogError($"Error processing request: {ex.Message}");
                return new BadRequestObjectResult("Hello Rithwik Invalid request payload");
            }
        }
    }
}

Output:

当您不发送(任何正文)必需的参数时:

image

当您发送一些非必需的负载时:

image

发送所需参数时:

image

评论

0赞 BVernon 11/17/2023
我看到这是我问题的自然答案,但它确实使将 RithwikModel 对象作为函数的输入而不是 http 请求的能力变得有点无用。让框架为我自动反序列化是件好事,但我想在这种情况下没有办法以更横切的方式拦截反序列化过程,就像我们可以在 mvc 中使用动作过滤器一样?