WCF-WCF中傳輸泛型List 對象(轉)

   在編程時,DataSet、DataTable,以及 List、Dictionary 等 Collection 類型常會使用到。在 .NET Web Service 和 WCF 中,服務器端函數 (Operation) 的返回類型,若爲 DataSet、DataTable,則客戶端可直接調用 (若客戶端程序也是 .NET 的話);但在 WCF 中,VS 2008 默認的配置,並無法傳輸 List<string>、List<自定義類> 等類型的對象,而泛型的 Dictionary 對象卻可以。

[注:Method、Operation 中文都叫做「方法」,但前者是存在 OO 中的類,不存在網絡上;後者存在於 Service 中,公開在網絡上可供其他程序調用。WCF、Data Services 和 RIA Services 中公開在網絡上的函數和方法,都可稱作 Operation。]

關於這點,小弟我查了微軟 MCTS 認證 WCF 3.5 的官方用書 [10]、O'Reilly 的書籍 [11],都未提到如何解決,書中只提到 .NET collections 的 metadata,以 WSDL 在網絡上傳輸時,會以「數組 (array)」的格式呈現。

      Because .NET collections are .NET-specific, WCF cannot expose them in the service metadata, yet because they are so useful, WCF offers dedicated marshaling rules for collections.

Whenever you define a service operation that uses the collection interfaces IEnumerable<T>, IList<T>, or ICollection<T>, the specific collection-type information gets lost in the metadata (WSDL) export, so in terms of how collection types are sent across the wire, they all are represented as arrays, the resulting metadata always uses an array.

開發 WCF 時,若 VS 2008 都用默認配置,則當 WCF 的服務器端函數 (Operation) 的返回類型爲 List<string> 時,實際返回的類型爲 string[] 數組,因此客戶端若仍用 List<string> 的變量去接收和賦值時,在編譯時期,即會發生下圖 1 的轉型錯誤:


圖 1 List 數據結構反序列化後,在客戶端自動變成了數組


WCF 客戶端程序「添加服務引用 (Add Service Reference)」的設置即可處理此種需求。做法如下:

請參閱本帖。當我們的客戶端程序,要引用網絡上既有的 WCF 服務契約時,我們會如下圖 2 般,添加一個 service proxy reference。


圖 2 在 ASP.NET 客戶端程序中引用 WCF Service 


在下圖 3 的「添加服務引用」窗體中,右上方的「前往」按鈕,是要查看網絡上某個 IP 和端口的 WCF Service;右邊的「發現」按鈕,是要查看和此客戶端項目,位於同一個 VS 2008 解決方案裏的 WCF Service。此時我們單擊窗體左下方的「高級」按鈕。


圖 3 在此窗格里輸入正確的元數據交換地址,會自動取得 WCF Service 的 Operation 名稱


如下圖 4,我們在「集合類型」下拉菜單中,把默認的 System.Array 改成我們想使用的 Generic.List 類型;而另一個「字典集合類型」下拉菜單則保持不變,表示此 WCF Service 可在網絡上傳輸泛型的 Dictionary 類型對象。


圖 4 默認的集合類型爲 System.Array


微軟的 VS 默認會這樣設置,可能如同博文所提到的,WCF 的客戶端可能是舊版 .NET 1.x 版的環境,也可能是 Java 或其他各種非微軟的技術平臺,因此 VS 2008 默認選用所有廠商、所有平臺都支持的 Array 數組,作爲網絡傳輸的類型,而非最新版 .NET 平臺特有的 Collection 數據結構。

最後,若用戶端程序要再更改配置,只要如下圖 5 般,在 VS 項目裏既有的 Reference 上,選擇「配置服務引用」即可。


圖 5 在 ASP.NET 客戶端程序中,修改已引用的 WCF Service


以下爲本帖下載示例的代碼。我們在服務器端的 WCF Service,提供三個返回類型分別爲 List<string>、List<自定義類>、Dictionary<string,string> 的函數,給 WCF 客戶端 ASP.NET 程序調用,執行結果如下圖 6。

Server-side/IService.cs System.Collections.Generic;
using System.ServiceModel;
using System.Runtime.Serialization;

[ServiceContract]
[ServiceKnownType(
typeof(Employee))]
public interface IService
{
    [OperationContract]
    
string GetData(int value);

    [OperationContract]
    List
<string> getListString();

    [OperationContract]
    [ServiceKnownType(
typeof(Employee))]
    List
<Employee> getListEmployee();

    [OperationContract]
    Dictionary
<stringstring> getDictionaryString();
}

[DataContract]
[KnownType(
typeof(Employee))]
public class Employee
{
    
public Employee(string name, int age, object oooo)
    {
        
this.name = name;
        
this.age = age;
        
this.oooo = oooo;
    }

