【Azure API 管理】使用APIM進行XML內容讀取時遇見的詭異錯誤 Expression evaluation failed. Object reference not set to an instance of an object.
- 2022 年 3 月 8 日
- 筆記
- 【Azure API 管理】, APIM, APIM Policy 解析XML語句, Expression evaluation failed, preserveContent:true, set-variable
問題描述
使用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="//schemas.xmlsoap.org/soap/cdhotel/"> <Body> <GetHotel xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xmlns="//tempuri.org/"> <input> <ID xmlns="//schemas.datacontract.org/2014/01/wcf">202203081007001</ID> <Name xmlns="//schemas.datacontract.org/2014/01/wcf">Cheng Du Junyi Hotel</Name> <Code xmlns="//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的語句,兩者語法完全一樣。
- 但第二次就出現了 未將對象應用到實例的異常。
錯誤截圖:
問題解決
經過反覆實驗,問題肯定出現在 context.Request.Body.As<XElement> 上,是不是這個內容只能使用一次呢? 經 Google 搜尋,終於得出了官方解釋和解決辦法:
官方解釋
文檔鏈接://docs.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables
IMessageBody | As<T>(preserveContent: bool = false): Where T: string, byte[],JObject, JToken, JArray, XNode, XElement, XDocument
The
To avoid that and have the method operate on a copy of the body stream, set the |
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文件動畫:
注意:
- 因為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 : //docs.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables
Get an attribute value from XML Response in azure apim : //stackoverflow.com/questions/68618339/get-an-attribute-value-from-xml-response-in-azure-apim
XElement Class : //docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xelement?view=net-6.0