在
UpdatePanel
对页面进行部分刷新时注册一些
Data Item
是
ASP.NET AJAX
的特点之一。我们可以在服务器端为某个控件注册一个字符串甚至是一个对象,然后在客户端将将其取回。但是现在我希望向您展示一些您可能会忽视的事情。
让我们先从最基本的用法开始。在一个异步回送过程中,我们可以调用
RegisterDataItem
方法将一个字符串与一个
UpdatePanel
绑定起来:
RegisterDataItem
方法调用
ScriptManager.GetCurrent(
this
.Page).RegisterDataItem(
this
.UpdatePanel1, "
DataItem
");
在客户端,如果我们监听
pageLoading
,
pageLoaded
或者
endRequest
事件,我们就可以使用下面的代码,使用控件的
ClientID
将这个字符串取回:
将字符串取回
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(
function
(sender, e)
{
var
dataItem = e.get_dataItems()["
<%= this.UpdatePanel1.ClientID %>
"];
// "DataItem"
// more implementations...
});
RegisterDataItem
方法还有一个重载的版本会接受一个额外的参数,其作用是使用一个布尔值来表明我们注册的
dataItem
是不是已经被序列化为
JSON
。但是这里就出了一个问题
——
甚至在官方文档中的示例也是不正确的。
下面的代码选自官方文档中的示例:
官方文档中的示例
protected
void
Page_Load(
object
sender, EventArgs e)
{
if
(ScriptManager1.IsInAsyncPostBack)
{
System.Web.Script.Serialization.JavaScriptSerializer json =
new
System.Web.Script.Serialization.JavaScriptSerializer();
ScriptManager1.RegisterDataItem(Label1, DateTime.Now.ToString());
ScriptManager1.RegisterDataItem(Label2, json.Serialize("
more data
"),
true
);
}
}
这个示例的确能够正常工作,但是它事实上掩盖了一个问题。请注意,序列化之后的
Data Item
会被
JavaScript
内置的
eval
方法解释执行:
使用
eval
函数解释执行
function
Sys$WebForms$PageRequestManager$_onFormSubmitCompleted(sender, eventArgs)
{
//...
for
(i = 0; i < dataItemJsonNodes.
length
; i++) {
var
dataItemJsonNode = dataItemJsonNodes[i];
this
._dataItems[dataItemJsonNode.id] =
eval(dataItemJsonNode.content)
;
}
//...
}
在官方文档的示例中,字符串
“
more data
”
会被序列化为
“
"more data"
”
(请注意多出的双引号),这样从
eval
方法中得到的结果就是
“
more data
”
,这正是我们注册的内容。这个再正常不多了,不是吗?猜猜看如果我们将一个序列化的对象注册到客户端时会发生什么事情呢?我为此写了一个示例:
注册一个对象
// the definition of Person class
public
class
Person
{
public
string Name;
}
// the code to register a Person object
Person person =
new
Person();
person.Name = "
Jeffz
";
JavaScriptSerializer serializer =
new
JavaScriptSerializer();
ScriptManager.GetCurrent(
this
.Page).RegisterDataItem(
this
.UpdatePanel1, serializer.Serialize(person),
true
);
当我们进行异步更新时就会抛出异常。似乎这是因为服务器端注册的表示
Person
对象的
JSON
字符串
“
{"Name":"Jeffz"}
”
无法被直接传递进入
eval
方法。具体的原因关乎语法方面的问题,可以在
ECMAScript Specification
中找到解释,因此我在这里就不多作解释了。那么,请注意我下面的代码:
为
JSON
字符串添加括号
ScriptManager.GetCurrent(
this
.Page).RegisterDataItem(
this
.UpdatePanel1,
"(" +
serializer.Serialize(person)
+ ")"
,
true
);
现在,我们的代码就能正常工作了,于是我们就可以在客户端将
Person
对象取出:
在客户段取出
Person
对象
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(
function
(sender, e)
{
var
person = e.get_dataItems()["
<%= this.UpdatePanel1.ClientID %>
"];
alert
(person.Name);
// "Jeffz"
});
这样看来,这个问题也就可以使用这个方法来解决了。不过为什么
ASP.NET AJAX
会使用
eval
方法,而不是定义在
Microsoft AJAX Library
中的
deserialize
方法来反序列化一个对象呢?我们可以发现,在
deserialize
方法中,一个表达式被传递给
eval
方法之前会被自动加上括号:
desrialize
方法
Sys.Serialization.JavaScriptSerializer.deserialize =
function
(data)
{
try
{
var
exp = data.replace(...);
return
eval
('(' + exp + ')');
}
catch
(e)
{
throw
Error.argument('data', Sys.Res.cannotDeserializeInvalidJson);
}
}
我猜想,会不会是因为编写部分刷新相关代码的开发人员并不完全了解
Microsoft AJAX Library
中的功能呢?不过可能性最大的原因则是这些代码没有被完整地
Review
和测试过。事实上这并不是
ASP.NET AJAX
最终版本中唯一的
bug
,其他的还包括那个著名的
“跨域名frame的拒绝访问错误”
和
StringBuilder的Bug
。
嗯,我们问题也看够了,就把目光移向别处吧。尽管逻辑上一个
Data Item
应该是一个包含信息的对象,但是请注意我们是使用
eva
l
方法来
“
反序列化
”
一个
JSON
字符串的。这意味着我们事实上可以将任意的合法表达式发送到客户端,然后它就会被正确执行。请看下面的示例:
服务器端代码
ScriptManager.GetCurrent(
this
.Page).RegisterDataItem(
this
.UpdatePanel1, "
var __f = function(){alert('Hello World!');}; __f;
",
true
);
客户端代码
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(
function
(sender, e)
{
e.get_dataItems()["
<%= this.UpdatePanel1.ClientID %>
"]();
});
我在上面代码中将一个函数注册为一个
Data Item
并且在客户端将其执行了。如果您深入了解
UpdatePanel
局部刷新的过程,您会注意到您在服务器端注册的
JavaScript
代码只有在页面更新结束后才会生效,也就是说,我们无法在
pageLoading
事件被触发时执行这些代码。我会在之后的文章中针对这样的设计以及如何使用
work arounds
来解决这个问题进行详细的探讨。这些
work arounds
就是基于
Data Item
注册的使用方式的,因为表示
Data Item
的
“JSON
字符串
”
在
pageLoading
触发之前就被解释执行。
示例:
<%
@ Page Language
=
"
C#
"
AutoEventWireup
=
"
true
"
CodeFile
=
"
WebForm1.aspx.cs
"
Inherits
=
"
Sopu.Business.WebForm1
"
%>
<!
DOCTYPE html PUBLIC
"
-//W3C//DTD XHTML 1.0 Transitional//EN
"
"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
>
<
head runat
=
"
server
"
>
<
title
>
无标题页
</
title
>
</
head
>
<
body
>
<
form id
=
"
form1
"
runat
=
"
server
"
>
<
asp:ScriptManager ID
=
"
ScriptManager1
"
runat
=
"
server
"
></
asp:ScriptManager
>
<
script type
=
"
text/javascript
"
>
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(
function(sender, e)
{
var dataItem
=
e.get_dataItems()[
"
<%= this.UpdatePanel1.ClientID %>
"
];
alert(dataItem.Name);
});
</
script
>
<
div
>
<
asp:UpdatePanel ID
=
"
UpdatePanel1
"
runat
=
"
server
"
>
<
ContentTemplate
>
<
asp:Button ID
=
"
test
"
runat
=
"
server
"
Text
=
"
test
"
OnClick
=
"
test_Click
"
/>
</
ContentTemplate
>
</
asp:UpdatePanel
>
</
div
>
</
form
>
</
body
>
</
html
>
using
System;
using
System.Data;
using
System.Configuration;
using
System.Collections;
using
System.Web;
using
System.Web.Security;
using
System.Web.UI;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Web.UI.HtmlControls;
using
System.Web.Script.Serialization;
namespace
Sopu.Business
{
public
partial
class
WebForm1 : System.Web.UI.Page
{
protected
void
Page_Load(
object
sender, EventArgs e)
{
}
protected
void
test_Click(
object
sender, EventArgs e)
{
Person person
=
new
Person();
person.Name
=
"
Jeffz
"
;
//
System.Web.Script.Serialization.JavaScriptSerializer serializer = new JavaScriptSerializer();
//
方法一
JavaScriptSerializer serializer
=
new
JavaScriptSerializer();
ScriptManager.GetCurrent(
this
.Page).RegisterDataItem(
this
.UpdatePanel1,
"
(
"
+
serializer.Serialize(person)
+
"
)
"
,
true
);
//
方法二
System.Web.UI.ScriptManager.RegisterStartupScript(
this
.Page,
this
.GetType(),
"
test
"
,
"
var p = (
"
+
serializer.Serialize(person)
+
"
);alert(p.Name)
"
,
true
);
}
}
public
class
Person
{
public
string
Name;
}
}