D365FO中X++调用第三方API报400错误,求代码排查与修正
D365FO中X++调用第三方API报400 Bad Request错误排查与修正
问题背景
在Dynamics 365 Finance and Operations(D365FO)中,使用X++代码通过Bearer Token认证调用第三方API时,收到错误:The remote server returned an error: (400) Bad Request.,以下是原代码,需排查问题并提供正确示例。
原代码
class JSONDeSerialization { /// <summary> /// Runs the class with the specified arguments. /// </summary> /// <param name = "_args">The specified arguments.</param> public void consumeAPI() { str destinationUrl = 'https://abc.api/', responseJSON; str token = 'xxxxxx'; System.Net.HttpWebRequest request; System.Net.HttpWebResponse response; CLRObject clrObj; System.IO.Stream requestStream, responseStream; System.IO.StreamReader streamReader; System.Exception ex; System.Net.WebHeaderCollection httpHeader; System.IO.Stream stream; System.IO.Stream dataStream; try { new InteropPermission(InteropKind::ClrInterop).assert(); httpHeader = new System.Net.WebHeaderCollection(); clrObj = System.Net.WebRequest::Create(destinationUrl); request = clrObj; httpHeader.Add("authorization", "Bearer " + token); request.set_KeepAlive(true); request.set_ContentType("application/json"); request.set_Method("POST"); request.set_Headers(httpHeader); response = request.GetResponse(); responseStream = response.getResponseStream(); streamReader = new System.IO.StreamReader(responseStream); responseJSON = streamReader.ReadToEnd(); info(responseJSON); //JSON deserialatisation JSONObjectContract jSONObjectContract = FormJsonSerializer::deserializeObject(classnum(JSONObjectContract), responseJSON); List listdata = new List(Types::Class); listdata = jSONObjectContract.parmdata(); ListEnumerator listEnumerator = listdata.getEnumerator(); while(listEnumerator.moveNext()) { EInvoiceDataContract jsonDataContract = listEnumerator.current(); info(strFmt("%1 %2",jsonDataContract.parmIGSTRate(), jsonDataContract.parmIGSTAmount())); } //JSON deserialisation streamReader.Close(); responseStream.Close(); response.Close(); } catch(Exception::CLRError) { ex = CLRInterop::getLastException().GetBaseException(); error(ex.get_Message()); } } }
原代码问题分析
- 缺少请求体数据:POST请求要求提交JSON格式的请求体,但原代码仅配置了请求头,未向请求流写入任何数据,这是400错误的核心原因。
- Header设置不规范:直接用
WebHeaderCollection添加Authorization头,.NET中部分HTTP头(如Authorization)建议通过专属方法设置,避免潜在的头处理冲突。 - 资源管理不严谨:手动调用Close方法释放资源,未使用X++的
UsingStatement自动管理CLR资源,存在资源泄漏风险。 - 错误信息不完整:仅捕获CLRError,未处理其他D365异常,也未获取API返回的详细错误响应(400错误通常包含具体错误描述)。
修正后的代码示例
class JSONAPIConsumer { public void consumeAPI() { str destinationUrl = 'https://abc.api/'; str token = 'xxxxxx'; // 根据第三方API要求构造JSON请求体 str requestBody = '{"field1": "value1", "field2": 123}'; System.Net.HttpWebRequest request; System.Net.HttpWebResponse response; System.IO.Stream requestStream; System.IO.StreamReader streamReader; System.Exception ex; str responseJSON; str errorResponse; try { new InteropPermission(InteropKind::ClrInterop).assert(); // 创建并配置请求对象 request = System.Net.WebRequest::Create(destinationUrl) as System.Net.HttpWebRequest; request.set_Method("POST"); request.set_ContentType("application/json"); // 正确设置Bearer Token认证头 request.set_Authorization(new System.Net.NetworkCredential("", token).GetCredential(destinationUrl, "Bearer")); request.set_KeepAlive(true); // 写入JSON请求体到请求流 if (!emptyStr(requestBody)) { requestStream = request.GetRequestStream(); System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(); System.Byte[] byteArray = encoding.GetBytes(requestBody); requestStream.Write(byteArray, 0, byteArray.get_Length()); requestStream.Close(); } // 获取API响应 response = request.GetResponse() as System.Net.HttpWebResponse; streamReader = new System.IO.StreamReader(response.get_ResponseStream()); responseJSON = streamReader.ReadToEnd(); info(strFmt("API响应内容: %1", responseJSON)); // JSON反序列化逻辑(根据实际Contract调整) JSONObjectContract jsonObjContract = FormJsonSerializer::deserializeObject(classnum(JSONObjectContract), responseJSON); List dataList = jsonObjContract.parmdata(); ListEnumerator enumerator = dataList.getEnumerator(); while (enumerator.moveNext()) { EInvoiceDataContract dataContract = enumerator.current(); info(strFmt("IGST税率: %1, IGST金额: %2", dataContract.parmIGSTRate(), dataContract.parmIGSTAmount())); } // 释放资源 streamReader.Close(); response.Close(); } catch(Exception::CLRError) { ex = CLRInterop::getLastException().GetBaseException(); error(strFmt("CLR错误信息: %1", ex.get_Message())); // 尝试获取API返回的详细错误响应 try { System.Net.HttpWebResponse errorResp = CLRInterop::getLastException().get_InnerException().get_Response() as System.Net.HttpWebResponse; if (errorResp != null) { streamReader = new System.IO.StreamReader(errorResp.get_ResponseStream()); errorResponse = streamReader.ReadToEnd(); error(strFmt("API错误详情: %1", errorResponse)); streamReader.Close(); errorResp.Close(); } } catch { // 忽略获取错误响应时的异常 } } catch(Exception::Error) { error(strFmt("D365内部错误: %1", error())); } finally { CodeAccessPermission::revertAssert(); } } }
关键注意事项
- 请求体必须匹配API要求:
requestBody需严格按照第三方API文档构造,确保字段名称、数据类型、嵌套结构完全符合要求。 - 验证Token有效性:确认
token是有效的Bearer Token,未过期且拥有该API的调用权限。 - 检查URL完整性:确保
destinationUrl包含正确的API端点路径(如https://abc.api/submit-invoice),而非仅域名。 - 调试建议:先用Postman等工具验证请求的正确性,对比X++代码的请求头、请求体是否与Postman一致,快速定位问题。
内容的提问来源于stack exchange,提问作者Abhilash Abhi
相关产品推荐
相关产品推荐

