複雜類型
若想傳遞某些複雜的資料類型,比如某個自訂類型或結構,那麼我們要顯式告知ASP.NET AJAX非同步通訊層為該伺服器端類型產生相應的用戶端JavaScript類型。
如下C#代碼描述的Employee類就可以看作一個“複雜類型”,讓我們以這個Employee類為例,說明在ASP.NET AJAX非同步通訊層傳遞複雜類型的方法:
public class Employee
{
private int m_id;
public int Id
{
get { return m_id; }
set { m_id = value; }
}
private string m_name;
public string Name
{
get { return m_name; }
set { m_name = value; }
}
private string m_email;
public string Email
{
get { return m_email; }
set { m_email = value; }
}
private int m_salary;
public int Salary
{
get { return m_salary; }
set { m_salary = value; }
}
public Employee()
{
}
public Employee(int id, string name, string email, int salary)
{
m_id = id;
m_name = name;
m_email = email;
m_salary = salary;
}
}
可以看到,該Employee類共包含4個公有屬性:Id、Name、Email和Salary,分別表示員工的編號、姓名、電子郵件地址以及工資。除了這4個公有屬性之外,Employee類還提供了兩個建構函式。但並沒有定義任何方法(即所謂的“退化類”。“退化類”與結構體類似,用來表示結構化的資料)。本小節以及隨後的若干小節都將使用該Employee類,為加深印象,我們在這裡給出了Employee類的類圖,3-16所示。
注意:若想讓ASP.NET AJAX非同步通訊層為伺服器端類型自動產生相應的用戶端版本,那麼該類型一定要提供一個無參數的建構函式,且該類所有的公有屬性都應該提供getter和setter訪問器。另外,在根據伺服器端類型自動產生的用戶端版本類型中,只有原伺服器端類型的公有屬性會被保留下來,原類型的方法和私人欄位都將不會映射到用戶端對象中(公有欄位會被映射到用戶端對象中)。
圖3-16 Employee類的類圖
瞭解了Employee類之後,讓我們通過一個樣本程式介紹如何在ASP.NET AJAX非同步通訊層傳遞該Employee類型。首先是伺服器端的Web Service代碼,其中定義了兩個方法:CreateNewEmployee()和SaveEmployee(),分別將返回或接受一個Employee對象:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[GenerateScriptType(typeof(Employee))]
[ScriptService]
public class PeopleManagementService : System.Web.Services.WebService
{
[WebMethod]
public Employee CreateNewEmployee()
{
return new Employee(0, string.Empty, string.Empty, 0);
}
[WebMethod]
public bool SaveEmployee(Employee em)
{
// 儲存到資料庫中...
return true;
}
}
CreateNewEmployee()和SaveEmployee()方法都不能完成什麼具體的工作,僅僅起到示範功能。PeopleManagementService所應用的[GenerateScriptType(typeof(Employee))]屬性(上述代碼中的粗體部分)才是完成程式功能的關鍵之處,該屬性定義於System.Web.Script.Services命名空間中。若我們希望讓ASP.NET AJAX非同步通訊層為某一複雜類型產生用戶端的JavaScript對應類型,以便完成伺服器和用戶端之間的通訊,則應該用GenerateScriptType屬性顯式聲明。
注意:實際上,對於“最外層”(即在Web Service方法的參數或傳回值出現過)的複雜類型,[GenerateScriptType(typeof([TypeName]))]屬性並不是強制要求的。但若是最外層複雜類型中還嵌套有內層的複雜類型,比如Employee中有個屬性指向的是另一個複雜類型——Manager(這個Manager類型即為嵌套複雜類型),那麼我們就一定要顯式為Web Service添加[GenerateScriptType(typeof([Manager]))]屬性。或者某Web Service方法的參數或傳回值為List<Employee>(將在稍後介紹),那麼我們也要為Web Service添加[GenerateScriptType(typeof([Employee]))]屬性。
正是因為存在著這樣的複雜性,所以本書建議對於任何出現過的複雜類型,均在Web Service類中用[GenerateScriptType(typeof([TypeName]))]屬性標出,以減少由不小心導致的遺漏所帶來異常的可能性。另外,[GenerateScriptType(typeof([TypeName]))]屬性也可以添加到Web Service方法上,效果與添加到Web Service類上相同。
然後在ASP.NET頁面中添加ScriptManager控制項以及相應的Web Service的引用:
<asp:ScriptManager ID="sm" runat="server">
<Services>
<asp:ServiceReference Path="Services/PeopleManagementService.asmx" />
</Services>
</asp:ScriptManager>
頁面的UI部分代碼非常直觀:
<label for="tbId">Id</label><input id="tbId" type="text" /><br />
<label for="tbName">Name</label><input id="tbName" type="text" /><br />
<label for="tbEmail">Email</label><input id="tbEmail" type="text" /><br />
<label for="tbSalary">Salary</label><input id="tbSalary" type="text" /><br />
<input id="btnNew" type="button" value="Create New"
onclick="return btnNew_onclick()" />
<input id="btnSave" type="button" value="Save"
onclick="return btnSave_onclick()" />
前面四個<input />分別將用來顯示某個Employee對象的Id、Name、Email以及Salary屬性,並提供了對這些屬性的編輯功能。後面兩個<input />作為按鈕,將分別調用前面Web Service中定義的CreateNewEmployee()和SaveEmployee()兩個方法的用戶端代理。
btnNew_onclick()事件處理函數以及相應的回呼函數如下:
function btnNew_onclick() {
PeopleManagementService.CreateNewEmployee(onCreated);
}
function onCreated(result) {
employeeInEditing = result;
$get("tbId").value = employeeInEditing.Id;
$get("tbName").value = employeeInEditing.Name;
$get("tbEmail").value = employeeInEditing.Email;
$get("tbSalary").value = employeeInEditing.Salary;
}
注意回呼函數中的result對象即ASP.NET AJAX非同步通訊層為C# Employee類自動產生的用戶端類型。圖3-17顯示了在Visual Studio調試器中察看到的result對象(類型為Employee)的結構。
圖3-17 用戶端Employee類型的結構
提示:在實際開發中,我們也可以直接使用用戶端Employee類的建構函式,在用戶端直接構造出一個Employee對象,而不必總是從伺服器端取得程式中需要的新對象,以便提高應用程式的整體效能:
var em = new Employee();
上面代碼中的employeeInEditing為全域變數,用來儲存當前正被編輯的用戶端Employee對象,定義在這兩個函數之外:
var employeeInEditing = null;
回呼函數將伺服器端新建立的Employee對象的4個屬性值分別顯示在頁面中的4個文字框中,供使用者編輯。3-18所示。
圖3-18 點擊“Create New”按鈕之後,程式將從伺服器端取得一個新的Employee對象
編輯好這4個條目之後,點擊“Save”按鈕,將觸發btnSave_onclick()事件處理函數。btnSave_onclick()事件處理函數以及相應的回呼函數如下:
function btnSave_onclick() {
employeeInEditing.Id = $get("tbId").value;
employeeInEditing.Name = $get("tbName").value;
employeeInEditing.Email = $get("tbEmail").value;
employeeInEditing.Salary = $get("tbSalary").value;
PeopleManagementService.SaveEmployee(employeeInEditing, onSaved);
}
function onSaved(result) {
if (result) {
alert("Employee Saved!");
}
}
其中只是簡單地更新了用戶端Employee對象的4個屬性,然後通過ASP.NET AJAX非同步通訊層將其傳遞到伺服器端。圖3-19顯示了伺服器端SaveEmployee()方法接收到的從用戶端傳入的Employee對象。
圖3-19 SaveEmployee()方法接收到的從用戶端傳入的Employee對象
在伺服器端SaveEmployee()方法成功返回後,用戶端onSaved()回呼函數將在頁面中彈出提示對話方塊。3-20所示。
圖3-20 點擊“Save”按鈕儲存該Employee對象之後的程式介面
有些時候我們並不希望將某個伺服器端類型中所有的屬性均暴露給用戶端對象。比如Employee類中的Salary屬性,出於某些安全或隱私方面的考慮,我們或許想讓非同步通訊層在自動產生用戶端對象(即將其序列化為JSON字串)時忽略該屬性,即只在用戶端對象中產生Id、Name和Email三個屬性。
ASP.NET AJAX非同步通訊層能夠很好地支援這個需求,我們所要做的只是為Employee類中的Salary屬性添加[System.Web.Script.Serialization.ScriptIgnore]屬性,修改後的Salary屬性如下,注意其中粗體部分:
private int m_salary;
[System.Web.Script.Serialization.ScriptIgnore]
public int Salary
{
get { return m_salary; }
set { m_salary = value; }
}
這時,ASP.NET AJAX非同步通訊層為Employee類產生的用戶端JavaScript類型中就不再包含該Salary屬性了。圖3-21顯示了在Visual Studio調試器中看到的用戶端Employee類型的結構。
圖3-21 忽略Salary屬性後用戶端Employee類型的結構
總結:想要讓ASP.NET AJAX非同步通訊層為伺服器端複雜類型自動產生相應的用戶端JavaScript類型,並在調用過程中傳遞並接收該複雜類型,我們需要:
- 為Web Service類或Web Service中需要暴露給用戶端的方法添加[ScriptService]屬性;
- 為Web Service中需要暴露給用戶端的方法添加[WebMethod]屬性;
- Web Service類中的某個方法的某個參數或傳回值為該複雜類型;
- 為Web Service類添加若干個[GenerateScriptType(typeof([TypeName]))]屬性,[TypeName]表示該複雜類型或其嵌套的複雜類型的名稱;
- 該複雜類型必須要有一個無參數的建構函式;
- 該複雜類型的所有公有屬性應該提供getter和setter訪問器(即需要可讀可寫),除非如下幾種情況:
- 該屬性應用了[System.Web.Script.Serialization.ScriptIgnore]屬性,即讓ASP.NET AJAX非同步通訊層在產生用戶端JavaScript類型時忽略該屬性,那麼其屬性可以沒有setter或getter。
- 該伺服器端對象只是用來單向輸出JSON字串,那麼其屬性可以沒有setter。
- 在用戶端傳入的時候不會設定該屬性的值,那麼該屬性可以沒有setter。
- 在頁面中的ScriptManager控制項中添加對該Web Service的引用。
然後,ASP.NET AJAX非同步通訊層在為某伺服器端複雜類型產生用戶端JavaScript類型時,將:
- 會把沒有應用[System.Web.Script.Serialization.ScriptIgnore]的公有屬性(property)或公有欄位(field)映射到用戶端JavaScript類型中;
- 不會把該複雜類型的私人欄位對應到用戶端JavaScript類型中;
- 不會把該複雜類型的方法(method)映射到用戶端JavaScript類型中;
我們也可以使用如下文法在用戶端直接建立該複雜類型:
var myObj = new [NameSpace].[ClassName]();