You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 08:06:23