• 【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的语句,两者语法完全一样。
    • 但第二次就出现了 未将对象应用到实例的异常。

    错误截图:

     

    问题解决

    经过反复实验,问题肯定出现在 context.Request.Body.As<XElement> 上,是不是这个内容只能使用一次呢? 经 Google 搜寻,终于得出了官方解释和解决办法:

    官方解释

    文档链接:https://docs.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables

    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 : 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

  • 相关阅读:
    读取纯真IP数据库C++源代码
    Ubuntu 安装VMware tools 解决方法讨论
    VMware虚拟机磁盘压缩
    使用 TestLink 进行测试管理
    正则表达式30分钟入门教程(转)
    HDU 1325 Is It A Tree? POJ 1308 Is It A Tree? (并查集+入度,判断一个有向图是树)
    POJ 1703 Find them, Catch them (并查集)
    POJ 1182 食物链(并查集)
    POJ 1984 Navigation Nightmare (并查集)
    POJ 2236 Wireless Network (并查集)
  • 原文地址:https://www.cnblogs.com/lulight/p/15979446.html
Copyright © 2020-2023  润新知