本文来自《ASP.NET AJAX程序设计 第II卷:客户端Microsoft AJAX Library相关》的第三章《异步调用Web Service和页面中的类方法》,请同时参考本章的其他文章。
3.7.3 复杂类型
若想传递某些复杂的数据类型,比如某个自定义类型或结构,那么我们要显式告知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类型,并在调用过程中传递并接收该复杂类型,我们需要:
然后,ASP.NET AJAX异步通讯层在为某服务器端复杂类型生成客户端JavaScript类型时,将:
我们也可以使用如下语法在客户端直接创建该复杂类型:
var myObj = new [NameSpace].[ClassName]();