随笔-312  评论-12034  文章-2  trackbacks-256

本文来自《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类型,并在调用过程中传递并接收该复杂类型,我们需要:

  1. 为Web Service类或Web Service中需要暴露给客户端的方法添加[ScriptService]属性;
  2. 为Web Service中需要暴露给客户端的方法添加[WebMethod]属性;
  3. Web Service类中的某个方法的某个参数或返回值为该复杂类型;
  4. 为Web Service类添加若干个[GenerateScriptType(typeof([TypeName]))]属性,[TypeName]表示该复杂类型或其嵌套的复杂类型的名称;
  5. 该复杂类型必须要有一个无参数的构造函数;
  6. 该复杂类型的所有公有属性应该提供getter和setter访问器(即需要可读可写),除非如下几种情况:
    1. 该属性应用了[System.Web.Script.Serialization.ScriptIgnore]属性,即让ASP.NET AJAX异步通讯层在生成客户端JavaScript类型时忽略该属性,那么其属性可以没有setter或getter。
    2. 该服务器端对象只是用来单向输出JSON字符串,那么其属性可以没有setter。
    3. 在客户端传入的时候不会设置该属性的值,那么该属性可以没有setter。
  7. 在页面中的ScriptManager控件中添加对该Web Service的引用。

然后,ASP.NET AJAX异步通讯层在为某服务器端复杂类型生成客户端JavaScript类型时,将:

  1. 会把没有应用[System.Web.Script.Serialization.ScriptIgnore]的公有属性(property)或公有字段(field)映射到客户端JavaScript类型中;
  2. 不会把该复杂类型的私有字段映射到客户端JavaScript类型中;
  3. 不会把该复杂类型的方法(method)映射到客户端JavaScript类型中;

我们也可以使用如下语法在客户端直接创建该复杂类型:

var myObj = new [NameSpace].[ClassName]();
posted on 2007-06-12 09:52 Dflying Chen 阅读(4162) 评论(25)  编辑 收藏 网摘 所属分类: ASP.NET AJAX (Atlas)

评论:
#1楼 2007-06-12 13:39 | Anthan      
今天早上一来就开会,开到中午才晚没想到上来还能坐沙发,呵呵
昨天看老赵在MSDN上讲的,太深入了没弄明白,看你写的清楚了不少
先要懂得使用再去理解原理就快多了。

  回复  引用  查看    
#2楼 2007-06-12 14:14 | Michael[未注册用户]
以这种方式增加修改和直接在页面上放一UpdatePanel,性能方面有差别吗?
  回复  引用    
#3楼 2007-06-12 17:10 | yao[未注册用户]
应该有差别的,个人认为,用updatePanel对性能没什么太大帮助.虽然是异步的,但传送的数据还是整个页面. 不知道理解得对不对,哈.
  回复  引用    
#4楼 2007-06-12 17:43 | ylhyh[未注册用户]
mark了,慢慢看
  回复  引用    
#5楼 2007-06-12 17:44 | ylhyh[未注册用户]
@yao

我都想抛弃UpdatePanel了,性能太差,等待的时间有时候觉得还没有直接PostBack来得爽

  回复  引用    
#6楼 2007-06-12 17:51 | 里奥特      
学习中....
  回复  引用  查看    
#7楼[楼主] 2007-06-12 19:38 | Dflying Chen      
@Anthan
谢谢支持,忠实读者:)

  回复  引用  查看    
#8楼[楼主] 2007-06-12 19:39 | Dflying Chen      
@Michael
性能方面差别非常大,无论是从网络传输的字节数,还是服务器端效率来讲都是如此。

  回复  引用  查看    
#9楼[楼主] 2007-06-12 19:40 | Dflying Chen      
@yao
传输的数据是UpdatePanel中的内容以及ViewState,不过处理的过程和传统整页回送没有太大不同。

  回复  引用  查看    
