Модель всегда не указана в XML POST

В настоящее время я работаю над интеграцией между системами, и я решил использовать для этого WebApi, но у меня возникает проблема ...

Предположим, у меня есть модель:

public class TestModel
{
    public string Output { get; set; }
}

и метод POST:

public string Post(TestModel model)
{
    return model.Output;
}

Я создаю запрос от Fiddler с заголовком:

User-Agent: Fiddler
Content-Type: "application/xml"
Accept: "application/xml"
Host: localhost:8616
Content-Length: 57

и тело:

Sito

Параметр model в методе Post всегда null , и я понятия не имею, почему. У кого-нибудь есть ключ?

36
добавлено отредактировано
Просмотры: 1
de
как вы вызываете метод Post со стороны клиента? Вы уверены, что это HTTP POST?
добавлено автор wal, источник
Да, Питер, но вы выбрали POST в выпадающем списке скрипача? (по умолчанию GET)
добавлено автор wal, источник
Совет для выяснения того, что пошло не так (потому что Web API совершенно бесполезен в этом отношении) просто захватывает содержимое запроса через Request.Content.ReadAsStringAsync() и пытается десериализовать xml самостоятельно. Если что-то происходит, а не просто возвращает нуль, как это делает веб-API, XmlSerializer выдаст исключение, сообщив вам, почему он не может десериализоваться. В моем случае я понял, что в моем заявлении xml указано кодирование UTF-16, в то время как сам запрос был закодирован в кодировке UTF-8. Это то, что я никогда бы не подумал, просто не делая десериализацию.
добавлено автор Bas, источник
Скрипач. Кроме того, WebApi по умолчанию вызывает POST-вызовы методам POST, GET to GET ...
добавлено автор Wowca, источник
Конечно, я сделал. Как я уже сказал, вызов делается методом POST.
добавлено автор Wowca, источник

4 ответы

Две вещи:

  1. You don't need quotes "" around the content type and accept header values in Fiddler:

    User-Agent: Fiddler
    Content-Type: application/xml
    Accept: application/xml
    
  2. Web API uses the DataContractSerializer by default for xml serialization. So you need to include your type's namespace in your xml:

     
        Sito
     
    

    Or you can configure Web API to use XmlSerializer in your WebApiConfig.Register:

    config.Formatters.XmlFormatter.UseXmlSerializer = true;
    

    Then you don't need the namespace in your xml data:

     Sito
    
57
добавлено
Я не знал, что настройки WebAPI по умолчанию используют DataContractSerializer - спасибо за подсказку !!!
добавлено автор barrypicker, источник
Спасибо, потратил век, ища решение этого.
добавлено автор Paul Taylor, источник
Иди в том же вопросе с Джосоном. У меня была модель, украшенная атрибутами DataContract/Member. Проверено, что я могу де-сериализовать в модульном тесте. Был способен POST к контроллеру WebApi с только HttpRequestMessage в сигнатуре и читать и десериализовать класс там. Отсутствующий кусок, чтобы иметь возможность использовать только Post ([FromBody] MyClass myClass), должен был установить config.Formatters.JsonFormatter.UseDataContractJsonDeseriali & zwnj; zer = true в моем WebApiConfig.cs. Спасибо!
добавлено автор neonScarecrow, источник
Я сделал все это, и это не работает для меня ... что еще может быть проблемой?
добавлено автор jtate, источник
Потрясающие! Использовался UseXmlSerializer ! Я помещал его в файл Global.asax.cs , как часть глобальной конфигурации, но похоже, что он действительно должен быть в App_Start \ WebApiConfig.cs .
добавлено автор Wowca, источник

Хотя ответ уже получен, я нашел пару других деталей, которые стоит рассмотреть.

Самый простой пример XML-сообщения создается автоматически как часть нового проекта WebAPI визуальной студией, но в этом примере используется строка в качестве входного параметра.

Упрощенный пример контроллера WebAPI, созданного Visual Studio

using System.Web.Http;
namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
       //POST api/values
        public void Post([FromBody]string value)
        {
        }
    }
}

