При работе над фронтом для кафе появилась задача обращаться к веб-сервису 1С из приложения, разрабатываемого на Android. Google мне дал несколько ответов на тему как вообще работать с SOAP, используя библиотеку ksoap2-android. Они помогли в передаче простых типов, но когда дело дошло до передачи массива, пришлось немного подумать.
В конфигурации 1С создан веб-сервис с методом WriteSale. Метод принимает несколько параметров, один из которых, items, имеет тип ItemsSold (задан в пакете XDTO конфигурации). Остальные параметры имеют простые типы (string, datetime). Скрин конфигурации:

Тип ItemsSold имеет единственное свойство Items, для которого установлено свойство «Максимальное количество» в -1, указывая на то, что это массив. Тип этого свойства — ItemSold. Скрин:

У типа ItemSold все свойства простого типа. Метод WriteSale веб-сервиса имеет следующий код:
Для обращения к веб-сервису из приложения Android написал следующий код (в соответствии с хорошим примером простого клиента):
Вроде бы код выглядит правильно, формирует красивый xml-запрос:
Но веб-сервис отвечает на него 500-й ошибкой. При этом, обращаясь к другому методу с параметрами простого типа на том же веб-сервисе, мы получаем корректный ответ. Более того, обращаясь из другой базы 1С через WS-ссылка к приведенному выше методу веб-сервиса, мы получаем корректный ответ и выполнение необходимых действий на стороне веб-сервиса. Поэтому пришлось перехватить запрос, формируемый другой базой 1С. Сделать это фидлером не получилось, так как он каким-то образом обрезал само тело запроса с xml и не передавал его веб-сервису. Нормально перехватить запрос удалось только с помощью WireShark. Итак, текст запроса от 1С:
Несложно заметить, что для вложенных элементов массивов (Code, Price...) библиотека ksoap2-android не проставляет префиксы с пространством имен. Для корневых элементов (id, date...) они также не проставлены, но этот факт 1С в ступор не вводит. А их отсутствие у под-элементов заставляет программу усомниться в корректности входных данных, прочитать она их не может.
Изучив код библиотеки, решил, что наиболее рациональным будет модифицировать метод SoapObject#addProperty(String, Object) следующим образом:
В исходном коде я заменил объекты SoapObject на SoapObjectCustom в следующих местах:
Скорее всего, есть смысл в том, что авторы не включали префиксы пространства имен в свойства элементов. И вполне возможно, что в работе с другими веб-сервисами такие коррективы приведут к некорректному поведению программы. Тем не менее, данный метод работает с веб-сервисами 1С, надеюсь это описание кому-нибудь поможет в работе.
Описанное выше было протестировано с 1С v.8.2.15.294 и Android 12 (3.0).
Веб-сервис на стороне 1С
В конфигурации 1С создан веб-сервис с методом WriteSale. Метод принимает несколько параметров, один из которых, items, имеет тип ItemsSold (задан в пакете XDTO конфигурации). Остальные параметры имеют простые типы (string, datetime). Скрин конфигурации:

Тип ItemsSold имеет единственное свойство Items, для которого установлено свойство «Максимальное количество» в -1, указывая на то, что это массив. Тип этого свойства — ItemSold. Скрин:

