问题描述
使用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">202203081007001ID> <Name xmlns="http://schemas.datacontract.org/2014/01/wcf">Cheng Du Junyi HotelName> <Code xmlns="http://schemas.datacontract.org/2014/01/wcf">ICP1009100Code> input> GetHotel> Body> CDHotel>
在APIM Policies中,需要获取 ID, Name, Code 和 Desc 值,策略语句如下:
<policies> <inbound> <base /> <set-variable name="myID" value="@( context.Request.Body.As().Descendants().FirstOrDefault(x => x.Name.LocalName == " ID")?.Value )" /> <set-variable name="myName" value="@( context.Request.Body.As().Descendants().FirstOrDefault(x => x.Name.LocalName == " Name")?.Value )" /> <set-variable name="myCode" value="@( context.Request.Body.As().Descendants().FirstOrDefault(x => x.Name.LocalName == " Code")?.Value )" /> <set-variable name="myDesc" value="@( context.Request.Body.As().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
官方解释
文档链接:https://docs.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables
IMessageBody | As The context.Request.Body.As and context.Response.Body.As methods are used to read either a request and response message body in specified type T . By default, the method:
To avoid that and have the method operate on a copy of the body stream, set the preserveContent parameter to true , as in this example. |
context.Request.Body.As
context.Response.Body.As
方法用As
解决办法
正如文档中解释,使用 preserveContent : true 后,可以多次转换 Body Stream。
修改后的Policy为:
<inbound> <base /> <set-variable name="myID" value="@( context.Request.Body.As(preserveContent:true).Descendants().FirstOrDefault(x => x.Name.LocalName == " ID")?.Value )" /> <set-variable name="myName" value="@( context.Request.Body.As(preserveContent:true).Descendants().FirstOrDefault(x => x.Name.LocalName == " Name")?.Value )" /> <set-variable name="myCode" value="@( context.Request.Body.As(preserveContent:true).Descendants().FirstOrDefault(x => x.Name.LocalName == " Code")?.Value )" /> <set-variable name="myDesc" value="@( context.Request.Body.As(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 : 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