通过 kSOAP 库消费 .NET Web 服务
关于如何使用 kSOAP 库调用 .NET 定义的 Web Service 方法的简单示例。
引言
正如你们许多人所知,Web 服务是建立远距离独立平台之间通信的绝佳方式。
创建 .NET Web 服务非常简单,并且在任何支持 .NET Framework 的系统上使用它们更加容易。然而,当涉及非 .NET Framework 系统时,可能会出现一些互操作性问题。为了增强 Web 服务的互操作性,WS-I 发布了各种规范。本文将使用服务器端 .NET Web 服务广泛使用的 SOAP (Simple Object Access Protocol) 规范的 RPC (Remote Procedure Call) 消息传递模式。
在客户端,将使用安装了 Java 的 Android OS 的移动设备。对于 Android OS,我们需要一个专门为受限 Java 环境设计的 Web 服务客户端库,kSOAP 以开源的方式为我们提供了这项功能!
本文的主要目的是演示如何编写一个 .NET Web 服务,该服务可以通过 kSOAP 库与 Android OS 进行通信。
所需技术
本文使用的软件版本如下
- SOAP v 1.1
- kSOAP v 2.1.1 (带 WSDL 补丁)
- Microsoft .NET Framework 2.0 SDK
- Sun Microsystems Java Development Kit 1.6.0
- Android SDK m5-rc15 for Linux-x86
(在撰写本文时,所有这些软件都可以从互联网上免费下载。)
Using the Code
.NET 中的 Web 服务定义
Web 服务的源代码文件如下。重要的是所有方法名都必须是唯一的,即使方法签名不同。SOAPAction
值在命名空间内必须是唯一的。
(为简洁起见,未显示导入。)
[WebService(Namespace = "http://tempuri.org/")]
public class Service : System.Web.Services.WebService
{
public Service(){}
[SoapRpcMethod(), WebMethod]
public int GetGivenInt(int i)
{
return i;
}
[SoapRpcMethod(), WebMethod]
public Event GetGivenEvent(Event evnt)
{
return evnt;
}
[SoapRpcMethod(), WebMethod]
public int[] GetGivenIntArray(int[] array)
{
return array;
}
[SoapRpcMethod(), WebMethod]
public DateTime GetGivenDate(DateTime date)
{
return date;
}
[SoapRpcMethod, WebMethod]
public Event[] GetOnGoingEvents()
{
Event[] arrayToReturn = new Event[100];
Event e ;
for(int i = 0; i < 100; i++)
{
e = new Event();
e.Name = "Event"+i;
e.StartDate = new DateTime(2008, 6, 12);
e.EndDate = new DateTime(2008, 6, 20);
e.SubscriptionStartDate = new DateTime(2008, 3, 12);
e.SubscriptionEndDate = new DateTime(2008, 4, 12);
arrayToReturn[i] = e;
}
return arrayToReturn;
}
// Custom defined inner class to represent complex type.
public class Event
{
// Generate properties for
// String Name,
// int Key,
// DateTime SubscriptionStartDate, SubscriptionEndDate, StartDate, EndDate
}
客户端复杂类型定义 (Java)
(为简洁起见,未显示导入。)
public abstract class BaseObject implements KvmSerializable {
public static final String NAMESPACE = "http://tempuri.org/encodedTypes";
public BaseObject() {
super();
}
}
public class Event extends BaseObject
{
public static Class EVENT_CLASS = new Event().getClass();
private String name;
private int key;
private Date subscriptionStartDate;
private Date subscriptionEndDate;
private Date startDate;
private Date endDate;
@Override
public Object getProperty(int index)
{
switch (index) {
case 0:
return name;
case 1:
return key;
case 2:
return subscriptionStartDate;
case 3:
return subscriptionEndDate;
case 4:
return startDate;
case 5:
return endDate;
default:
return null;
}
}
@Override
public int getPropertyCount() {
return 6;
}
@Override
public void getPropertyInfo(int index, Hashtable properties, PropertyInfo info) {
switch (index)
{
case 0:
info.type = PropertyInfo.STRING_CLASS;
info.name = "Name";
break;
case 1:
info.type = PropertyInfo.INTEGER_CLASS;
info.name = "Key";
break;
case 2:
info.type = MarshalDate.DATE_CLASS;
info.name = "SubscriptionStartDate";
break;
case 3:
info.type = MarshalDate.DATE_CLASS;
info.name = "SubscriptionEndDate";
break;
case 4:
info.type = MarshalDate.DATE_CLASS;
info.name = "StartDate";
break;
case 5:
info.type = MarshalDate.DATE_CLASS;
info.name = "EndDate";
break;
default:
break;
}
}
@Override
public void setProperty(int index, Object value) {
switch (index) {
case 0:
name = value.toString();
break;
case 1:
key = Integer.parseInt(value.toString());
break;
case 2:
subscriptionStartDate = (Date)value;
break;
case 3:
subscriptionEndDate = (Date)value;
break;
case 4:
startDate = (Date)value;
break;
case 5:
endDate = (Date)value;
break;
default:
break;
}
}
// Getters and setters are omitted for brevity.
}
从客户端定义 Web 服务属性
定义调用 SOAP RPC Web 服务方法的参数
private static final String SOAP_ACTION = "http://tempuri.org/MethodName";
private static final String METHOD_NAME = "MethodName";
private static final String NAMESPACE = "http://tempuri.org/";
private static final String URL = "http://192.168.2.200/Service.asmx";
以上所有数据都可以从 Web 服务定义 (WSDL) 中检索。METHOD_NAME
是我们在 Web 服务中定义的方法的名称。NAMESPACE
是 Web 服务的命名空间;默认是 'http://tempuri.org/',也可以是您自己组织的特定命名空间。SOAP_ACTION
是 NAMESPACE
后跟 METHOD_NAME
的直接连接。URL
是 Web 服务可访问的位置。如果连接是通过 SSL,您需要在此处指定它 (例如,https)。
设置要传递的参数
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
定义好 SoapObject
后,我们可以通过 addProperty()
方法添加将通过 Web 服务方法发送的参数。
如果 Web 服务方法不需要任何参数,则无需添加任何属性。如果需要一个或多个参数,传递参数时重要的是 PropertyInfo
的名称和类型应与原始 Web 服务方法的参数名称匹配。
第一个示例是 GetGivenInt()
Web 服务方法,它接收一个原始的 int
参数并返回相同的原始整数值。
PropertyInfo pi = new PropertyInfo();
pi.setName("i");
pi.setValue(5);
request.addProperty(pi);
第二个示例 Web 服务方法是 GetGivenDate()
,它接收一个 DateTime
参数并返回相同的值。我们需要为 kSOAP 中未标准化的简单类型添加封送处理,我将在下一部分讨论。
PropertyInfo pi = new PropertyInfo();
pi.setName("date");
pi.setValue(new Date(System.currentTimeMillis()));
request.addProperty(pi);
另一个示例是一个复杂类型,它将作为参数发送到 GetGivenEvent()
Web 服务方法,该方法将返回相同的 Event
对象。由于 Event
类型是复杂的,我们将类型设置为 Event
类。
PropertyInfo pi = new PropertyInfo();
pi.setName("evnt");
Event e = new Event();
e.setName("Antalya, Turkey");
e.setKey(1);
e.setEndDate(new Date(EndDate.timeMillis()));
e.setStartDate(new Date(StartDate.timeMillis()));
e.setSubscriptionEndDate(new Date(SubscriptionEndDate.timeMillis()));
e.setSubscriptionStartDate(new Date(SubscriptionStartDate.timeMillis()));
pi.setValue(e);
pi.setType(Event.EVENT_CLASS);
request.addProperty(pi);
设置 Envelope
SoapSerializationEnvelope envelope =
new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
在此示例中,使用了 SOAP 版本 1.1,但 .NET Framework 支持版本 1.1 和 1.2。从 kSOAP2 调用 .NET Web 服务时,需要将 dotNet
标志设置为 true。最后,将 SoapObject
实例 'request' 分配给 SOAP 调用到 Envelope 的出站消息。
添加必要的封送处理
即使是简单的类型,如果它们默认未被 kSOAP 库定义,也需要进行封送处理。我们将要注册封送处理的类应实现 Marshal
接口,该接口有三个重要方法。
当检索到响应时,readInstance()
方法用于将 XML 字符串解析为简单类型。这里是 MarsalDate
类的示例;stringToDate()
方法应更改为您的定义的类型解析方法。
public Object readInstance(XmlPullParser parser, String namespace, String name,
PropertyInfo expected) throws IOException, XmlPullParserException {
return IsoDate.stringToDate(parser.nextText(), IsoDate.DATE_TIME);
}
当发送请求时,writeInstance()
方法用于将简单类型解析为 XML 字符串。这里给出的示例来自 MarsalDate
类;对于其他类型,解析方法应由您自己实现。
public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
writer.text(IsoDate.dateToString((Date) obj, IsoDate.DATE_TIME));
}
register()
方法告诉 Envelope,所有适合该命名空间和名称的 XML 元素都将由给定的类进行封送处理。
public void register(SoapSerializationEnvelope cm) {
cm.addMapping(cm.xsd, "dateTime", MarshalDate.DATE_CLASS, this);
}
在调用 Web 服务之前,应将适当的封送处理注册到 Envelope;否则,请求或响应将导致解析错误。
Marshal dateMarshal = new bilgiciftligi.serialization.MarshalDate();
dateMarshal.register(envelope);
添加必要的映射
映射对于复杂类型对象解析是必需的。这个想法与封送处理相似,但复杂类型对象应实现 KvmSerializable
及其所需的解析方法。映射应在 Web 服务调用之前添加。
envelope.addMapping(BaseObject.NAMESPACE, "Event", new Event().getClass());
调用 Web 服务方法
设置好参数、封送处理和映射后,我们就可以调用 Web 服务了。标准 HttpTranport
类用于调用,但对于 Android OS,我们更改了调用的某些部分进行跟踪;这就是为什么它被称为 AnroidHttpTransport
。但是,主要思想是相同的。提供 SOAP_ACTION
、URL
和 Envelope 到调用就足够了。
AndroidHttpTransport androidHttpTransport = new AndroidHttpTransport(URL);
androidHttpTransport.call(SOAP_ACTION, envelope);
解析响应
如果 Envelope 中的响应不是数组,在获取响应后,可以直接将其强制转换为所需的类型。
int receivedInt = (Integer)envelope.getResponse();
Log.v("BILGICIFTLIGI", receivedInt.toString());
对于复杂类型和未定义的简单类型,适用相同的规则。
Date receivedDate = (Date)envelope.getResponse();
Event receivedEvent = (Event)envelope.getResponse();
如果 Envelope 中的响应是任何类型 (复杂或原始) 的数组,则应先将其强制转换为 Vector
。通过利用 Generics 的强大功能,我们可以定义一个包含所需类型的 Vector
。
Vector<Event> receivedEvents = (Vector<Event>)envelope.getResponse();
if(receivedEvents != null)
{
for(Event curEvent : receivedEvents)
{
Log.v("BILGICIFTLIGI", curEvent.toString());
}
}
跟踪请求和响应
创建 Web 服务请求调用或获取响应时,典型的挑战是跟踪进行中的数据。所有重要数据都在网络接口之间移动,并且应用程序中抛出的异常有时可能没有太大帮助。因此,需要一个包嗅探器应用程序来跟踪所有步骤,确保不遗漏任何内容。Wireshark (以前称为 Ethereal) 是最好的网络协议分析器之一,可以帮助您解决这个问题。Wireshark 项目是开源的,并且可以免费获得二进制文件。我敢肯定,在跟踪过程中,它将是您最好的伙伴。
历史
- 首次提交创建于 2008 年 9 月 14 日。