C# 使用application/x-www-form-urlencoded content-type处理请求,但在NET Core Web API中使用XML正文

C# 使用application/x-www-form-urlencoded content-type处理请求,但在NET Core Web API中使用XML正文,c#,xml,asp.net-core-webapi,content-type,asp.net-core-2.1,C#,Xml,Asp.net Core Webapi,Content Type,Asp.net Core 2.1,我有一个.NETCore2.1WebAPI控制器和方法,用来处理POST XML请求 通过HTTP从外部服务。 下面是控制器操作的方法标题 [HttpPost] [Produces("application/xml")] public async Task<IActionResult> PostReceivedMessage([FromBody] ReceivedMessage receivedMessage) [HttpPost] [产生(“应用程序/xm

我有一个.NETCore2.1WebAPI控制器和方法,用来处理POST XML请求 通过HTTP从外部服务。 下面是控制器操作的方法标题

    [HttpPost]
    [Produces("application/xml")]
    public async Task<IActionResult> PostReceivedMessage([FromBody] ReceivedMessage receivedMessage)
[HttpPost]
[产生(“应用程序/xml”)]
公共异步任务PostReceivedMessage([FromBody]ReceivedMessage ReceivedMessage)
我编写了一个定制的XML输入格式化程序来处理XML请求,当 我向应用程序的控制器操作发布了一个来自Postman的示例XML请求。 但当服务发送类似的请求时,来自应用程序的响应状态为400,Bad request

经过一些调试后,我发现这些请求与

内容类型:应用程序/x-www-form-urlencoded

而不是人们可能期望的application/xml或text/xml。 如果我更改标题以匹配应用程序中的内容类型,应用程序也会显示相同的行为 由外部服务发送的请求

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

public async Task<IActionResult> PostReceivedMessage([FromForm] ReceivedMessage receivedMessage)
公共异步任务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>
POST/check/api/receivedmessages HTTP/1.1
缓存控制:没有缓存
Pragma:没有缓存
用户代理:Java/1.7.0\u 45
主持人:xxx.xxx.xxx.xxx
接受:text/html、image/gif、image/jpeg、*;q=.2,*/*;q=.2
连接:保持活力
内容类型:application/x-www-form-urlencoded
内容长度:270
957152743558610003456789550412019/10/20 02:23:55MOUserDataVHVqrA==DA555

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

[HttpPost]
[产生(“应用程序/xml”)]
公共异步任务PostReceivedMessage([FromForm]字符串receivedMessage)
{
XmlSerializer serializer=新的XmlSerializer(typeof(ReceivedMessage));
接收到的消息数据;
使用(TextReader=new StringReader(receivedMessage))
{
数据=(ReceivedMessage)序列化程序。反序列化(读取器);
}
返回Ok(数据);
}
我最终采纳了评论中的建议,并实施了 自定义模型活页夹。 我不确定这是否有点过分,但至少它让控制器的动作保持“精益”

自定义模型活页夹如下所示:

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;
        }
    }
有一件事需要注意。外部服务发送的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>
[key]957152743558610003456789550412019/10/20 02:23:55鼠标数据vqra
[值]=DA555
因此,我的模型绑定器笨拙地提取请求体的方式就是这个原因 串成一串


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

收到的请求是什么样子的?你能发布一个这样的示例吗?使用像wireshark或fiddler这样的嗅探器,将postman the works中的第一个请求与不工作的c#app进行比较。然后让c#看起来和邮递员的要求一模一样。c#中的默认标题与postman不同,使c#请求看起来像是postman可以解决问题。@JSteward更新了帖子,使其包含一个示例。@jdweng已经使用Wireshark比较了这两个请求。这就是为什么我发现只有当内容类型为application/x-www-form-urlencoded而不是内容类型为application/xml时,才会返回错误的请求状态。获取xml,而不是x-www-form-urlencoded。您应该能够从响应中以字符串的形式获取xml。然后使用xml方法处理xml字符串。没用。由于某些原因,receivedMessage显示为null,而不是以字符串形式包含请求正文。@Steve S由于asp.net core无法使用内置绑定直接获取数据,您可能需要使用自定义模型绑定首先获取请求正文。然后将其绑定到操作中所需的数据类型。我最终使用了自定义模型绑定器。我把答案贴了出来。非常感谢你为我指明了方向。
[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; }
    }
[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>