Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Custom Type Mapper






Мы расширим категорию COM-объектов, которые могут быть переданы клиенту по протоколу SOAP, реализовав свой собственный Custom Type Mapper. Наш mapper будет использовать возможность объектов сохранять и восстанавливать свое состояние с помощью интерфейса IPersistStream, который реализуют очень многие компоненты.

Mapper должен поддерживать интерфейс ISoapTypeMapper:

Метод/Свойство Описание
IID IID интерфейса объекта, для которого предназначен mapper.
Init Инициализация mapper-а.
Read Преобразование из XML в сложный тип.
SchemaNode Возвращает фрагмент схемы, описывающий сложный тип.
VarType Тип значения, которое ожидает mapper.
Write Преобразует сложный тип в XML.
XSDType Тип данных XML, который поддерживает mapper.

Так как mapper будет использовать интерфейс IPersistStream объекта, метод VarType будет возвращать VT_UNKNOWN, а XSDType - enXSDbase64binary.

Нам потребуется преобразование из бинарного формата в base64. Это преобразование может быть выполнено с помощью компонента, входящего в состав SOAP Toolkit – DataEncoder:

CComPtr< IDataEncoderFactory> spFactory; CheckError(spFactory.CoCreateInstance(CLSID_DataEncoderFactory30)); CComPtr< IDataEncoder> spEncoder; CheckError(spFactory-> GetDataEncoder(CComBSTR(L" base64"), & spEncoder));

Ниже приведена реализация методов Read и Write:

