65.9K
CodeProject 正在变化。 阅读更多。
Home

通过 kSOAP 库消费 .NET Web 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (9投票s)

2008 年 9 月 13 日

CPOL

6分钟阅读

viewsIcon

140907

关于如何使用 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 = &quot;Event&quot;+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 = &quot;http://tempuri.org/encodedTypes&quot;;
    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 = &quot;http://tempuri.org/MethodName&quot;;
private static final String METHOD_NAME = &quot;MethodName&quot;;
private static final String NAMESPACE = &quot;http://tempuri.org/&quot;;
private static final String URL = &quot;http://192.168.2.200/Service.asmx&quot;;

以上所有数据都可以从 Web 服务定义 (WSDL) 中检索。METHOD_NAME 是我们在 Web 服务中定义的方法的名称。NAMESPACE 是 Web 服务的命名空间;默认是 'http://tempuri.org/',也可以是您自己组织的特定命名空间。SOAP_ACTIONNAMESPACE 后跟 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(&quot;i&quot;);
pi.setValue(5);
request.addProperty(pi);

第二个示例 Web 服务方法是 GetGivenDate(),它接收一个 DateTime 参数并返回相同的值。我们需要为 kSOAP 中未标准化的简单类型添加封送处理,我将在下一部分讨论。

PropertyInfo pi = new PropertyInfo();
pi.setName(&quot;date&quot;);
pi.setValue(new Date(System.currentTimeMillis()));
request.addProperty(pi);

另一个示例是一个复杂类型,它将作为参数发送到 GetGivenEvent() Web 服务方法,该方法将返回相同的 Event 对象。由于 Event 类型是复杂的,我们将类型设置为 Event 类。

PropertyInfo pi = new PropertyInfo();
pi.setName(&quot;evnt&quot;);
Event e = new Event();
e.setName(&quot;Antalya, Turkey&quot;);
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, &quot;dateTime&quot;, MarshalDate.DATE_CLASS, this);
}

在调用 Web 服务之前,应将适当的封送处理注册到 Envelope;否则,请求或响应将导致解析错误。

Marshal dateMarshal = new bilgiciftligi.serialization.MarshalDate();
dateMarshal.register(envelope);

添加必要的映射

映射对于复杂类型对象解析是必需的。这个想法与封送处理相似,但复杂类型对象应实现 KvmSerializable 及其所需的解析方法。映射应在 Web 服务调用之前添加。

envelope.addMapping(BaseObject.NAMESPACE, &quot;Event&quot;, new Event().getClass());

调用 Web 服务方法

设置好参数、封送处理和映射后,我们就可以调用 Web 服务了。标准 HttpTranport 类用于调用,但对于 Android OS,我们更改了调用的某些部分进行跟踪;这就是为什么它被称为 AnroidHttpTransport。但是,主要思想是相同的。提供 SOAP_ACTIONURL 和 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 日。
© . All rights reserved.