    [DataMember]
    
public string name;
    [DataMember]
    
public int age;
    [DataMember]
    
public object oooo;
}
Server-side/Service.cs System.Collections.Generic;
using System.Runtime.Serialization;

public class Service : IService
{
    
public string GetData(int value)
    {
        
return string.Format("You entered: {0}", value);
    }

    
public List<string> getListString()
    {
        List
<string> list1 = new List<string>();
        list1.Add(
"List string 元素一");
        list1.Add(
"List string 元素二");
        
return list1;
    }

    
public List<Employee> getListEmployee()
    {
        List
<Employee> list2 = new List<Employee>();
        list2.Add(
new Employee("吳宇澤"18new object()));
        list2.Add(
new Employee("王大同"20new object()));
        
return list2;
    }

    
public Dictionary<stringstring> getDictionaryString()
    {
        Dictionary
<stringstring> dict1 =  new Dictionary<stringstring>();
        dict1.Add(
"吳宇澤""程序員");
        dict1.Add(
"王大同""業務員");
        
return dict1;
    }
}


Client-side/Default.aspx.cs

public partial class _Default : System.Web.UI.Page 
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        ServiceReference1.ServiceClient prox 
= new ServiceReference1.ServiceClient();

        
/*********** List<string> ***********/
        
//string[] list1 = new string[2];   //未改設置前,Server 返回的 List<string>,Client 只能取得 string 數組
        List<string> list1 = new List<string>();        
        list1 
