Ошибка десериализации параметра в службе WCF из Android kSOAP - PullRequest
2 голосов
/ 02 августа 2011

8/5/11: обновление в нижней части сообщения

Я пытаюсь вызвать метод веб-службы WCF из приложения для Android с помощью kSOAP. Метод принимает 4 параметра. 3 из них имеют тип Guid (для которого работает маршаллинг UUID в Java), а последний - пользовательский тип: LocationLibrary.Location Этот тип находится в отдельной DLL (LocationLibrary), которую я загружаю в проект веб-службы WCF, и он в основном состоит из двух значений типа double: широты и долготы.

    [OperationContract]       
    byte[] GetData(Guid deviceId, Guid appId, Guid devKey, LocationLibrary.Location loc);

Класс Location в проекте LocationLibrary очень прост:

    namespace LocationLibrary
    {
    public class Location
    {
        public double latitude { get; set; }

        public double longitude { get; set; }

        public new string ToString()
        {
            return latitude.ToString() + "," + longitude.ToString();
        }

        public bool Equals(Location loc)
        {
            return this.latitude == loc.latitude && this.longitude == loc.longitude;
        }
    }
}

В моем проекте Android я создал класс с именем "Location", который похож на версию .NET:

    public class Location {
    public double latitude;
    public double longitude;

    public Location() {}

    public static Location fromString(String s)
    {
    //Format from SOAP message is "anyType{latitude=39.6572799682617; longitude=-78.9278602600098; }"
    Location result = new Location();
    String[] tokens = s.split("=");
    String lat = tokens[1].split(";")[0];
    String lng = tokens[2].split(";")[0];
    result.latitude = Double.parseDouble(lat);
    result.longitude = Double.parseDouble(lng); 
    return result;
    }

    public String toString()
    {       
    return Double.toString(latitude) + "," + Double.toString(longitude);    
    }
}

При использовании kSOAP для подключения к веб-сервису я делаю следующее:

    private final String SOAP_ACTION = "http://tempuri.org/IMagicSauceV3/GetData";
    private final String OPERATION_NAME = "GetData";     
    private final String WSDL_TARGET_NAMESPACE = "http://tempuri.org/";
    private final String SOAP_ADDRESS = "http://mydomain.com/myservice.svc";
    private final SoapSerializationEnvelope envelope;

    SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE, OPERATION_NAME);
    request.addProperty(Cloud8Connector.deviceIdProperty);
    request.addProperty(Cloud8Connector.appIdProperty);
    request.addProperty(Cloud8Connector.devKeyProperty);        
    PropertyInfo locationProperty = new PropertyInfo();
    locationProperty.setName("loc");
    locationProperty.setValue(Cloud8Connector.myLoc);
    locationProperty.setType(Location.class);
    request.addProperty(locationProperty);

    envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
    envelope.dotNet = true;
       envelope.setOutputSoapObject(request);
       MarshalUUID mu = new MarshalUUID();
    mu.register(envelope);
    MarshalLocation ml = new MarshalLocation();
    ml.register(envelope);

     byte[] result = null;
    HttpTransportSE httpRequest = new HttpTransportSE(SOAP_ADDRESS);

    try
    {
        httpRequest.call(SOAP_ACTION, envelope);

        String payloadString = ((SoapPrimitive)(envelope.getResponse())).toString();
        result = Base64.decode(payloadString, Base64.DEFAULT);         
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }

Как видите, я создал простой класс Marshal для Location, который в основном просто использует методы fromString и toString. Я регистрирую конверт в инстанции маршала для Location:

    MarshalLocation ml = new MarshalLocation();
    ml.register(envelope);

Вот класс маршала для Локации:

    public class MarshalLocation implements Marshal
    {

    public Object readInstance(XmlPullParser parser, String namespace, String name, 
            PropertyInfo expected) throws IOException, XmlPullParserException {
        return Location.fromString(parser.nextText());                                
    }

    public void register(SoapSerializationEnvelope cm) {
         cm.addMapping(cm.xsd, "Location", Location.class, this);

    }

    public void writeInstance(XmlSerializer writer, Object obj) throws IOException {

            writer.text(((Location)obj).toString());             
        }    
    }