Это не очень полезно, потому что оно не затрагивает вопрос. Большинство веб-служб POST имеют довольно сложные типы в качестве параметров и, вероятно, сложный тип в качестве ответа. Я добавлю пример выше, чтобы включить сложный запрос и сложный ответ ...

Упрощенный образец, но с добавлением сложных типов

using System.Web.Http;
namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
       //POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    public class MyRequest
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class MyResponse
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

На этом этапе я могу вызвать скрипача.

Детали запроса Fiddler

Запросить заголовки:

User-Agent: Fiddler
Host: localhost:54842
Content-Length: 63

Тело запроса:


   99
   MyName

... и при размещении точки останова в контроллере я обнаружил, что объект запроса имеет значение null. Это связано с несколькими факторами ...

  • WebAPI по умолчанию использует DataContractSerializer
  • В запросе Fiddler не указан тип содержимого или кодировка
  • Тело запроса не включает объявление xml
  • Тело запроса не включает определения пространства имен.

Не внося никаких изменений в контроллер веб-службы, я могу изменить запрос на скрипач, чтобы он работал. Обратите внимание на определения пространства имен в блоке запроса xml POST. Кроме того, убедитесь, что объявление xml включено с правильными настройками UTF, которые соответствуют заголовку запроса.

Фиксированный модуль запроса Fiddler для работы со сложными типами данных

Запросить заголовки:

User-Agent: Fiddler
Host: localhost:54842
Content-Length: 276
Content-Type: application/xml; charset=utf-16

Тело запроса:

<?xml version="1.0" encoding="utf-16"?>
   
      99
      MyName
   

Обратите внимание, как пространство имен в запросе ссылается на одно и то же пространство имен в моем классе контроллера C# (вид). Поскольку мы не изменили этот проект на использование сериализатора, отличного от DataContractSerializer, и потому, что мы не украсили нашу модель (класс MyRequest или MyResponse) конкретными пространствами имен, она предполагает такое же пространство имен, что и сам WebAPI-контроллер. Это не очень понятно и очень запутанно. Лучшим подходом было бы определение конкретного пространства имен.

Чтобы определить конкретное пространство имен, мы модифицируем модель контроллера. Необходимо добавить ссылку на System.Runtime.Serialization, чтобы сделать эту работу.

Добавить пространства имен в модель

using System.Runtime.Serialization;
using System.Web.Http;
namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
       //POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    [DataContract(Namespace = "MyCustomNamespace")]
    public class MyRequest
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }

    [DataContract(Namespace = "MyCustomNamespace")]
    public class MyResponse
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }
}

Теперь обновите запрос Fiddler, чтобы использовать это пространство имен ...

Запрос Fiddler с пользовательским пространством имен

<?xml version="1.0" encoding="utf-16"?>
   
      99
      MyName
   

Мы можем принять эту идею еще дальше. Если пустая строка указана как пространство имен в модели, пространство имен в запросе скрипта не требуется.

Контроллер с пустым пространством имен строк

using System.Runtime.Serialization;
using System.Web.Http;

namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
       //POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    [DataContract(Namespace = "")]
    public class MyRequest
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }

    [DataContract(Namespace = "")]
    public class MyResponse
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }
}

Запрос Fiddler без объявленного пространства имен

<?xml version="1.0" encoding="utf-16"?>
   
      99
      MyName
   

Другие Gotchas

Остерегайтесь, DataContractSerializer ожидает, что элементы в полезной нагрузке xml будут упорядочены по алфавиту по умолчанию. Если полезная нагрузка xml вышла из строя, вы можете обнаружить, что некоторые элементы являются нулевыми (или если тип данных является целым числом, по умолчанию он равен нулю, или если это значение bool по умолчанию равно false). Например, если ни один заказ не указан, и отправляется следующий xml ...

Тело xml с неправильным упорядочением элементов

<?xml version="1.0" encoding="utf-16"?>

   MyName
   99
  

... значение для Age будет по умолчанию равно нулю. Если отправлен почти одинаковый xml ...

XML-тело с правильным упорядочением элементов

<?xml version="1.0" encoding="utf-16"?>

   99
   MyName
  

