WSDL генерит неправильный SoapAction
От: mDmitriy Россия  
Дата: 22.09.17 08:41
Оценка:
Всем привет!

Есть WSDL от стороннего сервиса
Содержит в себе такой раздел
<wsdl:operation name="SecurityAuthenticate">
<soap:operation soapAction="http://webservices.alienDomain.com/1ASIWVISVIP/VLSSLQ_06_1_1A" />
Этот Action является правильным

Но при этом в сгенеренном классе получается
[OperationContract(Action = "http://xml.alienDomain.com/AlienDomainWebServicesPT/SecurityAuthenticateRequest",

Свойство атрибута попадает в заголовок SoapAction и запрос не работает
Почему так происходит и как решить эту ситуацию?
Спасибо...

Подменять Action динамически не хочется, методов там много
Руками править сгенеренные классы тоже
Re: WSDL генерит неправильный SoapAction
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 22.09.17 09:11
Оценка: 3 (1)
Здравствуйте, mDmitriy, Вы писали:

D>Всем привет!


D>Почему так происходит и как решить эту ситуацию?

Проверить я сейчас, к сожалению, не могу (просто нет времени), а ранее такие затейливые WSDL мне не попадались, но, надо полагать, стандартный WSDL парсер не рассчитывает на подобную конструкцию (хотя это странно, другого способа явно указать Action вроде не предусмотрено) и просто игнорирует soap:operation, а имя Action формирует своим стандартным способом — беря немспейс сервиса, добавляя имя операции + Request в конец (хотя с последней частью вопрос — судя по этому https://stackoverflow.com/questions/6470463/wcf-operationcontract-whats-the-point-of-action-and-replyaction — не должно в конец добавляться ничего).

Подозреваю, что способы правки:
— на ходу правит WSDL
— слегка подправить генератор (там есть несколько вариантов).

Т.к. с ходу не могу посоветовать гарантированно работающий вариант, то попрошу у вас:
— можете прислать (выложить) минимальный воспроизводимый пример WSDL (можно и вcю WSDL, если там нет ничего закрытого, только тогда сразу подскажите на что смотреть — чтобы долго не искать)?
— как вы генерируете клиентскую часть (в студии, через svcutil, ...) и можете ли например использовать кастомизированный генератор?
Re[2]: WSDL генерит неправильный SoapAction
От: mDmitriy Россия  
Дата: 22.09.17 10:53
Оценка:
Здравствуйте, Михаил Романов, Вы писали:
МР>- на ходу правит WSDL
это не подходит
МР>- слегка подправить генератор (там есть несколько вариантов).
вот это интересно, а как?
я бы с удовольствием убрал бы из сгенерированных классов кучу всякой фигни
МР>- можете прислать (выложить) минимальный воспроизводимый пример WSDL (можно и вcю WSDL, если там нет ничего закрытого, только тогда сразу подскажите на что смотреть — чтобы долго не искать)?
могу прислать всю, но только вам лично, если скажете емейл
это конфиденциальная информация, увы
МР>- как вы генерируете клиентскую часть (в студии, через svcutil, ...) и можете ли например использовать кастомизированный генератор?
генерирую через студию
как подключить к ней свой генератор или настроить имеющийся — не знаю
в принципе свой генератор для ручной генерации — тоже решение
не так часто ее генерить
вроде даже можно такой собрать из микрософтовских классов, но тоже не знаю как
Re[3]: WSDL генерит неправильный SoapAction
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 22.09.17 12:20
Оценка: 190 (8)
Здравствуйте, mDmitriy, Вы писали:

МР>>- на ходу правит WSDL

D>это не подходит
"На ходу" я имел ввиду "перед тем как скормить генератору"

МР>>- слегка подправить генератор (там есть несколько вариантов).

D>вот это интересно, а как?
Ну выглядит примерно так...
1. За вычитывание WSDL отвечает класс WsdlImporter. Он по сути берет на вход MetadataSet (можно получить непосредственно от сервиса или считать из локального файла), а на выходе выдает ContractDescription.

2. После того как вы получили описание контрактов, вы берете класс ServiceContractGenerator, скармливаете ему на вход эти контракты (и прочие настройки), а он вам возвращает CodeDOM для сгенерированного класса, которые можно будет через CodeDomProvider сохранить на нужном вам языке.

Через этот класс работают и svcutil и Service Reference в VS.
Соответственно, первый метод — написать свою утилиту, которая будет проходить путь до готового CodeDOM а затем его модифицировать.

Второй метод, это написать плагины для WsdlImporter и ServiceContractGenerator, это соответственно
IWsdlImportExtension — он отрабатывает в момент преобразования WSDL/XSD в описание контракта
IOperationContractGenerationExtension и IServiceContractGenerationExtension — они, понятное дело, будут вызываться при генерации соответствующих фрагментов CodeDOM.

Плагины хороши тем, что их теоретически можно подключить к тем же VS и svcutil, просто указав где-то в настройках, но зато им доступен не весь CodeDOM (вроде бы)

Есть небольшая вводная статья (помимо MSDN) WCF Extensibility – WSDL Import (and Code Generation) Extensions

D>я бы с удовольствием убрал бы из сгенерированных классов кучу всякой фигни

Я тут не уверен — возня с CodeDOM то еще удовольствие... Но небольшие правки вполне можно попытаться сделать.

D>могу прислать всю, но только вам лично, если скажете емейл

D>это конфиденциальная информация, увы
Получил, попробую посмотреть.

D>вроде даже можно такой собрать из микрософтовских классов, но тоже не знаю как

Я выше описал примерно процесс, дальше по ссылка вроде должно быть более-менее понятно, но на всякий случай приложу свой пример

  Генератор, который создает прокси-класс, который "давит" все исключения
        public void GenerateProxy()
        {
            var metadataAddress = "http://localhost:54107/CalculatorService.svc?wsdl";

            // 01. Read metadata
            MetadataExchangeClient metaClient = new MetadataExchangeClient(
                new Uri(metadataAddress), MetadataExchangeClientMode.HttpGet);
            MetadataSet metadataSet = metaClient.GetMetadata();

            // 02. Import contracts
            WsdlImporter importer = new WsdlImporter(metadataSet);

            // 03. Put contracts to generator
            ServiceContractGenerator generator = new ServiceContractGenerator();
            foreach (ContractDescription cd in importer.ImportAllContracts())
            {
                generator.GenerateServiceContractType(cd);
            }

            // 04/ Enumerates all namesapces 
            foreach (CodeNamespace nm in generator.TargetCompileUnit.Namespaces)
            {
                // Select types witch names start from "System.ServiceModel.ClientBase" (all generated classses)
                // It's small hack, becouse real names start from "System.ServiceModel.ClientBase`1"
                var types =
                     nm.Types.OfType<CodeTypeDeclaration>().Where(
                        type => type.BaseTypes.OfType<CodeTypeReference>().Any(
                            t => t.BaseType.StartsWith("System.ServiceModel.ClientBase"))).Select(t => t);

                // Enumerates all clients
                foreach (CodeTypeDeclaration type in types)
                {
                    // Select regular (not constructor) methods
                    foreach (CodeMemberMethod method in
                        type.Members.OfType<CodeMemberMethod>().Where(t => t.GetType() == typeof(CodeMemberMethod)))
                    {
                        // Create new method body
                        CodeStatementCollection code = new CodeStatementCollection();

                        // Create catch for try/catch (catch CommunicationException)
                        CodeCatchClause ctch = new CodeCatchClause(
                            "e",
                            new CodeTypeReference(typeof(CommunicationException)));

                        // Create full code: 
                        // try 
                        // { old method body } 
                        // catch(CommunicationException) {}
                        code.Add(new CodeTryCatchFinallyStatement(
                            method.Statements.OfType<CodeStatement>().ToArray(),
                            new CodeCatchClause[] { ctch }));

                        // Clear old method body and insert new
                        method.Statements.Clear();
                        method.Statements.AddRange(code);
                    }
                }
            }

            // Create С# code generator
            CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("C#");
            // Create special TextWriter (with text indentation)
            IndentedTextWriter textWriter = new IndentedTextWriter(new System.IO.StreamWriter("proxy.cs"));

            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BlankLinesBetweenMembers = true;

            // Write the code
            codeProvider.GenerateCodeFromCompileUnit(generator.TargetCompileUnit, textWriter, options);
            textWriter.Close();
        }
Re[4]: WSDL генерит неправильный SoapAction
От: mDmitriy Россия  
Дата: 22.09.17 12:49
Оценка:
Здравствуйте, Михаил Романов, Вы писали:
МР>Я выше описал примерно процесс, дальше по ссылка вроде должно быть более-менее понятно, но на всякий случай приложу свой пример
Спасибо большое, буду смотреть
Re: WSDL генерит неправильный SoapAction
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 27.09.17 16:07
Оценка: 9 (2) +1
Здравствуйте, mDmitriy, Вы писали:

D>Всем привет!

Дмитрий, еще раз добрый день.

Я просмотрел присланные вами файлы и нашел причину проблемы, но пока не знаю как лучше решить — может коллеги что-то подскажут.
В общем суть в том, что в присланной wsdl некоторые операции имеют одинаковые имена.

Т.е. если упрощенно,
  ваша wsdl имеет вот такой вид
<?xml version="1.0" encoding="utf-8" ?>
<wsdl:definitions name="Service1" 
                  targetNamespace="http://tempuri.org/" 
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
                  xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
                  xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" 
                  xmlns:tns="http://tempuri.org/" 
                  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" 
                  xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" 
                  xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" 
                  xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" 
                  xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" 
                  xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" 
                  xmlns:wsa10="http://www.w3.org/2005/08/addressing" 
                  xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata">
  <wsdl:portType name="IService1">
    <wsdl:operation name="Operation1">
      <wsdl:input message="tns:IService1_Operation1_InputMessage"/>
      <wsdl:output message="tns:IService1_Operation1_OutputMessage"/>
    </wsdl:operation>
    <wsdl:operation name="Operation1">
      <wsdl:input message="tns:IService1_Operation1_InputMessage_2"/>
      <wsdl:output message="tns:IService1_Operation1_OutputMessage_2"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="Binding_IService1" type="tns:IService1">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="Operation1">
      <soap:operation soapAction="Action_1" />
      <wsdl:input>
        <soap:body use="literal" />
        <soap:header part="h1" use="literal" message="tns:H" />
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal" />
        <soap:header part="h1" use="literal" message="tns:H" />
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="Operation1">
      <soap:operation soapAction="Action_2" />
      <wsdl:input>
        <soap:body use="literal" />
        <soap:header part="h1" use="literal" message="tns:H" />
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal" />
        <soap:header part="h1" use="literal" message="tns:H" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
</wsdl:definitions>


Т.е. в разделе wsdl:portType описаны 2 операции с именем Operation1, но с разными сообщениями на вход и выход,
А в разделе wsdl:binding описаны 2 биндинга операций, с тем же именем Operation1, но с разыми Action.

И вот тут начинается самое печальное, что:

Причем, коллеги из Microsoft, когда делали парсер WSDL, не предусмотрели нормальной диагностики. Вместо того, чтобы честно сказать, что дублируются имена операций, WsdlImporter (а падает в нем) просто ловит исключение, что невозможно добавить в словарь значение с неуникальным ключом (как вы понимаете — это имя операции) и (!) записывает в логи импорта ошибку

Error: Cannot import wsdl:binding
Detail: An item with the same key has already been added.
XPath to Error Source: //wsdl:definitions[@targetNamespace='some namespace']/wsdl:binding[@name='some name']


Но дальше всё еще хуже, т.к. и генератор в VS и SvcUtil продолжают генерацию прокси-классов, но при этом (!!!!!) не учитывают информацию из биндинга.
Поэтому клиент генерируется, все методы присутствуют (кстати не проверял — на сколько верные типы данных), но Actions для них генерируются из xmlNamespace + OperationName + Some_Else (я полный алгоритм не знаю).

Поэтому вы и получали такой странный результат.


Собственно что делать... Я с ходу вижу 2 варианта:

Собственно, сами имена операций насколько я помню (это надо проверить), при вызове сервиса нигде не используется.
Для вызова и приема ответов используются Actions, а тело запроса описывается сообщением (а его мы не планируем менять).

Но, конкретно в вашем случае есть проблема — менять имена надо и в wsdl:portType, и в wsdl:binding, а в тех wsdl, что прислали вам никакой связи, кроме имени нет.
Я вообще не понимаю, как здесь должна идти генерация, ведь нужно свести в одно: Action (из биндинга) и типы сообщений (из порт типа) — а здесь это однозначно сделать невозможно!

Теоретически, механизм такой связи предусмотрен.
Если посмотреть, как генерирует метаданные сам WCF, там описания portType выглядит примерно так:
  <wsdl:portType name="IService1">
    <wsdl:operation name="Operation1">
      <wsdl:input message="tns:IService1_Operation1_InputMessage" wsaw:Action="Action_1"/>
      <wsdl:output message="tns:IService1_Operation1_OutputMessage" wsaw:Action="Action_1Response"/>
    </wsdl:operation>
    <wsdl:operation name="Operation1">
      <wsdl:input message="tns:IService1_Operation1_InputMessage_2" wsaw:Action="Action_2"/>
      <wsdl:output message="tns:IService1_Operation1_OutputMessage_2" wsaw:Action="Action_2Response"/>
    </wsdl:operation>
  </wsdl:portType>


Т.е. теперь можно легко сопоставить операцию из описания порта и операцию из биндинга.
Но увы, у вас таких атрибутов нет...

P.S. Если решите идти по пути переименования (надо проверить, что работает — просто выкинуть всё и сгенерировать 1 операцию с отличающимся именем, а затем убедиться, что это не повлияло не на что) и придумаете как автоматически преобразовывать имена, то идея состоит в том, чтобы написать IWsdlImportExtension, в котором реализовать BeforeImport(), в котором уже пробегаться по ServiceDescriptionCollection (это коллекция распарсенных WSDL документов) и менять там имена операций.
Re[2]: WSDL генерит неправильный SoapAction
От: mDmitriy Россия  
Дата: 27.09.17 16:22
Оценка:
Здравствуйте, Михаил Романов, Вы писали:
МР>Я просмотрел присланные вами файлы и нашел причину проблемы, но пока не знаю как лучше решить — может коллеги что-то подскажут.
МР>В общем суть в том, что в присланной wsdl некоторые операции имеют одинаковые имена.
огромное спасибо
теперь хоть понятно, в какую сторону копать
а как копать — буду думать
спасибо
Re: Вдогонку
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 28.09.17 12:02
Оценка: 6 (2)
Здравствуйте, mDmitriy, Вы писали:

D>Подменять Action динамически не хочется, методов там много

Если всё же решите делать замену автоматически при импорте, то можете взять за основу вот такой пример расширения для импорта

  Простой "дедубликатор (добавляет ко всем дублирующимся операциям номер 0,1,2,... в конец)"
using System.Collections.Generic;
using System.Linq;
using System.Web.Services.Description;
using System.Xml;
using System.Xml.Schema;
using smd = System.ServiceModel.Description;

namespace MetadataExtensions
{
    public class DuplicateOperationsRenamer : smd.IWsdlImportExtension
    {    
        public void BeforeImport(ServiceDescriptionCollection wsdlDocuments, XmlSchemaSet xmlSchemas, ICollection<XmlElement> policy)
        {
            foreach (ServiceDescription wsdl in wsdlDocuments)
            {
                foreach (Binding binding in wsdl.Bindings)
                {
                    PortType portType = wsdl.PortTypes[binding.Type.Name];

                    var operations = binding.Operations
                        .OfType<OperationBinding>()
                        .Select(op => op.Name)
                        .ToList();

                    var uniqueOperations = operations
                        .Distinct()
                        .ToList();

                    var duplicatedOpertaions = uniqueOperations
                        .Where(uo => operations.Count(o => o == uo) > 1)
                        .ToList();

                    foreach (var opertaionName in duplicatedOpertaions)
                    {
                        var portOperations = portType.Operations
                            .OfType<Operation>()
                            .Where(o => o.Name == opertaionName)
                            .ToList();

                        int i = 0;
                        portOperations.ForEach(o =>
                            {
                                o.Name = o.Name + i.ToString();
                                i++;
                            });

                        var bindingOpertions = binding.Operations
                            .OfType<OperationBinding>()
                            .Where(o => o.Name == opertaionName)
                            .ToList();

                        i = 0;
                        bindingOpertions.ForEach(o =>
                        {
                            o.Name = o.Name + i.ToString();
                            i++;
                        });
                    }
                }
            }
        }

        public void ImportContract(smd.WsdlImporter importer, smd.WsdlContractConversionContext context)
        {
        }

        public void ImportEndpoint(smd.WsdlImporter importer, smd.WsdlEndpointConversionContext context)
        {
        }
    }
}


Как использовать: создать App.config следующего содержания
<configuration>
  <system.serviceModel>
    <client>
      <metadata>
        <wsdlImporters>
          <extension type="MetadataExtensions.DuplicateOperationsRenamer, MetadataExtensions"/>
        </wsdlImporters>
      </metadata>
    </client>
  </system.serviceModel>
</configuration>


Далее, если вы работаете с svcutil, то

Ну а если вы генерируете в студии, то
Re[2]: Вдогонку
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 28.09.17 12:12
Оценка: 6 (2)
И еще один пример приведу...

Как оказалось, с загрузкой метаданных из файловой системы всё не так просто и очевидно, как при скачивании непосредственно с сервера...
Поэтому, если вы будете делать вариант с собственной утилитой, то вам может пригодится следующий фрагмент:
var metadataSet = new MetadataSet();

// Read wsdl's
foreach (var file in Directory.EnumerateFiles(dir, " *.wsdl"))
{
    var serviceDescription = System.Web.Services.Description.ServiceDescription.Read(file);
    var wsdlSection = MetadataSection.CreateFromServiceDescription(serviceDescription);

    metadataSet.MetadataSections.Add(wsdlSection);
}

// Read schemas
foreach (var file in Directory.EnumerateFiles(dir, "*.xsd"))
{
    var xsdSection = MetadataSection.CreateFromSchema(XmlSchema.Read(new FileStream(file, FileMode.Open), null));
    metadataSet.MetadataSections.Add(xsdSection);
}

// Create importer
var wsdlImporter = new WsdlImporter(metadataSet);

// Add extension (optional)
wsdlImporter.WsdlImportExtensions.Add(new ImportTraicer());

// Import endpoints (it's important! In other variants (e.g. ImportAllContracts()) bindings will be skipped)
var contracts = wsdlImporter.ImportAllEndpoints();

// Check importing errors
foreach (var error in wsdlImporter.Errors)
{
    Console.WriteLine(error.Message);
}
Re[3]: Спасибо большое!!!
От: mDmitriy Россия  
Дата: 28.09.17 12:35
Оценка:
Здравствуйте, Михаил Романов, Вы писали:
МР>И еще один пример приведу...
Спасибо
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.