= prox.getListString();

        Response.Write(list1[
0+ "<br>");
        Response.Write(list1[
1+ "<p>");


        
/*********** List<自定義類> ***********/
        List
<ServiceReference1.Employee> list2 = new List<ServiceReference1.Employee>();
        list2 
= prox.getListEmployee();
        
   在編程時,DataSet、DataTable,以及 List、Dictionary 等 Collection 類型常會使用到。在 .NET Web Service 和 WCF 中,服務器端函數 (Operation) 的返回類型,若爲 DataSet、DataTable,則客戶端可直接調用 (若客戶端程序也是 .NET 的話);但在 WCF 中,VS 2008 默認的配置,並無法傳輸 List<string>、List<自定義類> 等類型的對象,而泛型的 Dictionary 對象卻可以。
[注:Method、Operation 中文都叫做「方法」,但前者是存在 OO 中的類,不存在網絡上;後者存在於 Service 中,公開在網絡上可供其他程序調用。WCF、Data Services 和 RIA Services 中公開在網絡上的函數和方法,都可稱作 Operation。]


關於這點,小弟我查了微軟 MCTS 認證 WCF 3.5 的官方用書 [10]、O'Reilly 的書籍 [11],都未提到如何解決,書中只提到 .NET collections 的 metadata,以 WSDL 在網絡上傳輸時,會以「數組 (array)」的格式呈現。


      Because .NET collections are .NET-specific, WCF cannot expose them in the service metadata, yet because they are so useful, WCF offers dedicated marshaling rules for collections.


Whenever you define a service operation that uses the collection interfaces IEnumerable<T>, IList<T>, or ICollection<T>, the specific collection-type information gets lost in the metadata (WSDL) export, so in terms of how collection types are sent across the wire, they all are represented as arrays, the resulting metadata always uses an array.


開發 WCF 時,若 VS 2008 都用默認配置,則當 WCF 的服務器端函數 (Operation) 的返回類型爲 List<string> 時,實際返回的類型爲 string[] 數組,因此客戶端若仍用 List<string> 的變量去接收和賦值時,在編譯時期,即會發生下圖 1 的轉型錯誤:




圖 1 List 數據結構反序列化後,在客戶端自動變成了數組




WCF 客戶端程序「添加服務引用 (Add Service Reference)」的設置即可處理此種需求。做法如下:


請參閱本帖。當我們的客戶端程序,要引用網絡上既有的 WCF 服務契約時,我們會如下圖 2 般,添加一個 service proxy reference。




圖 2 在 ASP.NET 客戶端程序中引用 WCF Service 




在下圖 3 的「添加服務引用」窗體中,右上方的「前往」按鈕,是要查看網絡上某個 IP 和端口的 WCF Service;右邊的「發現」按鈕,是要查看和此客戶端項目,位於同一個 VS 2008 解決方案裏的 WCF Service。此時我們單擊窗體左下方的「高級」按鈕。




圖 3 在此窗格里輸入正確的元數據交換地址,會自動取得 WCF Service 的 Operation 名稱




如下圖 4,我們在「集合類型」下拉菜單中,把默認的 System.Array 改成我們想使用的 Generic.List 類型;而另一個「字典集合類型」下拉菜單則保持不變,表示此 WCF Service 可在網絡上傳輸泛型的 Dictionary 類型對象。




圖 4 默認的集合類型爲 System.Array




微軟的 VS 默認會這樣設置,可能如同博文所提到的,WCF 的客戶端可能是舊版 .NET 1.x 版的環境,也可能是 Java 或其他各種非微軟的技術平臺,因此 VS 2008 默認選用所有廠商、所有平臺都支持的 Array 數組,作爲網絡傳輸的類型,而非最新版 .NET 平臺特有的 Collection 數據結構。


最後,若用戶端程序要再更改配置,只要如下圖 5 般,在 VS 項目裏既有的 Reference 上,選擇「配置服務引用」即可。




圖 5 在 ASP.NET 客戶端程序中,修改已引用的 WCF Service




以下爲本帖下載示例的代碼。我們在服務器端的 WCF Service,提供三個返回類型分別爲 List<string>、List<自定義類>、Dictionary<string,string> 的函數,給 WCF 客戶端 ASP.NET 程序調用,執行結果如下圖 6。


Server-side/IService.cs System.Collections.Generic;
using System.ServiceModel;
using System.Runtime.Serialization;


[ServiceContract]
[ServiceKnownType(typeof(Employee))]
public interface IService
{
    [OperationContract]
    string GetData(int value);


    [OperationContract]
    List<string> getListString();


    [OperationContract]
    [ServiceKnownType(typeof(Employee))]
    List<Employee> getListEmployee();


    [OperationContract]
    Dictionary<string, string> getDictionaryString();
}


[DataContract]
[KnownType(typeof(Employee))]
public class Employee
{
    public Employee(string name, int age, object oooo)
    {
        this.name = name;
        this.age = age;
        this.oooo = oooo;
    }


    [DataMember]
    public string name;
    [DataMember]
    public int age;
    [DataMember]
    public object oooo;
}
Server-side/Service.cs System.Collections.Generic;
using System.Runtime.Serialization;


public class Service : IService
{
    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }


    public List<string> getListString()
    {
        List<string> list1 = new List<string>();
        list1.Add("List string 元素一");
        list1.Add("List string 元素二");
        return list1;
    }


    public List<Employee> getListEmployee()
    {
        List<Employee> list2 = new List<Employee>();
        list2.Add(new Employee("吳宇澤", 18, new object()));
        list2.Add(new Employee("王大同", 20, new object()));
        return list2;
    }


    public Dictionary<string, string> getDictionaryString()
    {
        Dictionary<string, string> dict1 =  new Dictionary<string, string>();
        dict1.Add("吳宇澤", "程序員");
        dict1.Add("王大同", "業務員");
        return dict1;
    }
}


Client-side/Default.aspx.cs


public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ServiceReference1.ServiceClient prox = new ServiceReference1.ServiceClient();


        /*********** List<string> ***********/
        //string[] list1 = new string[2];   //未改設置前,Server 返回的 List<string>,Client 只能取得 string 數組
        List<string> list1 = new List<string>();        
        list1 = prox.getListString();


        Response.Write(list1[0] + "<br>");
        Response.Write(list1[1] + "<p>");




        /*********** List<自定義類> ***********/
        List<ServiceReference1.Employee> list2 = new List<ServiceReference1.Employee>();
        list2 = prox.getListEmployee();
        
        Response.Write(list2[0].name + "<br>");
        Response.Write(list2[0].age + "<br>");
        Response.Write(list2[0].oooo + "<p>");      //object 類型




        /*********** Dictionary<string,string> ***********/
        Dictionary<string, string> dict1 = new Dictionary<string, string>();
        dict1 = prox.getDictionaryString();


        foreach (KeyValuePair<string, string> kvp in dict1)
        {
            Response.Write(kvp.Key + ", " + kvp.Value + "<br>");
        }
    }
}


圖 6 本帖示例執行結果,從 WCF Service 返回 List<string>、List<自定義類>、泛型 Dictionary 三種類型的變量
        Response.Write(list2[
0].age + "<br>");
        Response.Write(list2[
0].oooo + "<p>");      //object 類型


        
/*********** Dictionary<string,string> ***********/
        Dictionary
<stringstring> dict1 = new Dictionary<stringstring>();
        dict1 
= prox.getDictionaryString();

        
foreach (KeyValuePair<stringstring> kvp in dict1)
        {
            Response.Write(kvp.Key 
+ "" + kvp.Value + "<br>");
        }
    }
}


圖 6 本帖示例執行結果,從 WCF Service 返回 List<string>、List<自定義類>、泛型 Dictionary 三種類型的變量

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章