在 NET Core Web API 中使用 application/x-www-form-urlencoded content-type 但 XML body 处理请求
Handle Request with application/x-www-form-urlencoded content-type but XML body in NET Core Web API
我有一个 .NET Core 2.1 Web API 控制器和方法应该使用 POST XML 请求
通过 HTTP 从外部服务。
下面是控制器动作的方法header。
[HttpPost]
[Produces("application/xml")]
public async Task<IActionResult> PostReceivedMessage([FromBody] ReceivedMessage receivedMessage)
我编写了一个自定义 XML 输入格式化程序来处理 XML 请求,它在以下情况下工作正常
我 post 从 Postman 到应用程序控制器操作的示例 XML 请求。
但是当服务发送类似的请求时,应用程序的响应状态为 400,错误请求。
经过一些调试,我发现请求进来的是
Content-Type: application/x-www-form-urlencoded
而不是人们可能期望的 application/xml 或 text/xml。
如果我更改 header 以匹配
外部服务发送的请求。
我假设 x-www-form-urlencoded 用于表单数据,因为模型绑定不起作用
当我将操作 header 更改为:
public async Task<IActionResult> PostReceivedMessage([FromForm] ReceivedMessage receivedMessage)
由于我无法控制外部服务,我应该如何使控制器操作能够处理 x-www-form-urlencoded 作为 content-type 的 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>
获取字符串并将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);
}
我最终采纳了评论中提供的 @Xing Zou 建议并实施了
自定义模型活页夹。
我不确定这是否矫枉过正,但至少它保持了控制器的动作 "lean".
自定义模型活页夹如下所示:
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>
因此,我的模型绑定器笨拙地提取请求正文的方式的原因
成一个字符串。
我想如果不是这种情况,只需阅读表格中的第一个(也是唯一一个键)就可以获得正文。
我有一个 .NET Core 2.1 Web API 控制器和方法应该使用 POST XML 请求 通过 HTTP 从外部服务。 下面是控制器动作的方法header。
[HttpPost]
[Produces("application/xml")]
public async Task<IActionResult> PostReceivedMessage([FromBody] ReceivedMessage receivedMessage)
我编写了一个自定义 XML 输入格式化程序来处理 XML 请求,它在以下情况下工作正常 我 post 从 Postman 到应用程序控制器操作的示例 XML 请求。 但是当服务发送类似的请求时,应用程序的响应状态为 400,错误请求。
经过一些调试,我发现请求进来的是
Content-Type: application/x-www-form-urlencoded
而不是人们可能期望的 application/xml 或 text/xml。 如果我更改 header 以匹配 外部服务发送的请求。
我假设 x-www-form-urlencoded 用于表单数据,因为模型绑定不起作用 当我将操作 header 更改为:
public async Task<IActionResult> PostReceivedMessage([FromForm] ReceivedMessage receivedMessage)
由于我无法控制外部服务,我应该如何使控制器操作能够处理 x-www-form-urlencoded 作为 content-type 的 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>
获取字符串并将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);
}
我最终采纳了评论中提供的 @Xing Zou 建议并实施了 自定义模型活页夹。 我不确定这是否矫枉过正,但至少它保持了控制器的动作 "lean".
自定义模型活页夹如下所示:
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>
因此,我的模型绑定器笨拙地提取请求正文的方式的原因 成一个字符串。
我想如果不是这种情况,只需阅读表格中的第一个(也是唯一一个键)就可以获得正文。