从 Android 应用程序调用 ASP.NET Web 服务 (ASMX),最简单的方法






4.89/5 (28投票s)
如何使用 KSOAP 库以简单高效的方式从 Android 调用 ASP.NET Web 服务。
引言
互联网上已经有许多关于这个主题的教程。但在阅读其中一些教程时,我意识到它们要么对普通人来说太复杂,要么没有得到 proper 的解释。这是 Android 应用的一个重要方面,因为它可以轻松地利用现有的业务逻辑,而无需重写它们。此应用程序的另一个重要方面是,您可以轻松使用全局数据库来存储数据,这些数据可以被不同的手机共享,而不是像通常那样使用 Android 内置的 SQLLite 数据库。
Using the Code
首先,让我们来看一个简单的 Web 服务。
<%@ WebService language="C#" class="MyLocal" %>
using System;
using System.Web.Services;
using System.Xml.Serialization;
public class MyLocal {
[WebMethod]
public int Add(int a, int b) {
return a + b;
}
}
创建此 Web 服务时,它会要求提供一个命名空间,这里的命名空间是 www.tempura.org(几乎是默认命名空间,我没有更改!)。
当您在浏览器中打开此 Web 服务时,您会看到一个窗口,如下图所示。
我已将其标记以显示如何获取命名空间名称。它还将显示方法列表,您无法测试。那么如何知道参数和返回类型呢?单击该方法,然后查看下图。
现在您知道要调用哪个函数、其参数、返回类型和命名空间(当然还有 URL)。
要调用此方法,您需要 ksoap
库,您可以在此处下载。
将下载的 zip 文件复制到任何合适的位置。这是一个外部 jar 文件,您需要将其包含在您的 Android 项目中。
启动一个 Android 项目,然后选择 Android API。在工作区中右键单击项目节点,属性 -> Java 构建路径 -> 库 -> 添加外部 jar。
浏览并选择您的 ksoap jar 文件。
现在我们要做的就是编写一个可以调用 Web 服务并返回结果的方法。请记住,如果您尝试从主活动线程执行任何套接字操作,Android 会抛出异常。因此,最好编写一个单独的类并隔离 soap 相关的函数。
现在让我们来理解这个类的逻辑。
package my.MySOAPCallActivity.namespace;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.PropertyInfo;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
public class CallSoap
{
public final String SOAP_ACTION = "http://tempuri.org/Add";
public final String OPERATION_NAME = "Add";
public final String WSDL_TARGET_NAMESPACE = "http://tempuri.org/";
public final String SOAP_ADDRESS = "http://grasshoppernetwork.com/NewFile.asmx";
public CallSoap()
{
}
public String Call(int a,int b)
{
SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE,OPERATION_NAME);
PropertyInfo pi=new PropertyInfo();
pi.setName("a");
pi.setValue(a);
pi.setType(Integer.class);
request.addProperty(pi);
pi=new PropertyInfo();
pi.setName("b");
pi.setValue(b);
pi.setType(Integer.class);
request.addProperty(pi);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
HttpTransportSE httpTransport = new HttpTransportSE(SOAP_ADDRESS);
Object response=null;
try
{
httpTransport.call(SOAP_ACTION, envelope);
response = envelope.getResponse();
}
catch (Exception exception)
{
response=exception.toString();
}
return response.toString();
}
}
- 首先
SOAP_ACTION = 命名空间
,如数字 1 + 函数名所示; OPERATION_NAME = Web 方法的名称
;WSDL_TARGET_NAMESPACE = Web 服务的命名空间
;SOAP_ADDRESS = Web 服务的绝对 URL
;
SOAP 基于请求-响应对工作。所以首先您需要构建一个可以调用 Web 服务的请求对象。
SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE,OPERATION_NAME);
现在,您打算调用的操作或方法有一些参数,您需要将它们附加到请求对象。这是通过 PropertyInfo 实例 pi
完成的。这里需要注意的重要一点是,您在 setName()
方法中使用的名称必须是您在上面数字中看到的属性的确切名称,而在 setType()
中,必须指定变量的数据类型。使用 addProperty()
,添加所有参数。
使用 setValue()
方法,为属性设置值。
PropertyInfo pi=new PropertyInfo();
pi.setName("a");
pi.setValue(a);
pi.setType(Integer.class);
request.addProperty(pi);
创建一个序列化的信封,该信封将用于承载 SOAP 主体的参数,并通过 HttpTransportSE
方法调用该方法。
现在,您已经掌握了调用 Web 方法并获取结果的技术。为简单起见,我们创建了一个简单的 Android GUI,其中包含两个 EditText
和一个 Button
。
请参阅下面的 main.xml 代码。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<EditText
android:id="@+id/editText1"
android:layout_width="230dp"
android:layout_height="wrap_content" >
<requestFocus />
</EditText>
<EditText
android:id="@+id/editText2"
android:layout_width="232dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button1"
android:layout_width="229dp"
android:layout_height="wrap_content"
android:text="@string/btnStr" />
</LinearLayout>
我们希望从 activity
类中的按钮单击事件调用该方法。
请看下面的代码
package my.MySOAPCallActivity.namespace;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class SimpleAsmxSOAPCallActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b1=(Button)findViewById(R.id.button1);
final AlertDialog ad=new AlertDialog.Builder(this).create();
b1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
CallSoap cs=new CallSoap();
try
{
EditText ed1=(EditText)findViewById(R.id.editText1);
EditText ed2=(EditText)findViewById(R.id.editText2);
int a=Integer.parseInt(ed1.getText().toString());
int b=Integer.parseInt(ed2.getText().toString());
ad.setTitle("OUTPUT OF ADD of "+a+" and "+b);
String resp=cs.Call(a, b);
ad.setMessage(resp);
}catch(Exception ex)
{
ad.setTitle("Error!");
ad.setMessage(ex.toString());
}
ad.show(); }
});
}
}
现在您要做的就是从 EditTexts
获取 a
和 b
的值,并将这些值传递给调用方法。调用方法会返回 Web 方法的结果。
String resp=cs.Call(a, b);
ad.setMessage(resp);
一旦您成功运行并启动了它,很可能会遇到诸如 android.os.NetworkOnMainThreadException
之类的错误。
这是因为 Android 不允许您从主线程运行套接字相关操作,正如我们已经讨论过的。所以您需要运行一个线程或创建一个线程,您可以在其中执行这些操作。您可以轻松地将 CallSoap
类建模为实现 runnable 的类并获取相关内容。但我希望调用部分是一个单独的实体。所以我只是创建了一个单独的线程类来调用 CallSoap
方法。它提供了一个良好的分层实现,以便调用执行 SOAP 相关活动的函数的线程与主线程完全不同。
但现在的问题是,您的活动线程和网络线程处于多线程操作中,并且有可能您的主线程在获得 SOAP 操作结果之前就结束了。所以我用了一种原始的方法来等待结果到达然后使用它。
调用者类。
public class Caller extends Thread
{
public CallSoap cs;
public int a,b;
public void run(){
try{
cs=new CallSoap();
String resp=cs.Call(a, b);
MySOAPCallActivity.rslt=resp;
}catch(Exception ex)
{MySOAPCallActivity.rslt=ex.toString();}
}
}
修改活动线程中按钮的 OnClick
方法,该方法通过这个简单的调用者类调用 SOAP。
package my.MySOAPCallActivity.namespace;
import android.app.Activity;
import android.os.Bundle;
import android.app.AlertDialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class MySOAPCallActivity extends Activity {
public static String rslt=""; /** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b1=(Button)findViewById(R.id.button1);
final AlertDialog ad=new AlertDialog.Builder(this).create();
b1.setOnClickListener(new OnClickListener() {
@Override public void onClick(View arg0) {
// TODO Auto-generated method stub
try
{
EditText ed1=(EditText)findViewById(R.id.editText1);
EditText ed2=(EditText)findViewById(R.id.editText2);
int a=Integer.parseInt(ed1.getText().toString());
int b=Integer.parseInt(ed2.getText().toString());
rslt="START";
Caller c=new Caller(); c.a=a;
c.b=b; c.ad=ad;
c.join(); c.start();
while(rslt=="START") {
try {
Thread.sleep(10);
}catch(Exception ex) {
}
}
ad.setTitle("RESULT OF ADD of "+a+" and "+b);
ad.setMessage(rslt);
}catch(Exception ex) {
ad.setTitle("Error!"); ad.setMessage(ex.toString());
}
ad.show();
} });
}
}
最后,您会看到如下结果。
关注点
虽然返回像 Employee
或 Person
这样的 Complex
类并不像这里解释的技术那么简单。它们需要另一个可序列化的类。但如果您喜欢简单,可以将类的属性值嵌入到单个 string
中,例如“Name#Age#Phone
”。
其中“#
”是分隔符。请记住,DataReader
是不可序列化的。所以您的 Web 方法必须将 DataReader
转换为 string
格式,在收到结果后,您可以使用简单的 string
拆分方法分离字段。
更新:如何本地运行服务
与我之前认为的“无法实现”的看法不同,我们的朋友 Motaz 提出了一个很好的技巧和解决方案。将其包含在主文章中,以便任何阅读的人都能快速找到答案。
you just need to set the ip address of the service to 10.0.2.2 and the URL of the service would be :
http://10.0.2.2/service/WSGetCustomerCountryWise.asmx[^]
where
service is your web service alias name in the IIS server
WSGetCustomerCountryWise is the name of the web service