Здравствуйте, 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, ...) и можете ли например использовать кастомизированный генератор?
Здравствуйте, Михаил Романов, Вы писали: МР>- на ходу правит WSDL
это не подходит МР>- слегка подправить генератор (там есть несколько вариантов).
вот это интересно, а как?
я бы с удовольствием убрал бы из сгенерированных классов кучу всякой фигни МР>- можете прислать (выложить) минимальный воспроизводимый пример WSDL (можно и вcю WSDL, если там нет ничего закрытого, только тогда сразу подскажите на что смотреть — чтобы долго не искать)?
могу прислать всю, но только вам лично, если скажете емейл
это конфиденциальная информация, увы МР>- как вы генерируете клиентскую часть (в студии, через svcutil, ...) и можете ли например использовать кастомизированный генератор?
генерирую через студию
как подключить к ней свой генератор или настроить имеющийся — не знаю
в принципе свой генератор для ручной генерации — тоже решение
не так часто ее генерить
вроде даже можно такой собрать из микрософтовских классов, но тоже не знаю как
Здравствуйте, mDmitriy, Вы писали: МР>>- на ходу правит WSDL D>это не подходит
"На ходу" я имел ввиду "перед тем как скормить генератору" МР>>- слегка подправить генератор (там есть несколько вариантов). D>вот это интересно, а как?
Ну выглядит примерно так...
1. За вычитывание WSDL отвечает класс WsdlImporter. Он по сути берет на вход MetadataSet (можно получить непосредственно от сервиса или считать из локального файла), а на выходе выдает ContractDescription.
2. После того как вы получили описание контрактов, вы берете класс ServiceContractGenerator, скармливаете ему на вход эти контракты (и прочие настройки), а он вам возвращает CodeDOM для сгенерированного класса, которые можно будет через CodeDomProvider сохранить на нужном вам языке.
Через этот класс работают и svcutil и Service Reference в VS.
Соответственно, первый метод — написать свою утилиту, которая будет проходить путь до готового 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 clientsforeach (CodeTypeDeclaration type in types)
{
// Select regular (not constructor) methodsforeach (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 newmethod.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();
}
Здравствуйте, Михаил Романов, Вы писали: МР>Я выше описал примерно процесс, дальше по ссылка вроде должно быть более-менее понятно, но на всякий случай приложу свой пример
Спасибо большое, буду смотреть
Здравствуйте, mDmitriy, Вы писали: D>Всем привет!
Дмитрий, еще раз добрый день.
Я просмотрел присланные вами файлы и нашел причину проблемы, но пока не знаю как лучше решить — может коллеги что-то подскажут.
В общем суть в том, что в присланной wsdl некоторые операции имеют одинаковые имена.
Т.е. в разделе wsdl:portType описаны 2 операции с именем Operation1, но с разными сообщениями на вход и выход,
А в разделе wsdl:binding описаны 2 биндинга операций, с тем же именем Operation1, но с разыми Action.
И вот тут начинается самое печальное, что:
спецификация WSDL, не говорит, что имена операций должны быть уникальны. Наоборот, там говорится, что как минимум в биндинге имена могут повторятся для поддержки перегрузки — главное чтобы Actions были разные
WCF (а похоже еще и ASMX сервисы — т.к. для парсинга WSDL он использует код из System.Web.Services) не поддерживает перегрузку имен. Имена операций должны быть разными
Причем, коллеги из 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 выглядит примерно так:
Т.е. теперь можно легко сопоставить операцию из описания порта и операцию из биндинга.
Но увы, у вас таких атрибутов нет...
P.S. Если решите идти по пути переименования (надо проверить, что работает — просто выкинуть всё и сгенерировать 1 операцию с отличающимся именем, а затем убедиться, что это не повлияло не на что) и придумаете как автоматически преобразовывать имена, то идея состоит в том, чтобы написать IWsdlImportExtension, в котором реализовать BeforeImport(), в котором уже пробегаться по ServiceDescriptionCollection (это коллекция распарсенных WSDL документов) и менять там имена операций.
Здравствуйте, Михаил Романов, Вы писали: МР>Я просмотрел присланные вами файлы и нашел причину проблемы, но пока не знаю как лучше решить — может коллеги что-то подскажут. МР>В общем суть в том, что в присланной wsdl некоторые операции имеют одинаковые имена.
огромное спасибо
теперь хоть понятно, в какую сторону копать
а как копать — буду думать
спасибо
Здравствуйте, 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 следующего содержания
переименовать App.config в svcutil.exe.config (либо явно указывать у svcutil параметр /svcutilConfig:<configFile>)
собрать в одной папке svcutil.exe, svcutil.exe.config и сборку расширения (чтобы svcutil мог найти сборку). Альтернативный вариант — расширение поместить в GAC и заменить в конфиге ссылку на тип строгую сборку.
Ну а если вы генерируете в студии, то
в проекте, в котором будут добавляться Service Reference в app.config добавить фрагмент, который приведен выше
в тот же проект подключить сборку экстеншена как Reference (ну или опять же — сделать всё через GAC, тогда подключать Reference будет не надо, но придется указать строгое имя в конфиге)
Как оказалось, с загрузкой метаданных из файловой системы всё не так просто и очевидно, как при скачивании непосредственно с сервера...
Поэтому, если вы будете делать вариант с собственной утилитой, то вам может пригодится следующий фрагмент:
var metadataSet = new MetadataSet();
// Read wsdl'sforeach (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 schemasforeach (var file in Directory.EnumerateFiles(dir, "*.xsd"))
{
var xsdSection = MetadataSection.CreateFromSchema(XmlSchema.Read(new FileStream(file, FileMode.Open), null));
metadataSet.MetadataSections.Add(xsdSection);
}
// Create importervar 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 errorsforeach (var error in wsdlImporter.Errors)
{
Console.WriteLine(error.Message);
}