摘要 本文介紹了如何在非.NET語言環境中(如Java、Delphi等)調用.NET XML Web Services,并特別針對很多開發者所關心的"如何在Java/Delphi中使用.NET的Web Service返回的DataSet"的問題,提出了兩個解決方案:使用自定義類型數組和使用DataSet.WriteXML()。
-------------------------------------------------------------------
目錄 1. 概述 2. 創建.NET Web Services,返回數據集合 3. 在Java中調用.NET Web Services,處理返回的數據集合 4. 小結
-------------------------------------------------------------------
1.概述 很多正在開發或者打算開發XML Web Services的程序員都問過這樣的一個問題:"我的Web Service返回的結果是一個DataSet類型的對象,但如果我的客戶端不是用.NET寫的(因而沒有內建的DataSet類型),那該如何調用這個Web Service并訪問DataSet中的數據呢?"。 對于這個問題,首先應該說的是:1)在多種語言共存的編程環境下,是不適合使用類似DataSet這種只屬于特定語言的數據類型的。不管是在XML Web Services還是CORBA的環境中,都應該盡量使用簡單數據類型以及簡單數據類型的數組。2)應當很謹慎的決定是否需要通過 Web Service來返回大量數據。由于網絡傳輸的開銷既包括HTTP連接建立的時間,也包括傳送數據的時間,因此需要在減少訪問服務器次數和減少網絡傳輸量之間尋找一個合適的平衡。如非必須,則不適合通過Web Service傳送含有幾十條或者幾百條數據的數據表。 然后,就問題本身而言,.NET Web Services返回的DataSet類型是可以直接被其他非.NET的客戶端解析的,因為即便是DataSet類型的返回值,也會被表達成XML格式再進行傳輸。下面的例子就是一個返回類型為DataSet的Web Method,及其被調用后返回的XML格式數據: [WebMethod] public DataSet GetPersonData() { DataTable table=new DataTable("Person"); table.Columns.Add("Name"); table.Columns.Add("Gender"); table.Rows.Add(new string[2]{"Alice","Female"}); table.Rows.Add(new string[2]{"Bob","Male"}); table.Rows.Add(new string[2]{"Chris","Male"});
DataSet dataset=new DataSet("PersonTable"); dataset.Tables.Add(table); return dataset; }
圖表1. 返回類型為DataSet的Web Method <?xml version="1.0" encoding="utf-8"?> <DataSet xmlns="http://tempuri.org/"> <xs:schema id="PersonTable" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="PersonTable" msdata:IsDataSet="true" msdata:Locale="zh-CN"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="Person"> <xs:complexType> <xs:sequence> <xs:element name="Name" type="xs:string" minOccurs="0" /> <xs:element name="Gender" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema> <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> <PersonTable xmlns=""> <Person diffgr:id="Person1" msdata:rowOrder="0" diffgr:hasChanges="inserted"> <Name>Alice</Name> <Gender>Female</Gender> </Person> <Person diffgr:id="Person2" msdata:rowOrder="1" diffgr:hasChanges="inserted"> <Name>Bob</Name> <Gender>Male</Gender> </Person> <Person diffgr:id="Person3" msdata:rowOrder="2" diffgr:hasChanges="inserted"> <Name>Chris</Name> <Gender>Male</Gender> </Person> </PersonTable> </diffgr:diffgram> </DataSet>
圖表2. 被格式化成XML的DataSet 從上面的例子可以看出,直接使用DataSet作為返回類型,其結果是相當復雜的,其中不但包含了DataSet中的數據,還包括了數據更改的信息,以及DataSet的Schema。雖然有些工具能夠生成一個類似DataSet的客戶端類型,但無論是直接解析復雜的XML還是使用類似DataSet的類,都不夠直接不夠清晰。 解決這個問題的方案有兩種: 1) 用簡單數據類型構造自定義類型,用每一個自定義類型對象封裝數據集中的一行,將自定義類型對象的數組(Array)返回客戶端;由于是用簡單數據類型定義,客戶端能夠完全不變的還原出自定義類型的定義; 2) 用DataSet.WriteXML()方法將數據集中的數據提取成XML格式,并以字符串的形式返回給客戶端,再由客戶端解析XML字符串,還原出數據。由于使用WriteXML()的時候能夠過濾掉冗余信息,返回的內容和圖表2中的內容相比大大簡化了。 下面就將用C#分別實現這兩種方案,并詳細演示如何在Java客戶端中調用并獲取數據。
-------------------------------------------------------------------
2. 創建.NET Web Services,返回數據集合 借助于Visual Studio.NET,只需編寫Web Method本身的代碼,即可非常快速的創建可以實用的Web Services: [WebMethod] public Person[] GetPersons() { Person Alice=new Person("Alice","Female"); Person Bob=new Person("Bob","Male"); Person Chris=new Person("Chris","Female"); Person Dennis=new Person("Dennis","Male");
return new Person[]{Alice,Bob,Chris,Dennis}; }
[WebMethod] public string GetPersonTable() { DataTable table=new DataTable("Person"); table.Columns.Add("Name"); table.Columns.Add("Gender"); table.Rows.Add(new string[2]{"Alice","Female"}); table.Rows.Add(new string[2]{"Bob","Male"}); table.Rows.Add(new string[2]{"Chris","Female"}); table.Rows.Add(new string[2]{"Dennis","Male"}); table.Rows.Add(new string[2]{"Eric","Male"});
DataSet dataset=new DataSet("PersonTable"); dataset.Tables.Add(table);
System.Text.StringBuilder strbuilder=new System.Text.StringBuilder(); StringWriter writer=new StringWriter(strbuilder); dataset.WriteXml(writer,System.Data.XmlWriteMode.IgnoreSchema);
return strbuilder.ToString(); }
圖表3. 用.NET實現的XML Web Services 在上面的代碼中,函數GetPersons()和GetPersonTable()分別對應于"1. 概述"中所提到的兩種解決方案。其中,Person類型就是用于封裝數據集中一行數據的自定義的數據類型: [Serializable] public class Person { public Person() { }
public Person(string name,string gender) { this.Name=name; this.Gender=gender; }
public string Name=""; public string Gender=""; }
圖表4. 自定義類型Person 下面就是在Internet Exploerer里直接調用這兩個Web Method所得到的XML格式的結果: <?xml version="1.0" encoding="utf-8"?> <ArrayOfPerson xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/"> <Person> <Name>Alice</Name> <Gender>Female</Gender> </Person> <Person> <Name>Bob</Name> <Gender>Male</Gender> </Person> <Person> <Name>Chris</Name> <Gender>Female</Gender> </Person> <Person> <Name>Dennis</Name> <Gender>Male</Gender> </Person> </ArrayOfPerson>
圖表5. GetPersons()的返回結果 <PersonTable> <Person> <Name>Alice</Name> <Gender>Female</Gender> </Person> <Person> <Name>Bob</Name> <Gender>Male</Gender> </Person> <Person> <Name>Chris</Name> <Gender>Female</Gender> </Person> <Person> <Name>Dennis</Name> <Gender>Male</Gender> </Person> <Person> <Name>Eric</Name> <Gender>Male</Gender> </Person> </PersonTable>
圖表6. GetPersonTable()的返回的String類型的值 到這里,XML Web Service的創建就已經完成了。.NET中創建的XML Web Services遵循的是統一的行業標準(SOAP、XML等),因此無論使用.NET語言還是使用非.NET語言,都可以調用它。尤其對于GetPersonTable()返回的結果來說,只要用XML Parser作簡單的處理,就能把數據提取出來。下面就將演示如何在Java中完成這一處理。
-------------------------------------------------------------------
3. 在Java中調用.NET Web Services,處理返回的數據集合 編寫客戶端調用Web Services時,最快速簡便做法的是利用工具根據Web Services的WSDL描述生成客戶端代理類(Proxy),而不是直接對SOAP Toolkit進行編程。在.NET Framework中,這個工作可以由wsdl.exe完成。在Java中,可以在JBuilder或者VisualAge Java等Java IDE中安裝一個叫做WASP Developer的免費插件,這個插件也能完成由WSDL描述生成Proxy類的工作。另外,該公司還提供了WASP Server for Java和WASP UDDI,降低了用戶用Java開發Web Services以及建立UDDI服務的難度。 在本文的例子中,用了JBuilder 7+WASP Developer來調用前文中建立的.NET XML Web Services。在調用之前,首先是用WASP Developer生成Proxy類。從圖表7、8中可以看到,生成Proxy的工具已經在Java中也創建了一個Person類:
圖表7. 用WASP Developer生成的Proxy類 package javaclient.service1Soap12Clientstruct;
public class Person { public java.lang.String Name; public java.lang.String Gender; }
/* * Generated by WSDLCompiler, (c) 2002, Systinet Corp. * http://www.systinet.com */
圖表8. WASP Developer創建的Person類 在Proxy類的基礎上,就可以訪問.NET編寫的Web Services并提取數據了。對于GetPersons()來說,由于已經生成了Person類,所以調用代碼非常簡單直接: import org.idoox.webservice.client.WebServiceLookup; import org.idoox.wasp.Context; import javax.swing.*; import javax.swing.table.*;
try{ System.setProperty( "wasp.location","C:\\Home\\Util\\wasp_4.0SP2");
javaclient.service1Soap12Client.Service1Soap service; String serviceURI = "http://localhost/dotNetHost/Service1.asmx"; String wsdlURI = "http://localhost/dotNetHost/Service1.asmx?wsdl";
WebServiceLookup lookup = (WebServiceLookup) Context.getInstance(Context.WEBSERVICE_LOOKUP); service = (javaclient.service1Soap12Client.Service1Soap) lookup.lookup( wsdlURI, javaclient.service1Soap12Client.Service1Soap.class, serviceURI);
Person[] persons= service.GetPersons(new javaclient.service1Soap12Clientstruct.GetPersons()).GetPersonsResult; DefaultTableModel model=new DefaultTableModel(new String[]{"name","gender"},0); for(int i=0;i<persons.length;i++){ model.addRow(new String[]{persons[i].Name.toString(),persons[i].Gender.toString()}); } this.jTable1.setModel(model); } catch(Exception ex) { javax.swing.JOptionPane.showMessageDialog(this,ex.getMessage()); }
圖表9. 在Java中調用GetPersons()的代碼 調用GetPersonTable()并解析XML字符串要相對復雜一些,需要用到XML Parser從圖表6中的XML字符串中提取數據,并逐一顯示在JTable中。下面是從訪問Web Method到解析XML,最后提取數據并顯示的完整代碼,及其運行結果截圖: import javax.swing.*; import javax.swing.table.*;
import org.idoox.webservice.client.WebServiceLookup; import org.idoox.wasp.Context;
import org.w3c.dom.*; import org.apache.xml.serialize.*; import org.apache.xerces.dom.*;
try{ System.setProperty( "wasp.location","C:\\Home\\Util\\wasp_4.0SP2");
javaclient.service1Soap12Client.Service1Soap service; String serviceURI = "http://localhost/dotNetHost/Service1.asmx"; String wsdlURI = "http://localhost/dotNetHost/Service1.asmx?wsdl";
WebServiceLookup lookup = (WebServiceLookup) Context.getInstance(Context.WEBSERVICE_LOOKUP); service = (javaclient.service1Soap12Client.Service1Soap) lookup.lookup( wsdlURI, javaclient.service1Soap12Client.Service1Soap.class, serviceURI);
String result= service.GetPersonTable(new javaclient.service1Soap12Clientstruct.GetPersonTable()).GetPersonTableResult; DefaultTableModel model=new DefaultTableModel(new String[]{"name","gender"},0);
java.io.StringReader reader=new java.io.StringReader(result); org.apache.xerces.parsers.DOMParser parser=new org.apache.xerces.parsers.DOMParser(); parser.parse(new org.xml.sax.InputSource(reader)); org.w3c.dom.Document document=parser.getDocument(); org.w3c.dom.NodeList children=document.getDocumentElement().getChildNodes();
for(int i=0;i<children.getLength();i++){ org.w3c.dom.Node node=children.item(i); if(node.getNodeName().equals("Person")){ org.w3c.dom.NodeList personAttrNodes=node.getChildNodes(); String name="N/A"; String gender="N/A";
for(int j=0;j<personAttrNodes.getLength();j++){ org.w3c.dom.Node attrNode=personAttrNodes.item(j); String attrNodeName=attrNode.getNodeName(); if(attrNodeName.equals("Name")){ name=attrNode.getFirstChild().getNodeValue(); }else if(attrNodeName.equals("Gender")){ gender=attrNode.getFirstChild().getNodeValue(); }else{ continue; } } model.addRow(new String[]{name,gender}); } } this.jTable1.setModel(model); } catch(Exception ex) { javax.swing.JOptionPane.showMessageDialog(this,ex.getMessage()); }
圖表10. 在Java中調用GetPersonTable()的代碼
圖表11.調用GetPersonTable()的運行結果
-------------------------------------------------------------------
4. 小結 從前面的敘述和代碼中可以看出,對于"如何在Java/Delphi中使用.NET的Web Service返回的DataSet"的問題,雖然在非.NET語言環境中直接接受DataSet類型的返回值比較困難,但可以有其他的解決方案。 對于第一種解決方案,也就是采用自定義數據類型,它的優點是客戶端代碼非常簡單,容易編寫,而且容易理解。它的缺點是服務器端需要定義新的類,而且當通過ADO.NET從數據庫中提取數據以后,還要再手工編寫代碼,將DataSet中的數據遍歷一遍,轉存成自定義類型的對象。 對于第二種解決方案,就是用DataSet.WriteXML()的方案,它的優點和缺點正好和第一種方案是互補的。它在客戶端需要編寫較多的代碼,尤其是需要用XML Parser編程,提高了編程的難度。另一方面,它在服務器端比較簡單。如果開發者在原先的純.NET環境(即客戶端也是.NET)中已經開發了返回類型為DataSet的Web Services,那么當開發者希望在客戶端引入非.NET語言時,服務器端只需要簡單的再增加一個Web Method,通過簡單的WriteXML()調用對原有的方法進行簡單的包裝,就能夠適用于非.NET客戶端了。 從更廣闊的角度來看,上面兩種解決方案也可以用在"非.NET服務器+.NET客戶端"的Web Services環境中。在各種非原生(Native)的情況下,盡量使用簡單數據類型,或者使用XML來表達數據,總是上佳的選擇。
|