Обработайте запрос с типом содержимого application / x-www-form-urlencoded, но с телом XML в NET Core Web API - PullRequest
0 голосов
/ 22 октября 2019

У меня есть контроллер и метод веб-API .NET Core 2.1, который должен принимать запросы POST XML от внешней службы через HTTP. Ниже приведен заголовок метода действия контроллера.

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

Я написал специальный форматировщик входных данных XML для обработки XML-запроса, который прекрасно работает, когда я отправляю пример XML-запроса от Postman в действие контроллера приложения. ,Но когда служба отправляет подобный запрос, ответ от приложения имеет статус 400, Bad Request.

После некоторой отладки я обнаружил, что запросы поступают с

содержимым-Тип: application / x-www-form-urlencoded

вместо application / xml или text / xml, как и следовало ожидать. То же поведение проявляется приложением, если я изменяю заголовок в соответствии с типом содержимого в запросе, отправляемом внешней службой.

Я предполагаю, что x-www-form-urlencoded предназначен для данных формы, посколькуПривязка модели не работает, когда я изменяю заголовок действия на:

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

Поскольку у меня нет контроля над внешней службой, как я должен сделать действие контроллера способным обрабатывать запросы XML с помощью x-www-form-urlencoded как тип содержимого?

ОБНОВЛЕНИЕ: Ниже приведен пример запроса:

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>

Ответы [ 2 ]

0 голосов
/ 26 октября 2019

Я закончил тем, что принял предложение @ Син Цзоу , предложенное в комментариях, и реализовал привязку пользовательской модели. Я не уверен, является ли это излишним, но, по крайней мере, это делает действие контроллера «постным».

Пользовательское связующее для модели выглядит следующим образом:

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>

Следовательно, причина, по которой механизм связывания моей модели неловко извлекает тело запроса в строку.

Полагаю, если бы это было не так, можно получитьтело, просто прочитав первый (и единственный ключ) в форме.

0 голосов
/ 22 октября 2019

Просто получить строку и преобразовать строку 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);
    }
...