STDMETHODIMP CPersistMapper:: Write(ISoapSerializer * par_ISoapSerializer, BSTR par_encoding, enEncodingStyle par_encodingMode, LONG par_flags, VARIANT * par_var) { try { using namespace _com_util; // создаем фабрику кодировщиков и получаем нужный CComPtr< IDataEncoderFactory> spFactory; CheckError(spFactory.CoCreateInstance(CLSID_DataEncoderFactory30)); CComPtr< IDataEncoder> spEncoder; CheckError(spFactory-> GetDataEncoder(CComBSTR(L" base64"), & spEncoder)); if((par_var-> vt! = VT_UNKNOWN) & & (par_var-> vt! = VT_DISPATCH)) _com_issue_error(E_INVALIDARG); // сохраняем объект в IStream CComPtr< IPersistStream> spPersist; CheckError(par_var-> punkVal-> QueryInterface(IID_IPersistStream, (void**)& spPersist)); CComPtr< IStream> spStm; CheckError(CreateStreamOnHGlobal(0, TRUE, & spStm)); CheckError(spPersist-> Save(spStm, FALSE)); LARGE_INTEGER off = {0}; CheckError(spStm-> Seek(off, STREAM_SEEK_SET, NULL)); STATSTG st = {0}; CheckError(spStm-> Stat(& st, 0)); // перекодируем в base64 CComPtr< IStream> spEncoded; CheckError(CreateStreamOnHGlobal(0, TRUE, & spEncoded)); CheckError(spEncoder-> EncodeStream(spStm, spEncoded)); CheckError(spEncoded-> Seek(off, STREAM_SEEK_SET, NULL)); LPVOID hGlobal = NULL; CheckError(GetHGlobalFromStream(spEncoded, & hGlobal)); CheckError(spEncoded-> Stat(& st, 0)); // записываем результат в serializer CheckError(par_ISoapSerializer-> WriteBuffer(st.cbSize.LowPart, *((unsigned char**) hGlobal))); } catch(_com_error& e) { return e.Error(); } return S_OK; } STDMETHODIMP CPersistMapper:: Read(ISoapReader * par_soapreader, IXMLDOMNode * par_Node, BSTR par_encoding, enEncodingStyle par_encodingMode, LONG par_flags, VARIANT * par_var) { try { using namespace _com_util; CComPtr< IDataEncoderFactory> spFactory; // получаем атрибут targetPROGID из WSML и создаем нужный объект CComPtr< IXMLDOMElement> spElem; CheckError(m_spWSML.QueryInterface(& spElem)); CComVariant vValue; CComPtr< IPersistStream> spPersist; CheckError(spElem-> getAttribute(L" targetPROGID", & vValue)); CheckError(vValue.ChangeType(VT_BSTR)); CheckError(spPersist.CoCreateInstance(vValue.bstrVal)); // создаем фабрику кодировщиков и получаем нужный // код аналогичный приведенному выше для Write... // получаем данные в base64, преобразуем их в IStream и перекодируем // в бинарный формат CComBSTR bstrText; CheckError(par_Node-> get_text(& bstrText)); CComPtr< IStream> spStm; USES_CONVERSION; CheckError(CreateStreamOnHGlobal(0, TRUE, & spStm)); CheckError(spStm-> Write(W2A(bstrText), bstrText.Length(), 0)); LARGE_INTEGER off = {0}; CheckError(spStm-> Seek(off, STREAM_SEEK_SET, 0)); CComPtr< IStream> spDecoded; CheckError(CreateStreamOnHGlobal(0, TRUE, & spDecoded)); CheckError(spEncoder-> DecodeStream(spStm, spDecoded)); CheckError(spDecoded-> Seek(off, STREAM_SEEK_SET, 0)); // загружаем данные объекта CheckError(spPersist-> Load(spDecoded)); CComPtr< IUnknown> spUnk; CheckError(spPersist.QueryInterface(& spUnk)); CComVariant vResult = spUnk; // возвращаем созданный объект CheckError(vResult.Detach(par_var)); } catch(_com_error& e) { return e.Error(); } return S_OK; }

Метод Read создает экземпляр передаваемого компонента, используя атрибут targetPROGID, указанный в WSML-файле.

Чтобы проверить mapper в действии, нам потребуется серверный компонент, возвращающий объектную ссылку, и компонент, поддерживающий IPersistStream. В качестве последнего нам подойдет Recordset, входящий в состав ADO. Метод серверного компонента мы объявим следующим образом:

interface IPersistObj: IDispatch { [id(1)] HRESULT Get([out]_Recordset** pData); };

А реализация этого метода будет создавать Recordset с двумя полями, заполняя их некоторыми значениями:

STDMETHODIMP CPersistObj:: Get(_Recordset **pData) { _RecordsetPtr pRecordset(__uuidof(Recordset)); pRecordset-> CursorLocation = adUseClient; FieldsPtr pFields = pRecordset-> Fields; pFields-> Append(L" id", adInteger, 0, adFldUnspecified); pFields-> Append(L" name", adBSTR, 0, adFldUnspecified); pRecordset-> Open(vtMissing, vtMissing, adOpenStatic, adLockBatchOptimistic, adOptionUnspecified); pRecordset-> AddNew(); pFields-> Item[L" id" ]-> Value = (long) 1; pFields-> Item[L" name" ]-> Value = L" 123"; *pData = pRecordset.Detach(); return S_OK; }

Теперь нам необходимо создать WSDL- и WSML-файлы, описывающие серверный компонент и использующие разработанный нами mapper. Часть работы придется проделать вручную, так как генератор, обнаружив в методе Get параметр с типом _Recordset, создаст описания большого количества компонентов из ADO со ссылкой на GCTM. Например, у интерфейса _Recordset есть свойство Fields, для которого генератор также вставит описание в WSDL и WSML. Нам придется поступить так: убрать из IDL ссылку на _Recordset (временно заменив на long, например), сгенерировать WSDL и WSML мастером, а затем добавить в WSDL и WSML описание для типа Recordset вручную.

В WSML-файл мы добавим:

  • Ссылку на наш mapper внутри тега < service>.
< using PROGID='CustomMapper.PersistMapper' cachable='0' ID='CTM' />
  • В секцию types описание типа Recordset со ссылкой на mapper.
< types> < type name='Recordset' targetNamespace=... uses='CTM' targetPROGID='ADODB.Recordset' iid='{00000535-0000-0010-8000-00aa006d2ea4}'/> < /types>
СОВЕТ Изменения нужно внести в два WSML-файла, сгенерированных мастером, один из них используется на сервере, а другой на клиенте, ссылка на Custom Mapper и описание типа Recordset должно присутствовать в обоих.

 

ПРЕДУПРЕЖДЕНИЕ При описании типа в WSML-файле очень важно указать правильный IID интерфейса, иначе SOAPServer не будет использовать указанный mapper. В данном случае мы указали IID интерфейса _Recordset.

В WSDL-файл необходимо добавить:

  • Описание сложного типа Recordset в раздел < Schema>:
< complexType name ='Recordset'> < sequence> < /sequence> < /complexType>
  • В описание метода Get добавить ссылку на тип Recordset:
< message name='PersistObj.GetResponse'> < part name='pData' type='typens: Recordset'/> < /message>

Код клиента, как обычно, очень прост:

Dim o As MSSOAPLib30.SoapClient30 Set o = New MSSOAPLib30.SoapClient30 o.MSSoapInit " D: ProjectsSOAPMapperSampleSrvIISMapperSampleSrv.WSDL",, _ " PersistObjSoapPort", _ " D: ProjectsSOAPMapperSampleSrvIISMapperSampleSrvClient.WSML" Dim p As Object o.Get p

Работу mapper-а можно “увидеть”, используя утилиту трассировки SOAP-вызовов. Для этого в WSDL-файле изменим URL, добавив порт 8080, который использует трассировщик:

< soap: address location='https://ivan: 8080/MapperSampleSrv/MapperSampleSrv.ASP'/>
ПРИМЕЧАНИЕ Утилита трассировки должна быть запущена во время SOAP-вызова, иначе обращение к порту 8080 будет неуспешным.

Ниже приведен пример отклика сервера, использующего mapper, “увиденный” с помощью утилиты трассировки:

< SOAP-ENV: Body...> < SOAPSDK4: GetResponse...> < pData> tpLyPwSyzxGNIwCqAF/+WAEHVEchAAAAAAIZALaS8j8Ess8RjSMAqgBf/lgBAAAAAAAAAAADZwDS rWP2AuvPEbDjAKoAPwAPAAAAAgACAAAAAAAAAAEAAAABAME8jrbrbdARjfYAqgBf/lgHAAsAAAAE AAEAAAATAAAABAABAAAADQAAAAAADgAAAAAADwAAAAAAEAAAAAAAEgAAAAAAEHwAAgC+IrXI81zO Ea3lAKoARHc9BAB/AAAAAgD//4YAAAACAP//IgAAAAQAAAAAAEkAAAAEAAAAAADBPI62623QEY32 AKoAX/5YBQAEAAAABAAPAAAABQAAAAQAAgAAAAMAAAAEAA8AAAAHAAAABAAyAAAACAAAAAQAAwAA AAYhAIABAAEAAgBpAGQAAwAEAAAAAAAAAAAAAAAUAAAAAAD//wYlAIABAAIABABuAGEAbQBlAIIA

/////wAAAAAAAAAAhAAAAAAA
//8NwAABAAAABgAAADEAMgAzAA8= < /pData> < /SOAPSDK4: GetResponse> < /SOAP-ENV: Body>






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.