Однако я получаю эту ошибку из WCF и не могу заставить ее работать. Из-за ошибки и поиска я думаю, что мне нужно что-то настроить с помощью моего веб-сервиса, но я не уверен, что именно является лучшим способом обойти это.

Я пытался добавить это в файл web.config веб-службы:

        <system.runtime.serialization>
    <dataContractSerializer>
        <declaredTypes>
            <add type="Location, LocationLibrary, Version=1.0.0.0,Culture=neutral,PublicKeyToken=null">
                <knownType type="Location, LocationLibrary, Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"></knownType>
            </add>
        </declaredTypes>
    </dataContractSerializer>
</system.runtime.serialization>

И я попытался добавить атрибут ServiceKnownType в подпись метода интерфейса:

    [OperationContract]       
    [ServiceKnownType(typeof(LocationLibrary.Location))]
    byte[] GetData(Guid deviceId, Guid appId, Guid devKey, LocationLibrary.Location loc);

Но я все еще получаю эту ошибку: (

Можете ли вы указать мне правильное направление? Спасибо!

Ошибка:

07-30 00: 42: 08.186: WARN / System.err (8723): SoapFault - код ошибки: 'a: DeserializationFailed' faultstring: 'Форматер бросил исключение при попытке десериализации сообщения: произошла ошибка при попытке десериализации параметра http://tempuri.org/:loc. Сообщение InnerException было «Ошибка в строке 1 позиции 522. Элемент 'http://tempuri.org/:loc' содержит данные типа, который отображается на имя 'http://www.w3.org/2001/XMLSchema:Location'. десериализатор имеет нет знаний какого-либо типа, который сопоставляется с этим именем. Рассмотрите возможность использования DataContractResolver или добавьте тип, соответствующий 'Location', в список известных типов - например, с помощью KnownTypeAttribute атрибут или добавив его в список известных типов, передаваемых DataContractSerializer. ». Пожалуйста, смотрите InnerException для более подробной информации. ' faultactor: деталь 'null': org.kxml2.kdom.Node@4053aa28

Обновление

В идеале, мне не нужно было бы перемещать классы LocationLibrary внутри основного проекта WCF, поскольку это усложнит обработку устаревших клиентов, которые ожидают этого в другом пространстве имен. Тем не менее, я заставил все работать, переместив эти классы в основной проект WCF И изменив класс маршала в java так:

    public void register(SoapSerializationEnvelope cm) {            
         cm.addMapping("http://schemas.datacontract.org/2004/07/MagicSauceV3", "Location", Location.class, this);
    }        

    public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
            Location loc = (Location)obj;
            writer.startTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "latitude");
            writer.text(Double.toString(loc.latitude));
            writer.endTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "latitude");
            writer.startTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "longitude");
            writer.text(Double.toString(loc.longitude));
            writer.endTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "longitude");       
        }

Теперь, когда я получил это на работу, я просто заставил его работать с WCL-классами LocationLibrary, как они были (в отдельной DLL). Итак, я изменил класс маршала следующим образом:

    public void register(SoapSerializationEnvelope cm) {            
         cm.addMapping("http://schemas.datacontract.org/2004/07/LocationLibrary", "Location", Location.class, this);
    }


    public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
            Location loc = (Location)obj;
            writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
            writer.text(Double.toString(loc.latitude));
            writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
            writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");
            writer.text(Double.toString(loc.longitude));
            writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");       
        }

Похоже, что это должно быть успешно, поскольку он производит XML, аналогичный версии WP7, которая работает.

Рабочий запрос WP7 XML:

<GetData xmlns="http://tempuri.org/">
<deviceId>{valid GUID}</deviceId>
<appId>{valid GUID}</appId>
<devKey>{valid GUID}</devKey>
<loc xmlns:d4p1="http://schemas.datacontract.org/2004/07/LocationLibrary" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:latitude>47.65</d4p1:latitude>
<d4p1:longitude>-122.34</d4p1:longitude>
</loc>
</GetData>

