最近在项目中启用了Nullable 可为空的类型,这个特性确实很好用,在 WebAPI 的入参上可以直接采用 ?来标记一个字段是否允许为空,但是使用过程中遇到了如下一个问题,比如创建部门接口
我们定义入参模型如下:.
public class DtoDepartment{/// <summary>/// 部门名称/// </summary>public string Name { get; set; }/// <summary>/// 上级部门ID/// </summary>public Guid? ParentId { get; set; }}
我们这里将上级部门ID定义为可以为空的类型,因为有些部门不存在上级部门
然后定义这样一个接口
[HttpPost("CreateDepartment")]public bool CreateDepartment(DtoDepartment department){//省略业务逻辑return true;}
当前端请求的时候传入如下Json 时则就会触发异常
{"name": "商务一部","parentId": ""}
异常内容为:
{"errMsg": "The department field is required. | The JSON value could not be converted to System.Nullable`1[System.Guid]. Path: $.parentId | LineNumber: 2 | BytePositionInLine: 16."}
像这样的情况是因为虽然我们定义的 Dto 允许上级部门ID字段为空,但是前端调用的时候 parentId 实际传入的是 "" 空字符串,当空字符串给 Guid? 转换的时候就会产生这样的异常,当遇到这样的情况时,我们可以要求前端调整 JSON 格式如下
{"name": "商务一部","parentId": null}
前端只要给 parentId 的赋值从 "" 调整为 null 之后我们的接口就可以正常运行了,但是有的时候前端的组件这里取值可能是和一些组件库绑定的,不太方便绑定默认值为 null,很多情况下组件库组件的默认值都是 "" 空字符串的形式,所以经过和前端同事多次沟通之后想着从后端彻底解决这个问题,经过研究之后编写了下面的 NullableConverter 转换器,只要在项目启动的时候注册到 AddJsonOptions 其中即可。
using System.Text.Json;using System.Text.Json.Serialization;namespace Common.JsonConverter{public class NullableConverter<T> : JsonConverter<T?> where T : struct{public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options){if (reader.TokenType == JsonTokenType.String){if (string.IsNullOrEmpty(reader.GetString()) || string.IsNullOrWhiteSpace(reader.GetString())){return null;}}return JsonSerializer.Deserialize<T>(ref reader, options);}public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options){JsonSerializer.Serialize(writer, value!.Value, options);}}}
上面我们只是用 Guid? 举了一个例子,实际情况下
-
DateTime?
-
DateTimeOffset?
-
long?
-
int?
-
double?
-
decimal?
-
float?
-
Guid?
-
bool?
都有可能存在这个问题,所以我们为这几种类型都配置了这个可为空类型转换器。这样前端在调用接口时配到这类型的字段,传 "" 和 null 我们后端就都可以接收了,收到之后字段的值都是 null
我这里项目采用的是微软的 System.Text.Json 处理的 Json 序列化,注册配置 NullableConverter 代码如下:
#region 注册 Json 序列化配置builder.Services.AddControllers().AddJsonOptions(options =>{options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<DateTime>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<DateTimeOffset>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<long>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<int>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<double>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<decimal>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<float>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<Guid>());options.JsonSerializerOptions.Converters.Add(new Common.JsonConverter.NullableConverter<bool>());});#endregion