天天看點

【Azure API 管理】使用APIM進行XML内容讀取時遇見的詭異錯誤 Expression evaluation failed. Object reference not set to an instance of an object.

問題描述

使用APIM,在 Inbound 中對請求的Body内容進行解析。用戶端請求所傳遞的Request Body為XML格式,需要從Request Body中解析出多個(Element)節點值,然後設定通過(set-variable)為參數在後續使用。

但是驗證發現,當且隻當使用一個set-variable 從 Request Body中讀取資料時候,是可以成功的。如果要讀取第二個,第三個時,始終會遇見一個詭異的錯誤 Expression evaluation failed. Object reference not set to an instance of an object。 關鍵問題是,為什麼第一個可以成功,第二個的語句和第一個完全一樣,卻面臨如此問題?真是詭異!

需要解析的XML格式如下:

<?xml version="1.0" encoding="utf-8"?>
<CDHotel xmlns="http://schemas.xmlsoap.org/soap/cdhotel/">
<Body>
<GetHotel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/">
<input>
<ID xmlns="http://schemas.datacontract.org/2014/01/wcf">202203081007001</ID>
<Name xmlns="http://schemas.datacontract.org/2014/01/wcf">Cheng Du Junyi Hotel</Name>
<Code xmlns="http://schemas.datacontract.org/2014/01/wcf">ICP1009100</Code>
</input>
</GetHotel>
</Body>
</CDHotel>      

在APIM Policies中,需要擷取 ID, Name, Code 和 Desc 值,政策語句如下:

<!--
    IMPORTANT:
    - Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements.
    - To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element.
    - To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element.
    - To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar.
    - To remove a policy, delete the corresponding policy statement from the policy document.
    - Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope.
    - Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope.
    - Policies are applied in the order of their appearance, from the top down.
    - Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope.
-->
<policies>
    <inbound>
        <base />
        <set-variable name="myID" value="@(
 context.Request.Body.As<XElement>().Descendants().FirstOrDefault(x => x.Name.LocalName == "ID")?.Value
        )" />
        <set-variable name="myName" value="@(
 context.Request.Body.As<XElement>().Descendants().FirstOrDefault(x => x.Name.LocalName == "Name")?.Value
        )" />
        <set-variable name="myCode" value="@(
 context.Request.Body.As<XElement>().Descendants().FirstOrDefault(x => x.Name.LocalName == "Code")?.Value
        )" />
        <set-variable name="myDesc" value="@(
 context.Request.Body.As<XElement>().Descendants().FirstOrDefault(x => x.Name.LocalName == "Desc")?.Value
        )" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <set-header name="myID" exists-action="override">
            <value>@((string)context.Variables["myID"])</value>
        </set-header>
        <set-header name="myName" exists-action="override">
            <value>@((string)context.Variables["myName"])</value>
        </set-header>
        <set-header name="myCode" exists-action="override">
            <value>@((string)context.Variables["myCode"])</value>
        </set-header>
        <set-header name="myDesc" exists-action="override">
            <value>@((string)context.Variables["myDesc"])</value>
        </set-header>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>      

在APIM的Test功能,檢視Trace語句後,錯誤消息為:

set-variable (0.905 ms)
    {
    "message": "Expression was successfully evaluated.",
    "expression": "\n context.Request.Body.As<XElement>().Descendants().FirstOrDefault(x => x.Name.LocalName == \"ID\")?.Value\n        ",
    "value": "202203081007001"
}
set-variable (0.013 ms)
    {
    "message": "Context variable was successfully set.",
    "name": "myID",
    "value": "202203081007001"
}
set-variable (7.898 ms)
    {
    "messages": [
        {
            "message": "Expression evaluation failed.",
            "expression": "\n context.Request.Body.As<XElement>().Descendants().FirstOrDefault(x => x.Name.LocalName == \"Name\")?.Value\n        ",
            "details": "Object reference not set to an instance of an object."
        },
        "Expression evaluation failed. Object reference not set to an instance of an object.",
        "Object reference not set to an instance of an object."
    ]
}      

說明:

  • 綠色高亮部分為Set-Variable的語句,兩者文法完全一樣。
  • 但第二次就出現了 未将對象應用到執行個體的異常。

錯誤截圖:

【Azure API 管理】使用APIM進行XML内容讀取時遇見的詭異錯誤 Expression evaluation failed. Object reference not set to an instance of an object.

問題解決

經過反複實驗,問題肯定出現在 context.Request.Body.As<XElement> 上,是不是這個内容隻能使用一次呢? 經 Google 搜尋,終于得出了官方解釋和解決辦法:

官方解釋

文檔連結:https://docs.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables

【Azure API 管理】使用APIM進行XML内容讀取時遇見的詭異錯誤 Expression evaluation failed. Object reference not set to an instance of an object.

context.Request.Body.As<T> 和 

context.Response.Body.As<T>

 方法用As<T>的方式指定讀取 Request 和 Response的Body内容,預設情況下,這個方式讀取的時原始消息的Body流,讀取一次後就變為不可用,也就是說隻能 As<T>的方式一次。這就解釋了為什麼第二個Set Variable語句出現 Object 異常。

解決辦法

正如文檔中解釋,使用 preserveContent : true 後,可以多次轉換  Body Stream。

修改後的Policy為:

<inbound>
        <base />
        <set-variable name="myID" value="@(
 context.Request.Body.As<XElement>(preserveContent:true).Descendants().FirstOrDefault(x => x.Name.LocalName == "ID")?.Value
        )" />
        <set-variable name="myName" value="@(
 context.Request.Body.As<XElement>(preserveContent:true).Descendants().FirstOrDefault(x => x.Name.LocalName == "Name")?.Value
        )" />
        <set-variable name="myCode" value="@(
 context.Request.Body.As<XElement>(preserveContent:true).Descendants().FirstOrDefault(x => x.Name.LocalName == "Code")?.Value
        )" />
        <set-variable name="myDesc" value="@(
 context.Request.Body.As<XElement>(preserveContent:true).Descendants().FirstOrDefault(x => x.Name.LocalName == "Desc")?.Value
        )" />
    </inbound>      

修改後,測試解析XML檔案動畫:

【Azure API 管理】使用APIM進行XML内容讀取時遇見的詭異錯誤 Expression evaluation failed. Object reference not set to an instance of an object.

注意:

  • 因為APIM執行個體的記憶體存在限制,内部的Memory限制為500MB,當緩存的Request/Response的内容大于500MB的時候,就會出現 MessagePayLoadTooLarge異常。
  • 當使用 preserveContent:true 後,會把目前的Body内容緩存在APIM執行個體的記憶體中,如果Body内容大于500MB,則會出現 MessagePayLoadTooLarge問題,是以對于Body Size過大的請求,不能使用 Buffer 及讀取整個Response/Request Body在Policy代碼中。

參考資料

API Management policy expressions - Context variable - IMessageBody : https://docs.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables

Get an attribute value from XML Response in azure apim : https://stackoverflow.com/questions/68618339/get-an-attribute-value-from-xml-response-in-azure-apim

XElement Class : https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xelement?view=net-6.0

當在複雜的環境中面臨問題,格物之道需:濁而靜之徐清,安以動之徐生。 雲中,恰是如此!

繼續閱讀