Главная страница
Случайная страница
КАТЕГОРИИ:
АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
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>
|