Неработающий XML-запрос Android:

<GetData xmlns="http://tempuri.org/" id="o0" c:root="1">
<deviceId i:type="d:UUID">{valid GUID}</deviceId>
<appId i:type="d:UUID">{valid GUID}</appId>
<devKey i:type="d:UUID">{valid GUID}</devKey>
<loc i:type="n0:Location" xmlns:n0="http://schemas.datacontract.org/2004/07/LocationLibrary">
<n0:latitude>47.65</n0:latitude>
<n0:longitude>-122.34</n0:longitude>
</loc>
</GetData>

Приведенный выше XML-запрос Android вызывает такую ​​ошибку:

08-05 22: 51: 23.703: WARN / System.err (1382): SoapFault - код ошибки: 'a: DeserializationFailed' faultstring: 'Средство форматирования выдало исключение при попытке десериализации сообщения: во время произошла ошибка при попытке десериализации параметра http://tempuri.org/:loc. сообщение InnerException было «Ошибка в строке 1, позиция 590. Элемент» http://tempuri.org/:loc' содержит данные типа, который сопоставляется с именем «http://schemas.datacontract.org/2004/07/LocationLibrary:Location'. Десериализатору неизвестно, какой тип карты с этим именем. Попробуйте использовать DataContractResolver или добавить тип, соответствующий «Location», в список известных типов - например, с помощью атрибута KnownTypeAttribute или добавив его в список известных типов, передаваемых в DataContractSerializer. '. Пожалуйста, смотрите InnerException для более подробной информации. ' faultactor: деталь 'null': org.kxml2.kdom.Node@4054bcb8

Единственное заметное отличие, которое я вижу, это свойство "loc" в рабочем XML-запросе: XMLNS: я = "http://www.w3.org/2001/XMLSchema-instance"

Я не знаю, будет ли это иметь значение (и, похоже, на него не ссылаются вложенные теги). Я также не знаю, как добавить это дополнительное пространство имен, не испортив другое.

Извините за длинный пост, но я хотел убедиться, что у вас есть вся информация, которая вам нужна, чтобы помочь:)

Спасибо!

Ответы [ 2 ]

1 голос
/ 08 августа 2011

Спасибо Ладиславу за точку в правильном направлении! Я не уверен, что этика здесь за вознаграждение, но я был бы рад следовать, если кто-то посоветует.

Ответ был двояким, и я могу подтвердить, что теперь он работает с классом LocationLibrary в отдельной DLL:

  1. Измените способ формирования XML-запроса в Android с помощью моего класса маршалинга. Метод writeInstance использовал только .textMethod класса XmlSerializer, вызывая .toString () для класса Location. Я изменил его, чтобы использовать правильный способ формирования тегов XML (методы .startTag и .endTag):

    public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
        Location loc = (Location)obj;
        writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
        writer.text(Double.toString(loc.latitude));
        writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
        writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");
        writer.text(Double.toString(loc.longitude));
        writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");       
    }
    
  2. Добавьте несколько простых тегов в службу WCF. Я добавил [DataContract] в класс Location в сборке LocationLibrary, используемой службой WCF, и добавил [DataMember] к каждому из его членов:

    [DataContract]
    public class Location
    {
    [DataMember]
    public double latitude { get; set; }
    
    [DataMember]
    public double longitude { get; set; }
    
    public new string ToString()
    {
        return latitude.ToString() + "," + longitude.ToString();
    }
    
    public bool Equals(Location loc)
    {
        return this.latitude == loc.latitude && this.longitude == loc.longitude;
    }
    
1 голос
/ 04 августа 2011

Ну, как я и ожидал, вы делаете просто ToString, но это неправильно. Ваше местоположение не переносится как строка из двух объединенных значений. Он транспортируется как объект, сериализованный в XML. Что-то вроде:

<Location> <!-- or loc -->
   <latitude>10.0</latitude>
   <longitude>-20.0</longitude>
</Location>
...