У типа ItemSold все свойства простого типа. Метод WriteSale веб-сервиса имеет следующий код:
Функция WriteSale(id, date, clientCardNumber, discountRate, items, deptId, bonuses, premiumBonuses) Текст = "OK"; Попытка Карточка = ПолучитьКартуПоНомеру(clientCardNumber); ПроцентСкидки = Число(discountRate); Подразделение = НайтиПодразделениеПоПрефиксу(deptId); //... Документ = НайтиДокументПоКодуДате(date, Число(id), Подразделение); Если Не ЗначениеЗаполнено(Документ) Тогда Объект = Документы.ПолныйЧек.СоздатьДокумент(); Объект.Дата = date; Объект.КафеНомерДокумента = id; Иначе Объект = Документ.ПолучитьОбъект(); КонецЕсли; Объект.ОплатаБонусами = Число(bonuses); //... Объект.Продажи.Очистить(); // Здесь идет использование массива Для Каждого item Из items.Items Цикл Номенклатура = НайтиНоменклатуруПоКоду(item.Code); Строка = Объект.Продажи.Добавить(); Строка.Количество = Число(item.Quantity); //... КонецЦикла; Объект.Записать(РежимЗаписиДокумента.Проведение); Исключение Текст = ОписаниеОшибки(); ЗаписьЖурналаРегистрации("Cafe.WriteSale - исключение: " + Текст, УровеньЖурналаРегистрации.Ошибка); ВызватьИсключение; КонецПопытки; // Возвращаем текст ошибки или "ОК" Возврат Текст; КонецФункции
Клиент на стороне Android
Для обращения к веб-сервису из приложения Android написал следующий код (в соответствии с хорошим примером простого клиента):
protected String call() throws Exception { result = null; HttpTransportSE httpTransport = new HttpTransportSE(uri); httpTransport.debug = true; String resultString; SoapObject request = new SoapObject(namespace, methodName); request.addProperty("id", sale.getId()); SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss"); request.addProperty("date", dateFormat.format(sale.getDate())); request.addProperty("clientCardNumber", sale.getCardNumber()); request.addProperty("bonuses", Double.toString(sale.getBonuses())); //... // see - http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks#Adding_an_array_of_complex_objects_to_the_request SoapObject sales = new SoapObject(namespace, "items"); for (SaleItemInformation item : sale.getSales()) { SoapObject itemSoap = new SoapObject(namespace, "Items"); itemSoap.addProperty("Code", item.getItem().getSourceCode()); itemSoap.addProperty("Quantity", Double.toString(item.getQuantity())); //... sales.addSoapObject(itemSoap); } request.addSoapObject(sales); SoapSerializationEnvelope envelope = new SoapSerializationEnvelope( SoapEnvelope.VER11); // Тоже важный элемент - не выводит типы данных в элементах xml envelope.implicitTypes = true; envelope.setOutputSoapObject(request); try { httpTransport.call(soapAction, envelope); } catch (Exception e) { e.printStackTrace(); throw e; } resultString = envelope.getResponse().toString(); return resultString; }
Вроде бы код выглядит правильно, формирует красивый xml-запрос:
<v:Envelope xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d="http://www.w3.org/2001/XMLSchema" xmlns:c="http://schemas.xmlsoap.org/soap/encoding/" xmlns:v="http://schemas.xmlsoap.org/soap/envelope/"> <v:Header /> <v:Body> <n0:WriteSale id="o0" c:root="1" xmlns:n0="http://www.xxxxx.ru"> <date i:type="d:string">Thu May 31 16:13:08 YEKST 2012</date> <clientCardNumber i:type="d:string">120</clientCardNumber> <discountRate i:type="d:string">5.0</discountRate> <id i:type="d:long">11</id> <n0:items i:type="n0:items"> <n0:Items i:type="n0:Items"> <Code i:type="d:string">3000</Code> <Price i:type="d:string">100.0</Price> <Quantity i:type="d:string">2.0</Quantity> <Sum i:type="d:string">200.0</Sum> </n0:Items> <n0:Items i:type="n0:Items"> <Code i:type="d:string">3001</Code> <Price i:type="d:string">110.0</Price> <Quantity i:type="d:string">1.0</Quantity> <Sum i:type="d:string">110.0</Sum> </n0:Items> </n0:items> </n0:WriteSale> </v:Body> </v:Envelope>
Но веб-сервис отвечает на него 500-й ошибкой. При этом, обращаясь к другому методу с параметрами простого типа на том же веб-сервисе, мы получаем корректный ответ. Более того, обращаясь из другой базы 1С через WS-ссылка к приведенному выше методу веб-сервиса, мы получаем корректный ответ и выполнение необходимых действий на стороне веб-сервиса. Поэтому пришлось перехватить запрос, формируемый другой базой 1С. Сделать это фидлером не получилось, так как он каким-то образом обрезал само тело запроса с xml и не передавал его веб-сервису. Нормально перехватить запрос удалось только с помощью WireShark. Итак, текст запроса от 1С:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header/> <soap:Body> <m:WriteSale xmlns:m="http://www.xxxxx.ru"> <m:id xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">1</m:id> <m:date xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2</m:date> <m:clientCardNumber xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3</m:clientCardNumber> <m:discountRate xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">4</m:discountRate> <m:items xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <m:Items> <m:Code>123</m:Code> <m:Price>12.2</m:Price> <m:Quantity>2</m:Quantity> <m:Sum>2</m:Sum> </m:Items> <m:Items> <m:Code>2</m:Code> <m:Price>1</m:Price> <m:Quantity>2</m:Quantity> <m:Sum>2</m:Sum> </m:Items> </m:items> </m:WriteSale></soap:Body> </soap:Envelope>
Несложно заметить, что для вложенных элементов массивов (Code, Price...) библиотека ksoap2-android не проставляет префиксы с пространством имен. Для корневых элементов (id, date...) они также не проставлены, но этот факт 1С в ступор не вводит. А их отсутствие у под-элементов заставляет программу усомниться в корректности входных данных, прочитать она их не может.
Изучив код библиотеки, решил, что наиболее рациональным будет модифицировать метод SoapObject#addProperty(String, Object) следующим образом:
public static class SoapObjectCustom extends SoapObject { public SoapObjectCustom(String namespace, String name) { super(namespace, name); } @Override public SoapObject addProperty(String name, Object value) { PropertyInfo propertyInfo = new PropertyInfo(); propertyInfo.name = name; propertyInfo.type = value == null ? PropertyInfo.OBJECT_CLASS : value.getClass(); propertyInfo.setValue(value); // Добавил эту строку propertyInfo.setNamespace(this.namespace); return addProperty(propertyInfo); } }
В исходном коде я заменил объекты SoapObject на SoapObjectCustom в следующих местах:
//... SoapObjectCustom request = new SoapObjectCustom(namespace, methodName); //... SoapObject sales = new SoapObject(namespace, "items"); for (SaleItemInformation item : sale.getSales()) { SoapObjectCustom itemSoap = new SoapObjectCustom(namespace, "Items"); //... } //...
Заключение
Скорее всего, есть смысл в том, что авторы не включали префиксы пространства имен в свойства элементов. И вполне возможно, что в работе с другими веб-сервисами такие коррективы приведут к некорректному поведению программы. Тем не менее, данный метод работает с веб-сервисами 1С, надеюсь это описание кому-нибудь поможет в работе.
Описанное выше было протестировано с 1С v.8.2.15.294 и Android 12 (3.0).
