您现在的位置是:首页 >技术教程 >“The xxx field is required“, Nullable引发的bug网站首页技术教程

“The xxx field is required“, Nullable引发的bug

艾萨克SU 2024-09-03 12:01:03
简介“The xxx field is required“, Nullable引发的bug

0.背景

最近接到一个报告的bug,有一个前端的功能不好用了。这个项目是由前端和后台服务组成的,前端用的angular,服务用的是.net6.0。从前端开始排查,发现有个api的返回错误,返回中报告 “The xxx field is required”,这个field在请求中被设置未了null,而查看了service里面的模型代码,这个field并没有被设置为required,而这段代码一直没有修改。
request的内容

{
   "name_guid": "B0AE3B10-A824-4D1E-8F2D-160A5FC0F577",
   "items": [
       {
           "item1": "AAA",
           "item2": "AA1",
           "item3": null,
       },
       {
           "item1": "BBB",
           "item2": "BB1",
           "item3": null,
       },
       {
           "item1": "CCC",
           "item2": "CC1",
           "item3": null,
       }
   ]
}

返回的内容

status:400Bad Request
{
   "errors": {
       "items[0].item3": [
           "The item3 field is required."
       ],
       "items[1].item3": [
           "The item3 field is required."
       ],
       "items[2].item3": [
           "The item3 field is required."
       ]
   },
   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
   "title": "One or more validation errors occurred.",
   "status": 400,
   "traceId": "00-xxxxxxxxxxxxxxxxx-00"
}

再来看看服务端的定义

    public class itemdef
    {
  		[Required]
        [JsonProperty(PropertyName = "item1")]
        public string item1{ get; set; }

        [JsonProperty(PropertyName = "item2")]
        public string item2 { get; set; } = string.Empty;
        
        [JsonProperty(PropertyName = "item3")]
        public string item3 { get; set; } = string.Empty;
   }
    
    public class theGetRequest 
    {
        [JsonProperty(PropertyName = "name_guid")]
        [StringLength(36)]
        public string JobGuid { get; set; }

        [JsonProperty(PropertyName = "channels")]
        public itemdef[] items{ get; set; }
    }

1.排查

1.1 跟代码

首先想到的跟代码,因为能够稳定复现,所以应该好查。
首先在代码库中搜索报错信息,查is required,居然没有查到。
那就在入口函数处打断点,于是在controller中的处理函数入口处打断点,结果没进去。那就有两种可能,一种是断点位置打错了,是其他函数处理的这个请求,另一个就是还没进这个函数就返回了。
于是修改了请求,把null改成一个字符串,断点命中,说明位置是对的,只是还没进这个函数就返回了。
查了一下资料,这个是WebApi的模型验证,那一定是模型定义的问题。
但是查了一下代码,并没有发现近期有相关的修改。

1.2 分析

再回头看一下这个现象,返回值显示item3 是必须的,但是在模型中并没有设置[required]属性,那么是默认就是required?那么怎么设置为非required呢?代码一样,为什么之前可以呢?
询问了chatgpt,得到的答案是:
在 ASP.NET Web API 中,默认情况下,所有值类型的属性都被视为必需的,即使您没有显式地将它们标记为 [Required]。因此,如果您想要将一个值类型的属性设置为非必需的,您需要将其声明为可空值类型(例如 int?、double?、DateTime? 等)。
尝试给item3的定义加上“?”,变成可空的,编译运行,居然成功了。
那么为什么之前没改代码却可以不加“?”呢?

1.2 查变更

要回答这个问题就得定位到是那条修改引起的这个问题。用二分法查找。最后终于找到了引起这个bug的变更。我们把一部分代码从service中提取出来放到了一个common的dll中,以便与其他服务共享,但是代码内容基本没变。那么是什么原因导致了这个行为上的不同呢?

2.水落石出

经过了调查,发现common的project设置中有一行
enable
而在原来service的project设置中却没有。
答案找到了,由于service没有设置nullable为enable,所以item3是可以接受null值的,但是移到common中之后,由于设置了nullable,导致item3不能接受null值,才导致了模型检查失败返回。

3.另一处值得注意的地方

注意到了item3的模型定义中有一个“= string.Empty;”,这应该是设置默认值啦,那是这个默认值没生效吗?
进行了另一个尝试,把request中的item3=null删掉,就是没有这个field,发送请求,没有报错。
那么说明设置默认值生效了。

4.总结

从这件事情上总结出两个经验:

4.1 模型验证中的nullable

参考内容:Model validation in ASP.NET Core MVC and Razor Pages

4.2 request中的=null和无该参数的区别

服务端在处理=null和无参数是不同的,当=null的时候会认为有该参数,值为null,不会触发默认值赋值,而如果没有这个参数则会使用默认值。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。