ASP.NET Core 警惕可空类型开启之后模型校验失败
在开启 Nullable 可空类型之后,原本可以调用的 API 也许就会提示 400 BadRequest 因为传入参数不合法,模型校验失败,此时将不会进入预期的 API 函数,同时也不会在输出里面找到有用的信息
在 SDK 风格的 csproj 文件开启可空类型可以添加下面代码
<Nullable>enable</Nullable>
为了方便让小伙伴知道上面代码加在哪里,我贴出更多的 csproj 文件代码
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
在开启之后,原本工作的很好的 API 也许在客户端调用的时候,将会提示 400 BadRequest 内容大概如下
{
"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title":"One or more validation errors occurred.",
"status":400,
"traceId":"00-99b1c07693a55c4990389901832992a4-b2ed63ee45e85344-01",
"errors":
{
"Account":
[
"The Account field is required."
]
}
}
复习一下为什么会存在 400 错误
- 也许调用的 API 错误了,本来是预期调用 Foo 的,但却调用了 A 接口
- 也许调用的端口不对,也许是被 Fiddler 干扰了
- 也许是传入的参数不合法
如上面提示,实际内容是 The Account field is required
翻译过来就是接口里面的参数,要求一定存在 Account 属性
而明明之前工作的好好的,接口实现如下
[HttpPost]
// ReSharper disable once StringLiteralTypo
[Route("/lindexi/doubi")]
[RequestSizeLimit(100_000_000)]
public async Task<string> PostFile([FromForm] LindexiUploadFileRequest request)
也就是需要通过 FromForm 拿到内容,而 LindexiUploadFileRequest 的定义如下
[DataContract]
public class LindexiUploadFileRequest
{
[DataMember(Name = "file")]
[JsonPropertyName("file")]
public IFormFile File { get; set; }
[DataMember(Name = "account")]
[JsonPropertyName("account")]
public string Account { get; set; }
}
客户端调用代码大概如下
public async Task<string> Upload(string host, string file)
{
var multipartFormDataContent = new MultipartFormDataContent();
var fileName = Path.GetFileName(file);
var stringContent = new StringContent(fileName);
multipartFormDataContent.Add(stringContent, "Name");
using var fileStream = new FileStream(file, FileMode.Open);
using var streamContent = new StreamContent(fileStream);
multipartFormDataContent.Add(streamContent, "File", fileName);
var account = "";
multipartFormDataContent.Add(new StringContent(account), "Account");
var httpClient = new HttpClient();
var url = $"{host}/lindexi/doubi";
var response = await httpClient.PostAsync(url, multipartFormDataContent).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return $"{response.StatusCode}n{content}";
}
小伙伴是否可以看出问题?实际上在开启可空之后,尽管在客户端代码里面设置了 multipartFormDataContent.Add(new StringContent(account), "Account");
但是传入的内容是空字符串
而开启可空之后,定义的数据模型 public string Account { get; set; }
表示 Account 一定不是空,于是传入空的 Account 属性将会校验不通过
有两个解决方法,第一个解决方法就是标记 Account 属性可空
[DataMember(Name = "account")]
[JsonPropertyName("account")]
public string? Account { get; set; }
但是对于大项目,很难测试全,此时可以在全局配置,让行为和之前相同
services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);
打开 Startup.cs 文件,在 ConfigureServices 函数添加上面代码即可
但对于 EF 这边,有更多的变更,详细请看 Working with nullable reference types - EF Core
因此如果是新项目,我推荐开启可空,而对于现有的项目,我不推荐打开
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/ASP.NET-Core-%E8%AD%A6%E6%83%95%E5%8F%AF%E7%A9%BA%E7%B1%BB%E5%9E%8B%E5%BC%80%E5%90%AF%E4%B9%8B%E5%90%8E%E6%A8%A1%E5%9E%8B%E6%A0%A1%E9%AA%8C%E5%A4%B1%E8%B4%A5.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
- win10的80端口被system占用的问题
- 【Spring开发】—— AOP之方法级拦截
- eclipse tomcat下网页修改不生效
- 【插件开发】—— 14 Site is incorrect!编辑器启动报错!
- Java魔法堂:Date与日期时间格式化
- Java魔法堂:打包知识点之META-INF/MAINFEST.MF
- WordPress快速建站
- 大数据时代下的生活
- 【Spring实战】—— 1 入门讲解
- 博客园小技巧
- JS魔法堂:关于元素位置和鼠标位置的属性
- MyBatis魔法堂:Insert操作详解(返回主键、批量插入)
- Winodws安装系统时,通过安装磁盘进行分区
- Eclipse安装SVN插件
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- sklearn自带的数据集以及生成数据
- elaticserch的索引
- 有效地读取图像,对比opencv、PIL、turbojpeg、lmdb、tfrecords
- 消息队列的使用(kafka举例)
- 类加载过程,双亲委派模型?
- 图卷积网络-多标签分类
- MySQL parttion分区,以及分区和分表的区别
- ThreadPoolExcutor源码分析
- 动态分组卷积-Dynamic Group Convolution for Accelerating Convolutional Neural Networks
- (15)Shell概述及脚本执行方式
- jvm线上内存问题排查
- (18)Bash输入输出重定向
- RPC 和 REST还有RESTFul到底是个什么玩意?
- 线程和线程池的几个状态值
- 阿里代码规约为什么不让使用Executors包装好线程池呢?