#10楼[楼主] 2007-06-12 19:40 | Dflying Chen      
@ylhyh
UpdatePanel的性能确实不算好,不过大多数情况下,应该也够用了

  回复  引用  查看    
#11楼[楼主] 2007-06-12 19:40 | Dflying Chen      
@里奥特
:)

  回复  引用  查看    
#12楼 2007-06-12 22:25 | 若寒      
谢谢分享。越来越想买到这本书了。书出版后马上去当当订购。
学习中。改天把老赵那课也下载来看看。哈。

  回复  引用  查看    
#13楼[楼主] 2007-06-12 23:14 | Dflying Chen      
@若寒
谢谢支持!

  回复  引用  查看    
#14楼 2007-06-21 10:30 | MK2      
晕, 还要等到7月尾.....
  回复  引用  查看    
#15楼[楼主] 2007-06-22 10:34 | Dflying Chen      
@MK2
很快啦:)

  回复  引用  查看    
#16楼 2007-07-30 17:19 | TSENG      
问题:循环改变10个label里面的文字,如下
for(var i = 1; i <= 10; i++) {
var a = $('lbl_' + i);
if(a.innerText != '') {
Service.HelloWorld(function onSucceeded(result) {
a.innerText = result;
}) ;
}
}
//为什么只能给最后一个label付值?
我估计原因是这个for循环运算速度非常快,但是Service.HelloWorld()获取值则需要几秒的时间,只能获得最后一個Service.HelloWorld()的值,如何解決這個問題?

  回复  引用  查看    
#17楼[楼主] 2007-07-31 08:38 | Dflying Chen      
@TSENG
在每一个onSucceeded中调用下一个Label的相关函数

  回复  引用  查看    
#18楼 2007-08-03 11:17 | ayu[未注册用户]
我有个问题.文中function btnSave_onclick() {
employeeInEditing.Id = $get("tbId").value;
.......
}
前面只是这样声明了:var employeeInEditing = null;
为什么可以用employeeInEditing.Id这种写法.
然而之前的:function onCreated(result) {
employeeInEditing = result;
$get("tbId").value = employeeInEditing.Id;
......}
我可以接受.因为有employeeInEditing = result这一句.
希望能解释一下.谢谢.

  回复  引用    
#19楼[楼主] 2007-08-03 20:18 | Dflying Chen      
@ayu
在btnSave_onclick() 的时候,employeeInEditing 应该已经被赋值过了

  回复  引用  查看    
#20楼 2007-08-04 00:18 | ayu[未注册用户]
btnSave_onclick()时已经赋值了?
我看了代码.只看到之前的
var employeeInEditing = null;

并没有看到其他赋值语句吖...
可否再解释一下.麻烦了不好意思.

实在不明白employeeInEditing 什么时候跟Employee的四个属性联系上了.
employeeInEditing.Id = $get("tbId").value;

谢谢

  回复  引用    
#21楼[楼主] 2007-08-05 12:12 | Dflying Chen      
@ayu
就是说,在btnSave_onclick() 之前,已经有employeeInEditing = result这一句了

  回复  引用  查看    
#22楼 2007-08-10 17:16 | Tony[未注册用户]
@Dflying Chen
我有这样的问题:我把Employee类和返回Employee类型的Web Service单独做好,发布在local 上(比如:http://localhost/service/PeopleManagementService.asmx)),请问,在客户端怎么调用?如果用function btnNew_onclick() {
PeopleManagementService.CreateNewEmployee(onCreated);
},会找不到PeopleManagementService的

  回复  引用    
#23楼[楼主] 2007-08-23 19:06 | Dflying Chen      
@Tony
这样就需要使用asbx文件了,在本书的第10章专门介绍了这个问题

  回复  引用  查看    
#24楼 2008-12-25 14:38 | 莫名0755[未注册用户]
请问,客户端Employee类的构造函数应该如何构造才能保证与服务端的Employee类能够相互转换??
  回复  引用    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 780052




相关文章:

相关链接: