从 Android kSOAP 反序列化 WCF 服务中的参数时出错
2011 年 8 月 5 日:在帖子底部附近更新
我正在尝试使用 kSOAP 从我的 Android 应用程序调用我的 WCF Web 服务方法。该方法有 4 个参数。其中 3 个是 Guid 类型(编组 Java 的 UUID 有效),最后一个是自定义类型:LocationLibrary.Location。该类型位于一个单独的 DLL(LocationLibrary)中,我将其加载到 WCF Web 服务项目中,它基本上由两个双精度值(纬度和经度)组成。
[OperationContract]
byte[] GetData(Guid deviceId, Guid appId, Guid devKey, LocationLibrary.Location loc);
项目 LocationLibrary 中的 Location 类非常简单:
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 连接到 Web 服务时,我执行以下操作:
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();
}
正如您所看到的,我为 Location 创建了一个简单的 Marshal 类,它基本上只使用 fromString 和 toString 方法。我使用 Location: 的 marshal 实例注册信封:
MarshalLocation ml = new MarshalLocation();
ml.register(envelope);
这是 Location: 的 marshal 类:
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 服务的 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:反序列化失败'错误字符串:'格式化程序抛出了一个 尝试反序列化消息时出现异常:出现错误 尝试反序列化参数 http://tempuri.org/:loc 时。这 InnerException 消息为“第 1 行位置 522 中出现错误。元素” 'http://tempuri.org/:loc' 包含来自映射到的类型的数据 名称“http://www.w3.org/2001/XMLSchema:Location”。解串器有 不知道映射到该名称的任何类型。考虑使用 DataContractResolver 或添加 'Location' 对应的类型 已知类型的列表 - 例如,通过使用 KnownTypeAttribute 属性或将其添加到传递给的已知类型列表中 数据契约序列化器。'。请参阅 InnerException 了解更多详细信息。 故障因子:'null'详细信息:org.kxml2.kdom.Node@4053aa28
Update
理想情况下,我不必将 LocationLibrary 类移动到主 WCF 项目中,因为这会使遗留客户端的处理变得复杂期望它在不同的命名空间中。然而,我确实通过将这些类移动到主 WCF 项目中并将 java 中的 marshal 类修改为
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");
}
:在一个单独的 DLL 中)。所以我修改了 marshal 类:
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");
}
这看起来应该会成功,因为它生成与 WP7 版本类似的 XML,并且可以正常工作。
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>
Android 非工作 XML 请求:
<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>
上述 Android XML 请求产生此响应错误:
08-05 22:51:23.703: WARN/System.err(1382): SoapFault - 错误代码:'a:DeserializationFailed' 错误字符串: '格式化程序在尝试反序列化消息时抛出异常:尝试反序列化参数 http://tempuri.org/:loc。 InnerException 消息是“第 1 行位置 590 中出现错误”。元素“http://tempuri.org/:loc”包含映射到名称“http://schemas.datacontract.org/2004/07/”的类型的数据。位置库:位置'。解串器不知道映射到该名称的任何类型。考虑使用 DataContractResolver 或将与“Location”对应的类型添加到已知类型列表中 - 例如,通过使用 KnownTypeAttribute 属性或将其添加到传递给 DataContractSerializer 的已知类型列表中。请参阅 InnerException 了解更多详细信息。 failureactor: 'null' 详细信息: org.kxml2.kdom.Node@4054bcb8
我看到的唯一显着区别是工作 XML 请求中的“loc”属性: xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
我不知道这是否会产生影响(并且它似乎没有被嵌套标签引用)。我也不知道如何添加额外的命名空间而不弄乱另一个命名空间。
很抱歉这篇文章很长,但我想确保您拥有需要帮助的所有信息:)
谢谢!
8/5/11: Update near bottom of post
I'm trying to call my WCF web service method from my Android app using kSOAP. The method takes 4 parameters. 3 of them are of type Guid (for which marshalling Java's UUID works), and the last is a custom type: LocationLibrary.Location. This type is in a separate DLL (LocationLibrary) that I load into the WCF web service project and it is basically comprised of two doubles, latitude and longitude.
[OperationContract]
byte[] GetData(Guid deviceId, Guid appId, Guid devKey, LocationLibrary.Location loc);
The Location class in the project LocationLibrary is very simple:
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;
}
}
}
In my Android project, I've created a class named "Location" that is similar to the .NET version:
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);
}
}
When using kSOAP to connect to the web service, I do the following:
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();
}
As you can see, I've created a simple Marshal class for Location, which basically just uses the fromString and toString methods. I register the envelope with the marshal instance for Location:
MarshalLocation ml = new MarshalLocation();
ml.register(envelope);
Here's the marshal class for Location:
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());
}
}
However, I get this error returned from WCF and cannot seem to get it to work. From the error and from searching, I think I need to tweak something with my web service, but I'm not sure what exactly is the best way to get around this.
I've tried adding this to the web.config file of the web service:
<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>
And I've tried adding the ServiceKnownType attribute to the interface method signature:
[OperationContract]
[ServiceKnownType(typeof(LocationLibrary.Location))]
byte[] GetData(Guid deviceId, Guid appId, Guid devKey, LocationLibrary.Location loc);
But I still get this error :(
Can you point me in the right direction? Thanks!
Error:
07-30 00:42:08.186: WARN/System.err(8723): SoapFault - faultcode:
'a:DeserializationFailed' faultstring: 'The formatter threw an
exception while trying to deserialize the message: There was an error
while trying to deserialize parameter http://tempuri.org/:loc. The
InnerException message was 'Error in line 1 position 522. Element
'http://tempuri.org/:loc' contains data from a type that maps to the
name 'http://www.w3.org/2001/XMLSchema:Location'. The deserializer has
no knowledge of any type that maps to this name. Consider using a
DataContractResolver or add the type corresponding to 'Location' to
the list of known types - for example, by using the KnownTypeAttribute
attribute or by adding it to the list of known types passed to
DataContractSerializer.'. Please see InnerException for more details.'
faultactor: 'null' detail: org.kxml2.kdom.Node@4053aa28
Update
Ideally, I wouldn't have to move the LocationLibrary classes inside of the main WCF project since that will complicate handling of legacy clients that expect it in a different namespace. However, I did get things to work by moving those classes inside of the main WCF project AND modifying the marshal class in java to this:
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");
}
Now that I got that to work, I just get it working with the LocationLibrary WCF classes how they were (in a separate DLL). So I've modified the marshal class as such:
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");
}
This seems like it should succeed since it produces similar XML to the WP7 version that does work.
WP7 working XML request:
<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>
Android non-working XML request:
<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>
The above Android XML request produces this response error:
08-05 22:51:23.703: WARN/System.err(1382): SoapFault - faultcode: 'a:DeserializationFailed' faultstring: 'The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:loc. The InnerException message was 'Error in line 1 position 590. Element 'http://tempuri.org/:loc' contains data from a type that maps to the name 'http://schemas.datacontract.org/2004/07/LocationLibrary:Location'. The deserializer has no knowledge of any type that maps to this name. Consider using a DataContractResolver or add the type corresponding to 'Location' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.' faultactor: 'null' detail: org.kxml2.kdom.Node@4054bcb8
The only notable difference I see is with the property of "loc" in the working XML request:
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
I don't know if that would make a difference (and it doesn't seem to be referenced with the nested tags). I also don't know how to add that extra namespace without messing up the other one.
Sorry for the long post but I wanted to make sure you have all of the info you need to help :)
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
正如我所期望的,您只是在执行
ToString
但这是错误的。您的位置不会像两个连接值的字符串那样传输。它作为序列化为 XML 的对象进行传输。像这样的东西:Well as I expected you are doing just
ToString
but it is wrong. Your location is not transported like string of two concatenated values. It is transported as object serialized to XML. Something like:感谢拉迪斯拉夫指出了正确的方向!我不确定这里有什么礼节来奖励这一点,但如果有人提出建议,我很乐意遵循。
答案有两个,我可以确认它现在可以与单独 DLL 中的 LocationLibrary 的 Location 类一起使用:
使用我的封送类更改我在 Android 中形成 XML 请求的方式。 writeInstance 方法仅使用 XmlSerializer 类的 .textMethod,在 Location 类上调用 .toString()。我将其更改为利用形成 XML 标记的正确方法(.startTag 和 .endTag 方法):
向 WCF 服务添加一些简单的标记。我将 [DataContract] 添加到 WCF 服务使用的 LocationLibrary 程序集中的 Location 类,并将 [DataMember] 添加到其每个成员:
<前><代码>[数据合同]
公开课地点
{
[数据成员]
公共双纬度 { get;放; }
[数据成员]
公共双经度 { get;放; }
公共新字符串 ToString()
{
返回 latitude.ToString() + "," + longitude.ToString();
}
public bool 等于(位置 loc)
{
返回 this.latitude == loc.latitude && this.longitude == loc.longitude;
}
Thanks to Ladislav for the point in the right direction! I'm not sure what the etiquette is here to reward that, but I'd be glad to follow if someone would advise.
The answer was two-fold, and I can confirm that it now works with the LocationLibrary's Location class in a separate DLL:
Change how I was forming the XML request in Android using my marshaling class. The writeInstance method was only using the .textMethod of the XmlSerializer class, calling .toString() on the Location class. I changed it to utilize the proper way of forming XML tags (.startTag and .endTag methods):
Add some simple tags to the WCF service. I added [DataContract] to the Location class in the LocationLibrary assembly utilized by the WCF service, and I added [DataMember] to each of its members: