Опубликовано в статье MSDeveloper.ru
В мартовской статье мы рассмотрели создание приложения BizTalk Server, которое получает запрос на сложение двух чисел, делегирует сложение другому сервису и отвечает на изначальный запрос. В данном продолжении мы рассмотрим особенности взаимодействия с другими сервисами.
Перед чтением этой статьи рекомендую ознакомиться с предыдущими двумя.
Содержание
Публикация веб-сервиса
Прошлая статья закончилась развертыванием приложения в BTS, в продолжении нам нужно опубликовать веб-сервис для этого приложения. Как обычно, воспользуемся WCF Publishing Wizard.
Figure 1
Figure 2
Если посмотреть еще раз на нашу оркестровку, то можно выделить два места, где ожидаются данные извне – это InboundPort и SvcInboundPort, причем первый порт принимает сообщения типа EsbRequest, а второй – EsbResponse. Этот момент следует учесть, когда будете выбирать тип сообщений в окне ниже (тип выбирается из нашей сборки EsbSample.EsbCore.dll).
Figure 3
Figure 4
Далее указываем адрес, по которому сервис будет опубликован:
Figure 5
Настройка приложения
Переходим в BizTalk Server Administration Console, находим свое приложение и конфигурируем его:
Figure 6
Появится окно Configure Application, настраиваем как на картинке:
Figure 7
Таким образом, мы указали, что входящие логические порты (Inbound Logical Ports) должны принимать сообщения от нашего опубликованного веб-сервиса.
Дальше нам нужно указать куда должны отправлять сообщения исходящие логические порты (Outbound Logical Ports). SvcOutboundPort – это порт, который отправляет сообщения модулю CalcService (тому, который «умеет» считать). Нам нужно будет создать этот порт вручную, для этого раскроем список и выберем пункт <New send port…>
Figure 8
Как и полагается, откроется окно для создания нового порта:
Figure 9
Зададим для него следующие параметры:
Name – SvcSendPort
Transport Type – WCF-BasicHttp
Send pipeline – XMLTransmit
Сравните с картинкой:
Figure 10
Далее нажимаем кнопку Configure… и сразу закрываем окно, нажав по кнопке OK – это нужно для того, чтобы прописался дефолтный URI. Этот порт надо конфигурировать дальше, но пока достаточно. Нажимаем OK, закрывая тем самым окно Send Port Properties. Созданный нами порт теперь привязан к SvcOutboundPort:
Figure 11
Остался последний порт в нашей оркестровке, это OutboundPort. Чтобы лишний раз не заморачиваться, создадим порт, который будет отправлять ответы в файл. Естественно можно прикрутить отправку сообщения на сервис, который вызвал шину, но ничего нового в этом процессе не будет.
Для создания файлового порта также раскрываем список напротив OutboundPort, выбираем <New send port…> и задаем такие параметры:
Name – FilePort
Transport Type – FILE
Send pipeline – PassThruTransmit
Figure 12
Figure 13
У внимательных читателей должен был давно возникнуть вопрос – «Чем отличается PassThruTransmit от XMLTransmit?» (это я о Send pipeline). Send pipeline – это эдакий «конвейер», по которому проходит сообщение при отправке. Если используется PassThruTransmit, то сообщение просто проходит по конвейеру, а при использовании XMLTransmit сообщение парсится и анализируется для определения его типа. Например, если взять наш Receive Location и посмотреть его свойства, можно увидеть, что он использует XMLReceive для receive pipeline (receive pipeline, как не трудно догадаться, используется при приеме сообщения):
Figure 14
Если заменить XMLReceive на PassThruReceive, при вызове нашей шины сообщение-запрос не будет парситься, и у него не будет определен тип, а, поскольку все порты ожидают сообщения определенного типа, Messaging Engine не сможет найти получателя сообщения, и выкинется исключение (см. http://goo.gl/Ajp3C).
Отвлекся, возвращаемся к нашим сервисам. У нас есть (почти) сконфигурированное приложение BTS, теперь нам нужно разработать сервис, который и будет обладать знаниями, умениями и навыками для решения задачи «сложение двух чисел».
Разработка модуля
Мы пойдем по пути наименьшего сопротивления, поэтому наш модуль будет представлять собой обычный WCF-сервис. Добавим его в решение:
Figure 15
Проговорим еще раз, что мы от него хотим:
- Получить запрос на обработку данных;
- Произвести обработку данных (сложить два числа);
- Отправить ответ.
Будем подразумевать, что обработка данных происходит очень медленно (а что, есть ведь такие задачи на сутки), поэтому будем использовать схему с корреляцией сообщений. Контракт нашего сервиса приобретает следующий вид (ICalcService.svc):
using System; using System.ServiceModel; namespace CalcService { [ServiceContract] public interface ICalcService { [OperationContract] void Add(int a, int b, Guid guid); } }
После вычислений нам нужно отправить ответ, содержащий результаты вычислений. Для этого нам нужно добавить в проект ссылку на сервис шины:
Figure 16
В появившемся окне вписываем адрес веб-сервиса шины, нажимаем на кнопку Go и… получаем сообщение об ошибке:
Figure 17
Текст ошибки виден, эту ошибку мы встречали ранее в прошлой статье, поэтому знаем, как с ней бороться – идем в BizTalk Server Administration, находим наше приложение, выбираем узел Receive Locations и у единственного порта меняем статус c Disabled на Enabled:
Figure 18
После этого снова пытаемся добавить ссылку на сервис, на этот раз успешно:
Figure 19
Нажимаем OK и теперь можно спрограммировать поведение нашего сервиса, приведу код, думаю, всё будет понятно:
using System;
using System.Threading;
using System.Threading.Tasks;
using CalcService.BizTalkServiceRef;
namespace CalcService
{
public class CalcService : ICalcService
{
#region ICalcService Members
public void Add( int a, int b, Guid guid )
{
Task.Factory.StartNew( () => SendResponse( a, b, guid ) );
}
#endregion
private void SendResponse( int a, int b, Guid guid )
{
//slight delay simulates extensive calculation
new AutoResetEvent( false ).WaitOne( 10000 );
using ( var btsProxy = new CalcEsbClient() )
{
btsProxy.Open();
int result = a + b;
var response = new Response
{
CorrelationID = guid.ToString(),
Result = result
};
try
{
btsProxy.CalcCallback( response );
}
finally
{
btsProxy.Close();
}
}
}
}
}
Получаем запрос на вычисление, запускаем в новом потоке вычисление, эмулируя задержку в 10 секунд, затем отвечаем шине.
Здесь есть один нюанс – почему-то код для сервиса сгенерировался в нечто некомпилируемое, поэтому мне пришлось вручную поправить Reference.cs, я заменил все вхождения строки «CalcService.BizTalkServiceRef.» на пустое значение «», после этого код заработал как надо. Кто-нибудь знает, почему так?
Нам нужно указать сервису, что мы будем использовать биндинг basicHttp, для этого открываем web.config и сверяем секцию <services>:
<services>
<service name="CalcService.CalcService" behaviorConfiguration="CalcService.CalcServiceBehavior">
<!-- Service Endpoints -->
<endpoint address="" binding="basicHttpBinding" contract="CalcService.ICalcService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
Дальше нам нужно этот сервис задеплоить, для этого в контекстном меню проекта CalcService выбираем пункт Publish…
Figure 20
Нажимаем Publish, затем проверяем в браузере:
Figure 21
Сервис работает.
Теперь нам нужно для окончательной настройки указать BTS, на какой адрес следует отправлять сообщения. Для этого идем в BizTalk Server Administration, находим свое приложение, в узле Send Ports выбираем SvcSendPort (то, которое недоконфигурировали ранее), открываем у него свойства:
Figure 22
Нажимаем кнопку Configure и задаем свойства как на картинке ниже:
Figure 23
Мы указали Address (URI) — http://localhost/CalcService/CalcService.svc как адрес сервиса, SOAP Action header — http://tempuri.org/ICalcService/Add. Для тех, кто незнаком с SOAP, поясню, откуда взялся action header. Все веб-службы описываются посредством WSDL (Web Service Definition Language), поэтому нам нужно заглянуть в этот WSDL. WCF-сервис позволяет это сделать довольно легко, нужно к адресу сервиса добавить параметр «?wsdl». То есть WSDL нашего сервиса находится по адресу http://localhost/CalcService/CalcService.svc?wsdl. В открышемся xml-документе нам нужно найти элемент следующего вида:
Figure 24
Собственно, атрибут soapAction и будет содержать строку, которая нам нужна. Подробнее про SOAP можно прочитать здесь.
Почти всё готово. Сейчас в BizTalk Server Administration выбираем наше приложение и запускаем его:
Figure 25
Для проверки работы сервиса воспользуемся утилитой WcfTestClient.exe, которая проще всего запускается через Visual Studio Command Prompt:
Figure 26
У утилиты выбираем пункт меню File – Add Service, в появившемся окне указываем адрес веб-сервиса шины (http://localhost/mysampleesb/calcesb.svc), добавляем сервис. Выбираем метод Calc и заполняем поля Request:
Figure 27
Для заполнения поля CorrelationID (это GUID) можно воспользоваться сайтом c говорящим названием http://newguid.com/. Нажимаем кнопку Invoke:
Figure 28
Видим, что нам пришел пустой ответ, отлично. Здесь нужно вспомнить, что десятком страниц выше мы задавали путь к папке, в которую BTS будет складывать ответ, полученный от веб-сервиса CalcService.
Figure 29
В моем случае это d:\temp\esb, нужно проверить, действительно ли результат был получен. Иду по этому адресу и вижу, что папка пуста:
Figure 30
Диагностировать проблему будем через BizTalk Administration Console, заходим в раздел Event Viewer (стандартный просмотрщик событий) и ищем логи об ошибках. Они, конечно же, находятся:
Figure 31
Текст ошибки, сокращенный:
The adapter failed to transmit message going to send port «SvcSendPort» with URL «http://localhost/CalcService/CalcService.svc». It will be retransmitted after the retry interval specified for this Send Port. Details:»System.ServiceModel.FaultException: <s:Envelope xmlns:s=»http://schemas.xmlsoap.org/soap/envelope/»><s:Body><s:Fault><faultcode xmlns:a=»http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher»>a:InternalServiceFault</faultcode><faultstring xml:lang=»ru-RU»>Error in deserializing body of request message for operation ‘Add’. OperationFormatter encountered an invalid Message body. Expected to find node type ‘Element’ with name ‘Add’ and namespace ‘http://tempuri.org/’. Found node type ‘Element’ with name ‘ns0:Request’ and namespace ‘http://EsbSample.EsbCore.EsbRequest’</faultstring><detail><ExceptionDetail xmlns=»http://schemas.datacontract.org/2004/07/System.ServiceModel» xmlns:i=»http://www.w3.org/2001/XMLSchema-instance»><HelpLink i:nil=»true» />
Я выделил жирным ключевые моменты. Получается, что мы послали CalcService сообщение в неверном формате. Если взглянуть на оркестровку, то мы видим, что отправляется сообщение типа EsbRequest, о котором CalcService не знает. В этой ситуации у нас два пути:
- Поменять код сервиса так, чтобы он принимал именно EsbRequest;
- Отправлять сообщения в формате, понятном сервису.
Первый путь очень удобен буквально всем, за исключением одного – при работе на реальных проектах очень часто бывает так, что формат сообщений, которые понимает сторонний модуль, изменить нельзя, система либо древняя, либо никто под нашу платформу адаптироваться не хочет. Поэтому выберем второй путь.
Для того, чтобы отправлять сообщения в формате, понятному сервису, нам нужно этот формат узнать. Как это можно сделать?
- Можно «подобрать» вручную;
- Можно скормить wsdl утилите наподобии soapUI, которая покажет нам формат сообщений;
- Можно воспользоваться мастером, встроенным в VS.
Первые два метода не столь удобны, как третий, поэтому воспользуемся им. Добавим в решение новую сборку из категории BizTalk Projects – Empty BizTalk Server Project
Figure 32
Название — EsbSample.ExternalSchemas. Далее подпишем сборку ранее созданным ключем, им же подписывалась первая сборка EsbSample.EsbCore. Затем добавим в проект папку «CalcServiceSchemas», в нее добавим новые элементы через команду контекстного меню Add – Add Generated Items…
Figure 33
В появившемся окне выберем Consume WCF Service, появится мастер:
Figure 34
На следующем шаге задаем адрес WSDL веб-сервиса шины и нажмем Get
Figure 35
Когда мастер закончит работу, в папке появятся различные файлы. Один из них, с расширением odx, нужно удалить – это оркестровка, содержащая типы портов и сообщений. Сама схема сообщений хранится в файле CalcService_tempuri_org.xsd.
Поясню, почему я удаляю тип порта и сообщений, ведь в принципе мы можем без особых усилий размещать в оркестровках конкретные порты и пользоваться конкретными типами сообщений. Но представьте, вы разработали триста оркестровок, а затем формат сообщения поменялся – последствия будут печальны, придется много переделывать. Поэтому советуется использовать в оркестровках свои внутренние форматы, а для общения со сторонними системами использовать трансформацию с помощью карт.
Выделим файл CalcService_tempuri_org.xsd в Solution Explorer и в окне свойств Properties зададим имя типа как «CalcServiceRequest», после нужно собрать решение.
Далее, в папку CalcServiceSchemas нужно добавить карту (Map) под названием RequestToCalcService.btm:
Figure 36
Ее реализация довольно проста, она копирует данные из сообщений в формате нашей внутренней схемы EsbRequest в формат, используемый CalcService.
Figure 37
После этого нужно задеплоить добавленную сборку в приложение Esb (не забудьте подписать, иначе не установится)
Figure 38
Далее все просто, заходим в BizTalk Server Administration, находим наше приложение, находим узел Send Ports, выбираем порт SvcSendPort, открываем у него свойства, переходим на раздел OutboundMaps и выбираем карту RequestToCalcService:
Figure 39
Жмем ОК, запускаем приложение заново, проверяем через WcfTestClient. Если я ничего не забыл, на этот раз никаких ошибок быть не должно, а в папке появляется заветный файл с ответом:
Figure 40
Что же магического произошло? Теперь, когда мы отправляем в порт SvcSendPort сообщение типа EsbRequest, он сам конвертирует его в формат, который используется нашим WCF-сервисом.
Вывод
Таким образом, теперь вся наша мини-система функционирует. Аналогичным образом добавляются другие системы, тем самым достигается интеграция систем через корпоративную шину.
Исходники проекта можно скачать здесь.
Отличная статья! СПАСИБО!
Лучшая статья по теме, что я читал
Благодарю, не зря, значит, старался
Огромная благодарность за проявленное время в создании данной статьи!
Спасибо за статью!