то контроллер WebAPI будет правильно сериализовать и заполнить параметр Age. Если вы хотите изменить порядок по умолчанию, чтобы xml можно было отправить в определенном порядке, добавьте элемент «Заказ» в атрибут DataMember.

Пример указания порядка свойств

using System.Runtime.Serialization;
using System.Web.Http;

namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
       //POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    [DataContract(Namespace = "")]
    public class MyRequest
    {
        [DataMember(Order = 1)]
        public string Name { get; set; }

        [DataMember(Order = 2)]
        public int Age { get; set; }
    }

    [DataContract(Namespace = "")]
    public class MyResponse
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }
}

В этом примере тело xml должно указывать элемент Name перед тем, как элемент Age будет правильно заполняться.

<�Сильный> Заключение

Мы видим, что неправильный или неполный орган запроса POST (с точки зрения DataContractSerializer) не выдает ошибки, а просто вызывает проблему времени выполнения. Если вы используете DataContractSerializer, нам нужно выполнить сериализатор (особенно вокруг пространств имен). Я нашел с помощью инструмента тестирования хороший подход - где я передаю XML-строку функции, которая использует DataContractSerializer для десериализации XML. Он выдает ошибки, когда десериализация не может произойти. Вот код для тестирования XML-строки с помощью DataContractSerializer (опять же, помните, если вы это реализуете, вам нужно добавить ссылку на System.Runtime.Serialization).

Пример кода тестирования для оценки десериализации DataContractSerializer

public MyRequest Deserialize(string inboundXML)
{
    var ms = new MemoryStream(Encoding.Unicode.GetBytes(inboundXML));
    var serializer = new DataContractSerializer(typeof(MyRequest));
    var request = new MyRequest();
    request = (MyRequest)serializer.ReadObject(ms);

    return request;
}

<�Сильный> Опции

Как отмечают другие, DataContractSerializer является стандартом для проектов WebAPI с использованием XML, но существуют и другие XML-сериализаторы. Вы можете удалить DataContractSerializer и вместо этого использовать XmlSerializer. XmlSerializer гораздо более прощающий в некорректном пространстве имен.

Другой вариант - ограничить запросы на использование JSON вместо XML. Я не проводил анализ, чтобы определить, используется ли DataContractSerializer во время десериализации JSON, и если для взаимодействия JSON требуются атрибуты DataContract для украшения моделей.

50
добавлено
Огромное спасибо !!!
добавлено автор jaxxbo, источник
@Setinel - спасибо за комплимент! Не знаю о правилах SO. Вместо того, чтобы получать пожертвованные деньги, мне нравится идея «Оплатить».
добавлено автор barrypicker, источник
Вам следует заплатить за этот ответ. Разве это против правил SO, чтобы поместить BTC пожертвовать информацию в ответ?
добавлено автор Sentinel, источник
Ответ Killer. Поразительно, насколько сложно отправлять/получать XML, когда это должно быть точкой службы REST!
добавлено автор Rocklan, источник
Два момента - 1. Мне нужно было добавить ссылку на System.Net.Http.Formatting, чтобы установить UseXmlSerializer в true. 2. Мне нужно было добавить «Accept: application/xml» в заголовок запроса, чтобы иметь возможность вернуть xml из запроса (похоже, он по умолчанию используется JSON).
добавлено автор Rocklan, источник

Я пытался решить это в течение двух дней. В конце концов я обнаружил, что внешний тег должен быть именем типа, а не именем переменной. Эффективно, используя метод POST как

public string Post([FromBody]TestModel model)
{
    return model.Output;
}

Я обеспечивал тело

Sito

вместо

Sito
2
добавлено

Как только вы убедитесь, что вы настраиваете заголовок Content-Type в application/xml и устанавливаете config.Formatters.XmlFormatter.UseXmlSerializer = true; в методе Register WebApiConfig.cs важно, чтобы вам не нужно было управлять версиями или кодировкой в ​​верхней части вашего XML-документа.

Этот последний кусок заставлял меня застрять, надеюсь, что это поможет кому-то там и экономит ваше время.

1
добавлено