Web API2调用时JObject转mproject实体抛出无效类型转换异常
问题描述
调用Web API 2接口时遇到如下错误:
Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'NG_API_DNET_FRX.Models.mproject'.
站点前端使用Angular框架,网页端发送的原始JSON内容如下:
{ "id": "3137", "clientId": "2", "Name": "MFAQ project1", "EstimatedStartDate": "07/01/2022", "EstimatedEndDate": "07/08/2022", "ActualStartDate": "07/15/2022", "ActualEndDate": "07/22/2022", "EstimatedBudget": "44444.0000", "ActualBudget": "55555.0000" }
调试时通过监视窗口复制的接口实际接收到的参数JSON表示如下:
注意:该对象外层包裹了双层大括号{{...}},JSONLint会对该格式报错,但此前所有接口调用都存在该现象且Web API可正常处理,不确定是否为本次问题的诱因。
{{ "id": 3137, "clientId": 2, "Name": "MFAQ project1", "EstimatedStartDate": "07/13/2022", "EstimatedEndDate": "6/8/2022", "ActualStartDate": "6/15/2022", "ActualEndDate": "6/22/2022", "EstimatedBudget": 44444, "ActualBudget": 55555 }}
目标接收结构体的C#定义及接口代码如下:
public class mproject { public int id; public int clientId; public string Name; public string EstimatedStartDate; public string EstimatedEndDate; public string ActualStartDate; public string ActualEndDate; public decimal EstimatedBudget; public decimal ActualBudget; public string sbProperties; public string projectType; public mprojectRev[] Revisions; } [System.Web.Http.HttpPatch] [Route("{itemId_}")] public IHttpActionResult PatchItemById([FromUri] int itemId_, [FromBody] mproject webForm_ ) { // 直接指定参数类型为mproject时,webForm_为null // 如果把参数类型改成dynamic/object,手动强转时就会抛出上述转换错误 //mproject webForm_; //try //{ // webForm_ = (mproject)webForm_1; //} //catch (Exception ex) //{ // return JSONStringResultExtension.JSONString(this, errorAsJSON(ex), HttpStatusCode.InternalServerError); //} }
该异常无内部异常。传入的参数未包含mproject类定义的3个属性,已排除该因素(此前缺失属性的场景均可正常处理)。近期在调试日期字段相关逻辑,怀疑日期格式是问题诱因。
核心问题:为什么该类型转换会抛出异常?
需要了解获取更多异常详情定位根因的方法,以及对应的故障排查技巧。
问题根因
按影响优先级排序:
- 核心诱因:mproject类用的是公共字段,不是公共属性
这是Web API 2里极其常见的坑。Web API 2默认用Newtonsoft.Json做序列化,默认配置下只会反序列化带get/set访问器的公共属性,完全忽略公共字段。你现在类里写的全是public int id;这种字段定义,序列化器找不到可写入的属性入口,没法把请求JSON里的值映射到mproject实例,直接导致模型绑定失败,所以你用mproject类型接参数的时候拿到的是null。
如果你把参数类型改成object/dynamic,拿到的是原始解析后的JObject对象,这时候直接写(mproject)jObj做强转,走的是CLR原生类型校验:JObject和mproject没有任何继承、类型转换实现关系,必然抛出你看到的类型转换异常。
你提到之前缺失字段也能正常处理,大概率是之前的模型类写的都是带get/set的属性,或者全局Json配置改过支持字段,之后被重置了。 - 双层大括号不是请求格式问题,是VS调试监视窗口的显示bug
调试器渲染JObject类型对象时会额外套一层大括号显示,实际内存里存的就是正常的单大括号JSON结构,所有接口调用都存在该现象也能佐证这一点,完全不用在这上面花时间排查。 - 日期格式、缺失字段都不是问题诱因
你把日期字段定义成了string类型,只要传的是字符串值就不会绑定失败;缺失字段的话序列化器会直接给对应类型赋默认值(int为0、引用类型为null),不会导致整个模型绑定直接失败。
排查&修复方案
- 优先改模型定义,把所有公共字段改成自动属性,这是最符合.NET开发规范、也不用改动全局配置的方案:
改完之后直接用public class mproject { public int id {get;set;} public int clientId {get;set;} public string Name {get;set;} public string EstimatedStartDate {get;set;} public string EstimatedEndDate {get;set;} public string ActualStartDate {get;set;} public string ActualEndDate {get;set;} public decimal EstimatedBudget {get;set;} public decimal ActualBudget {get;set;} public string sbProperties {get;set;} public string projectType {get;set;} public mprojectRev[] Revisions {get;set;} }[FromBody] mproject webForm_接收参数即可,绝大多数场景下直接恢复正常。 - 如果因为历史代码原因不能改字段定义,就去
WebApiConfig.cs里修改Json序列化配置,开启公共字段的序列化支持(注意要引用Newtonsoft.Json.Serialization和System.Reflection命名空间):config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver() { DefaultMembersSearchFlags = BindingFlags.Public | BindingFlags.Instance }; - 如果确实需要手动接收JObject再做转换,不要直接用C#强转语法,要用Newtonsoft自带的
ToObject<T>()方法,这个方法会走序列化映射逻辑,不是做CLR原生类型校验:// 错误写法:直接强转 // webForm_ = (mproject)webForm_1; // 正确写法 webForm_ = ((JObject)webForm_1).ToObject<mproject>(); - 后续排查模型绑定问题不用盲目猜测,直接在Action里访问
ModelState属性,里面会把每个字段绑定失败的具体原因列得清清楚楚,比如类型不匹配、格式错误等,比盯着请求JSON排查效率高得多。如果要全局捕获绑定错误,也可以写个模型绑定日志过滤器,统一记录所有绑定错误信息。
内容的提问来源于stack exchange,提问作者greg
相关产品推荐
相关产品推荐

