Перенос веб-клиента в WCF; Клиент WCF сериализует имя параметра метода
Я борюсь с миграцией с веб-сервиса / архитектуры веб-клиента на архитектуру WCF. Объект очень сложный, с множеством вложенных xsd и разных пространств имен. Прокси-классы создаются путем добавления веб-ссылки на исходный wsdl с более чем 30 веб-методами и использования xsd.exe для создания отсутствующих объектов SOAPFault. Мой экспериментальный сервис WCF состоит только из 1 веб-метода, который соответствует точному синтаксису одного из исходных методов: 1 объект в качестве параметра, возвращающий 1 другой объект в качестве значения результата. Я создал интерфейс WCF, используя эти прокси-классы, используя атрибуты: XMLSerializerFormat
а также ServiceContract
на интерфейсе, OperationContract
по одному методу из оригинальной wsdl с указанием Action
, ReplyAction
, все с правильными пространствами имен. Я создаю входящие клиентские сообщения, используя SoapUI; Я сгенерировал проект из исходных файлов WSDL (в результате чего в проекте SoapUI было более 30 методов) и создал один новый запрос для одного реализованного WebMethod, изменил URL-адрес моего веб-сервиса wcf и отправил сообщение. Из-за указанного (Ответить-) Действие в OperationContractAttribute
сообщение фактически получено и правильно десериализовано в объект.
Чтобы продвинуться так далеко (40 часов поиска в Google), большое разочарование привело меня к использованию настраиваемой конечной точки, в которой удаляются "обернутые теги" WCF, исправляются пространства имен для вложенных типов и сглаживается сгенерированный wsdl get (для лучшего совместимость с другими инструментами, чем MS VisualStudio).
Код интерфейса это:
[XmlSerializerFormat(Use = OperationFormatUse.Literal, Style = OperationFormatStyle.Document, SupportFaults = true)]
[ServiceContract(Namespace = Constants.NamespaceStufZKN)]
public interface IOntvangAsynchroon
{
[OperationContract(Action = Constants.NamespaceStufZKN + "/zakLk01", ReplyAction = Constants.NamespaceStufZKN + "/zakLk01", Name = "zakLk01")]
[FaultContract(typeof(Fo03Bericht), Namespace = Constants.NamespaceStuf)]
Bv03Bericht zakLk01([XmlElement("zakLk01", Namespace = Constants.NamespaceStufZKN)] ZAKLk01 zakLk011);
Когда я использую Webclient в коде для отправки сообщения, все работает. Моя проблема, когда я использую клиента WCF. я использую ChannelFactory<IOntvangAsynchroon>
Отправить сообщение. Но сгенерированный xml выглядит иначе: он содержит имя параметра метода! Мне потребовалось много времени, чтобы понять это, но вот что происходит:
Правильный xml (раздетый мыльный конверт):
<soap:Body>
<zakLk01 xmlns="http://www.egem.nl/StUF/sector/zkn/0310">
<stuurgegevens>
<berichtcode xmlns="http://www.egem.nl/StUF/StUF0301">Bv01</berichtcode>
<zender xmlns="http://www.egem.nl/StUF/StUF0301">
<applicatie>ONBEKEND</applicatie>
</zender>
</stuurgegevens>
<parameters>
</parameters>
</zakLk01>
</soap:Body>
Плохой xml:
<soap:Body>
<zakLk01 xmlns="http://www.egem.nl/StUF/sector/zkn/0310">
<zakLk011>
<stuurgegevens>
<berichtcode xmlns="http://www.egem.nl/StUF/StUF0301">Bv01</berichtcode>
<zender xmlns="http://www.egem.nl/StUF/StUF0301">
<applicatie>ONBEKEND</applicatie>
</zender>
</stuurgegevens>
<parameters>
</parameters>
</zakLk011>
</zakLk01>
</soap:Body>
Обратите внимание на zakLk011
элемент? Это имя параметра метода в моем интерфейсе! Так что сейчас zakLk011
, но это когда имя моего параметра было zakLk01
xml, казалось, содержал магический дубликат тега выше, но без пространства имен. Конечно, вы можете представить, как я схожу с ума от происходящего, прежде чем узнаю, что это было имя параметра!
Теперь я фактически создал Службу WCF, в которой я больше не могу отправлять сообщения, используя Клиент WCF. Для ясности: метод вызывается с помощью клиента WCF на моем веб-сервисе, но объект параметра пуст. Поскольку я использую пользовательскую конечную точку для регистрации входящего XML, я вижу, что сообщение получено нормально, но только с неправильным синтаксисом!
Код клиента WCF:
ZAKLk01 stufbericht = MessageFactory.CreateZAKLk01();
ChannelFactory<IOntvangAsynchroon> factory = new ChannelFactory<IOntvangAsynchroon>(new BasicHttpBinding(), new EndpointAddress("http://localhost:8193/Roxit/Link/zkn0310"));
factory.Endpoint.Behaviors.Add(new LinkEndpointBehavior());
IOntvangAsynchroon client = factory.CreateChannel();
client.zakLk01(stufbericht);
Я не использую сгенерированный клиент, я просто ссылаюсь на веб-сервис (разделяемые библиотеки), как я привык.
Редактировать: при генерации ссылки на сервис он генерирует дублирующиеся классы (не знаю почему..). Когда эти дубликаты удаляются, клиент отправляет сообщения с правильным XML. Но моя архитектура требует разделяемых библиотек, так что это мне не поможет.
Кто-нибудь может мне помочь, пожалуйста! Я не могу ничего гуглить по этому вопросу...
2 ответа
Предложение: если вы только начинаете работать с WCF, начните с того, что делаете "в духе WCF". Если вы знаете, как это сделать правильно, вы можете начать что-то менять. Прямо сейчас вы не знаете, какая часть вашей проблемы связана с WCF, а какая - просто из-за отсутствия у вас опыта работы с WCF.
Я предлагаю вам начать с нуля, не используя [XmlSerializerFormat]
, Просто создайте ServiceContract
с одним OperationContract
, Включите хотя бы один FaultContract
так что вы можете увидеть, как это работает.
Создайте клиент WCF с помощью "Добавить ссылку на службу" и убедитесь, что он работает.
Затем создайте проект SOAPUI, используя WSDL из службы WCF (не исходный WSDL). Убедитесь, что это работает.
Тогда вы можете начать менять вещи. Пытаться [XmlSerializerFormat]
, Попробуйте добавить различные [Xml*]
атрибутов. Медленно начинайте менять вещи, пока не увидите, что ломается.
Хорошо, я понял это сам. Я уже создал клиент WCF (Service Reference), который работал, и, внимательно изучив сгенерированный код, я выяснил, что происходит. Это связано с тем, что WCF оборачивает все классы, используемые в DECLARATION методов Webservice (поэтому классы, используемые в свойствах класса method-упомянутых классов, не переносятся). В моем случае тело класса ZAKLk01 обернуто тегами XMLElement, используя имя параметра в качестве XMLElement-name. Чтобы избавиться от этого поведения, я теперь использую классы-оболочки для моих сгенерированных прокси-классов ZAKLk01 и Bv03Bericht, так же как сгенерированные классы делают с моими прокси-классами Service References в моем новом клиенте WCF. Эти классы-оболочки оформлены с помощью MessageContractAttributes.
Чтобы привести пример одного из этих классов-оболочек:
[MessageContract(IsWrapped = false)]
public partial class zakLk01Request
{
[MessageBodyMember(Namespace = Constants.NamespaceStufZKN, Order = 0)]
public ZAKLk01 zakLk01;
public zakLk01Request()
{
}
public zakLk01Request(ZAKLk01 zakLk01)
{
this.zakLk01 = zakLk01;
}
}
Мой метод интерфейса теперь выглядит так:
[OperationContract(Action = Constants.NamespaceStufZKN + "/zakLk01", ReplyAction = Constants.NamespaceStufZKN + "/Bv03Bericht")]
[FaultContract(typeof(Fo03Bericht), Namespace = Constants.NamespaceStuf)]
zakLk01Response zakLk01(zakLk01Request zakLk01);
Гораздо чище без тегов XMLElement, функция которых (генерирует правильный xml) теперь заменена классами-обертками.
Причина, по которой я мог получить неупакованный xml, заключалась в том, что мой пользовательский messageinspector содержал некоторый код, предназначенный для приема неупакованных xml-сообщений без необходимости добавлять теги MessageContract ко всем существующим классам (или создавать множество классов-оболочек) (где-то гуглил), что он сделал просто отлично. Фрагмент кода:
MessageDescription.Body.WrapperName = null;
Но получение упакованных сообщений, которые были отправлены моим (первым) клиентом WCF, который все еще упаковывал классы, не сработало, конечно...
Я до сих пор не понимаю, как работают эти атрибуты Action: если я не предоставлю их, мой сгенерированный wsdl не содержит никакого метода. Ну, пока это не важно, так как я наконец-то смогу двигаться дальше, и в другой раз буду повозиться с атрибутами Action.