【问题标题】:Handle Request with application/x-www-form-urlencoded content-type but XML body in NET Core Web API在 NET Core Web API 中使用 application/x-www-form-urlencoded 内容类型但 XML 正文处理请求
【发布时间】:2020-02-18 00:46:52
【问题描述】:

我有一个应该使用 POST XML 请求的 .NET Core 2.1 Web API 控制器和方法 通过 HTTP 从外部服务。 下面是控制器动作的方法头。

    [HttpPost]
    [Produces("application/xml")]
    public async Task<IActionResult> PostReceivedMessage([FromBody] ReceivedMessage receivedMessage)

我编写了一个自定义 XML 输入格式化程序来处理 XML 请求,当 我将来自 Postman 的示例 XML 请求发布到应用程序的控制器操作。 但是当服务发送类似请求时,应用的响应状态为 400,Bad Request。

经过一些调试,我发现请求进来了

内容类型:application/x-www-form-urlencoded

而不是人们可能期望的 application/xml 或 text/xml。 如果我更改标头以匹配 外部服务发送的请求。

我假设 x-www-form-urlencoded 用于表单数据,因为模型绑定不起作用 当我将动作标题更改为:

public async Task<IActionResult> PostReceivedMessage([FromForm] ReceivedMessage receivedMessage)

由于我无法控制外部服务,我应该如何使控制器操作能够处理以 x-www-form-urlencoded 作为内容类型的 XML 请求?

更新: 下面是一个示例请求:

POST /check/api/receivedmessages HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.7.0_45
Host: xxx.xxx.xxx.xxx
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-type: application/x-www-form-urlencoded
Content-Length: 270

<Request><requestId>95715274355861000</requestId><msisdn>345678955041</msisdn><timeStamp>2019/10/20 02:23:55</timeStamp><keyword>MO</keyword><dataSet><param><id>UserData</id><value>VHVqrA==</value></param><param><id>DA</id><value>555</value></param></dataSet></Request>

【问题讨论】:

  • 收到的请求是什么样的?你能发一个样本吗?
  • 使用像 wireshark 或 fiddler 这样的嗅探器,并在 postman 中将第一个请求与非工作 c# 应用程序进行比较。然后让 c# 看起来完全像邮递员的请求。 c# 的默认标头与 postman 不同,使 c# 请求看起来像 postman 将解决问题。
  • @JSteward 更新了帖子以包含示例。
  • @jdweng 已经使用 Wireshark 来比较两个请求。这就是我发现错误请求状态仅在使用 Content-type: application/x-www-form-urlencoded 而不是使用 Content-type: application/xml 时返回的原因。
  • 获取 XML,而不是 x-www-form-urlencoded。您应该能够从响应中获取 xml 作为字符串。然后使用 XML 方法处理 xml 字符串。

标签: c# xml asp.net-core-webapi content-type asp.net-core-2.1


【解决方案1】:

我最终接受了 cmets 中提供的 @Xing Zou 建议并实施了 自定义模型绑定器。 我不确定这是否矫枉过正,但至少它使控制器动作保持“精简”。

自定义模型绑定器如下所示:

public class ReceivedMessageEntityBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            var request = bindingContext.HttpContext.Request;

            var firstKey = request.Form.Keys.First();
            StringValues formValue = "";

            request.Form.TryGetValue(firstKey, out formValue);

            var requestBody = firstKey + "=" + formValue;

            bindingContext.Result = ModelBindingResult.Success(FromXmlString(requestBody));

            return Task.CompletedTask;  
        }

        private ReceivedMessage FromXmlString(string requestBody)
        {
            XElement request = XElement.Parse(requestBody);

            var receivedMessage = new ReceivedMessage();

            receivedMessage.RequestId = (string)
                                        (from el in request.Descendants("requestId")
                                         select el).First();

            receivedMessage.Msisdn = (string)
                                        (from el in request.Descendants("msisdn")
                                         select el).First();


            receivedMessage.Timestamp = DateTime.Parse(
                                        (string)
                                        (from el in request.Descendants("timeStamp")
                                         select el).First());


            receivedMessage.Keyword = (string)
                                        (from el in request.Descendants("keyword")
                                         select el).First();


            IEnumerable<XElement> dataSet = from el in request.Descendants("param")
                                            select el;

            foreach (var param in dataSet)
            {
                var firstNode = param.Descendants().First();

                switch (firstNode.Value)
                {
                    case "UserData":
                        receivedMessage.UserData = (firstNode.NextNode as XElement).Value;
                        break;

                    case "DA":
                        receivedMessage.Da = (firstNode.NextNode as XElement).Value;
                        break;
                }
            }

            return receivedMessage;
        }
    }

并且模型现在被修饰为,以便可以使用自定义模型绑定器来绑定它。

[ModelBinder(BinderType = typeof(ReceivedMessageEntityBinder))]
    public class ReceivedMessage
    {
        public long Id { get; set; }

        [StringLength(12)]
        public string Msisdn { get; set; }
        public string RequestId { get; set; }
        public DateTime Timestamp { get; set; }
        public string Keyword { get; set; }
        public string UserData { get; set; }
        public string Da { get; set; }
    }

有一点需要注意。外部服务发送的 XML 包含一个经过 base64 编码的值。这意味着有两个“=”符号,我的猜测是这会导致正文被解释为具有 1 个键和 1 个值的形式。例如:

[key]<Request><requestId>95715274355861000</requestId><msisdn>345678955041</msisdn><timeStamp>2019/10/20 02:23:55</timeStamp><keyword>MO</keyword><dataSet><param><id>UserData</id><value>VHVqrA
[value]=</value></param><param><id>DA</id><value>555</value></param></dataSet></Request>

因此,我的模型绑定器笨拙地提取请求正文的原因 变成一个字符串。

我想如果不是这样,只需阅读表单中的第一个(也是唯一的键)就可以获取正文。

【讨论】:

    【解决方案2】:

    获取字符串并将xml字符串转换为C#对象很简单:

    [HttpPost]
    [Produces("application/xml")]      
    public async Task<IActionResult> PostReceivedMessage([FromForm]string receivedMessage)
        {
    
            XmlSerializer serializer = new XmlSerializer(typeof(ReceivedMessage));
            ReceivedMessage data;
            using (TextReader reader = new StringReader(receivedMessage))
            {
                 data = (ReceivedMessage)serializer.Deserialize(reader);
            }
    
    
            return Ok(data);
        }
    

    【讨论】:

    • 刚试过这个。没用。由于某种原因 receivedMessage 显示为 null 而不是将请求正文包含为字符串。
    • @Steve S 您可能需要先使用自定义模型绑定来获取您的请求正文,因为 asp.net 核心无法使用内置绑定直接获取数据。然后将其绑定到您在操作上所需的数据类型。
    • 我最终使用了自定义模型绑定器。我发布了解决方案作为答案。非常感谢您为我指明了这个方向。
    猜你喜欢
    • 1970-01-01
    • 2021-12-27
    • 2018-04-04
    • 2015-06-18
    • 1970-01-01
    • 2014-03-30
    • 2021-02-03
    • 2015-04-02
    • 1970-01-01
    相关资源
    最近更新 更多