视图状态一个最大的限制是它必须和特定页面紧密绑定。当用户从一个页面浏览到另一个页面时,这些信息也就消失了。这个问题有几种解决方案,最佳的方案取决于你的项目需求。
查询字符串
一个常用的办法是在 URL 中使用查询字符串传送信息。搜索引擎中频繁的使用了这种办法。
查询字符串的优势:
它是轻量级的,并且不会加重服务器的负担。和跨页回发不同,查询字符串很容易在页面间传送相同的信息。
查询字符串的限制:
- 信息仅限于简单的字符串,只能包含合法的 URL 字符。
- 用户很容易看到信息,对因特网上的窃听者也是如此。
- 大胆的用户可能会修改查询字符串或给它赋新值,而你的程序并不能预期和预防这些修改。
- 多数浏览器对 URL 字符串的长度有限制(通常 1K-2K)
尽管如此,把信息加入到查询字符串中仍是一项很有用的技术。
使用查询字符串
没有基于集合的方式来帮助你放置信息,你需要自己放置查询字符串的存储信息。
int recordID = 10;
Response.Redirect("newpage.aspx?recordID=" + recordID.ToString());
可以发送多个参数,中间用符号 & 隔开。
接收页面很容易就可以使用查询字符串来工作,它使用内置的 Request 对象提供的 QueryString 字典集合来取值:
string ID = Requet.QueryString["recordID"];
如果集合中不包含查询的 Key 值,那么 ID 将被设为 null 值。
取得的值总是字符串,也因此很容易就可以转换为其它数据类型。
URL 编码
查询字符串的一个潜在问题是使用 URL 中不允许的字符(URL 中所有字符必须是字母、数字、及少量的符号"$ - . + ! * ' ( ) , ")。如果担心将要在查询字符串中保存的数据含有非法字符,可以使用 HttpServerUtility 类中提供的方法进行编码。
string productName = "Flying Carpet";
Response.Redirect("newpage.aspx?productName=" + Server.UrlEncode(productName));
你可以使用 Server.UrlDecode() 方法将字符串恢复其初始值,但不需要这么做,当使用 Request.QueryString集合时,ASP.NET 自动解码了该值。
跨页回发
只不过是把信息从一个页面发送到另一个页面去,这个技术听起来很简单,但却是一个潜在的雷区!使用的不好,会导致创建的页面紧密耦合而难于改进和调试。
支持跨页回发的基础架构是属性 PostBackUrl,它在 IButtonControl 接口中定义并在按钮类控件(Button、LinkButton、ImageButton)中出现。只需要简单地将 PostBackUrl 设置为其他 Web 窗体的名字就可以使用跨页回发了。
看下面的小示例:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CrossPage1.aspx.cs" Inherits="Chapter06_CrossPage1" %>
<!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>CrossPage1</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="txtFirstName" runat="server"></asp:TextBox>
<asp:TextBox ID="txtLastName" runat="server"></asp:TextBox>
<asp:Button ID="btnSubmit" runat="server" PostBackUrl="CrossPage2.aspx"
Text="Submit" />
</div>
</form>
</body>
</html>
CrossPage2.aspx 可以通过 PreviousPage 属性和 CrossPage1.aspx 中的对象进行交互:
protected void Page_Load(object sender, EventArgs e)
{
if (Page.PreviousPage != null)
{
lblInfo.Text = "You came from a page titled " + PreviousPage.Header.Title;
}
}
1. 获取页面特定信息
在先前的示例中,仅仅是获得了 Page 类的成员。如果要得到更具体的细节,如控件的值,必须把 PreviousPage 转换为适当的类型:
protected void Page_Load(object sender, EventArgs e)
{
Chapter06_CrossPage1 prePage = PreviousPage as Chapter06_CrossPage1;
if (prePage != null)
{
// (Read some infomation from previous page.)
}
}
注:
对于没有项目文件的网站,VS 会标记为错误,表示它没有源页面类的类型信息(如 CrossPage1),不过编译后这个错误会消失。
即便已经将前一页面转换成了正确的页面类型,但还是不能够直接访问控件的值。这是因为控件被声明为了受保护的成员,此时,可以通过在页面类中添加封装控件变量的属性来实现上述操作:
public TextBox FirstNameTextBox
{
get { return txtFirstName; }
}
public TextBox LastNameTextBox
{
get { return txtLastName; }
}
但这不是好的方式,它显示了太多的细节,还允许目标页面随意访问控件的任意属性!最好的方式是定义特定且有效的方法或属性来提取需要的信息:
public string FullName
{
get { return txtFirstName.Text + txtLastName.Text; }
}
现在已经达到最好的状态了。两个页面的关系被文档化且易于理解。即使源页面控件改变了,仍只需要修改 FullName 属性,修改仅限于 CrossPage1.aspx 页面,而完全不需要修改 CrossPage2.aspx 页面。
2. 在任意事件处理程序中进行跨页发送
除了使用实现了 IButtonControl 接口的那些按钮进行跨页发送外,我们还可以使用一个重载的 Server.Transfer()方法将试图状态信息完整的送到目标页面。只要简单的加上一个值为 true 的 oreserverFrom 参数即可。
// 为 true,则保留 System.Web.HttpRequest.QueryString 和 System.Web.HttpRequest.Form 集合。
// 为 false,则清除 System.Web.HttpRequest.QueryString 和 System.Web.HttpRequest.Form 集合。
Server.Transfer("CrossPage2.aspx", true);
这样就可以在网页代码的任意地方使用跨页发送了(只要能访问到 Server 对象的地方)。
该技术会造成服务器的重定向,这意味着没有额外的往返来重定向客户,即使已去了另一个页面,但客户端浏览器中的URL不会改变。
怎么区分是通过一个按钮跨页传送还是通过 Server.Transfer()方法跨页传送的呢?
protected void Page_Load(object sender, EventArgs e)
{
if (PreviousPage == null)
{
// 直接被 get 或 post 方式请求
}
else if (PreviousPage.IsCrossPagePostBack)
{
// 通过 Button 类跨页回传
}
else
{
// 通过 Server.Transfer() 跨页回传
}
}
3. IsPostBack 属性和 IsCrossPagePostBack 属性
理解 Page.IsPostBack 属性在跨页回送中是很重要的。源页面(触发了跨页回送的页面)的 IsPostBack 属性为 true。目的页面(接收跨页回送的页面)的 IsPostBack 属性为 false。这个系统的好处是,它意味着你的初始化代码通常会在该运行的时候运行。
假设第一次请求 CrossPage1.aspx 使用如下代码时,它执行了一些费时的初始化工作:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
// Retrieve some data from a database and display it on the page.
}
}
现在假设用户通过跨页回送从 页面1 转到了 页面2 。只要 CrossPage2.aspx 方位 PreviousPage 属性,就会执行 CrossPage1.aspx 页面生命周期。此时,再次触发 CrossPage1.aspx 的 Page.Load 事件。然而,CrossPage1.aspx 上的 Page.IsPostBack 属性为 true,你的代码就会跳过费时的初始化步骤,控件的值从视图状态得以恢复。另一方面,CrossPage2.aspx 的IsPostBack 属性为 false,所有该页面执行必要的第一次初始化。
在某些情况下,当页面不是跨页回送的源页面时,你可能会有用于处理第一次请求和所有接下来的回送的代码。在这种情况下,你可以查看 IsCrossPagePostBack 属性,如果当前页面触发了一个跨页回送,则该属性的值为 true。
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsCrossPagePostBack)
{
// This page triggered a postback to CrossPage2.aspx.
}
else if (Page.IsPostBack)
{
// This page was posted back normally.
// Don't do the first-request initialization.
}
else
{
// This is the first request for the page.
// Perform all the required initialization.
}
}
4. 跨页发送和验证
在跨页回送中使用验证有一些潜在的麻烦。如果源页面包含验证控件,那么使用跨页发送会发生什么呢?
两个按钮都将 CausesValidation 设置为 true,因此单击任何一个按钮来触发跨页回发,回发都会被浏览器的客户端验证阻止,并出现错误信息。此时将 RequiredFieldValidator 验证控件的属性 EnableClientScript 设为 false 以模拟客户端不支持 JavaScript 脚本或恶意客户避开了客户端验证,再次单击任一按钮,页面回发,此时新页面出现了。
为了避免发生这种问题,显然,在执行任意动作前必须检查 Page.IsValid 以在目标页面中保证源页面有效。这是需要验证的 Web 窗体的标准防范。不同的是,当页面无效时什么都不做事不够的,我们往往需要采用一些步骤将用户带回到原始页面:
// This code is in target page.
protected void Page_Load(object sender, EventArgs e)
{
if (PreviousPage != null)
{
if (!PreviousPage.IsValid)
{
// Display an error message or just do nothing.
// Response.Redirect("CrossPage1.aspx");
}
else
{
// ...
}
}
}
上面这段代码还可以再度改进。现在,因为页面是被重新请求的(不是回发),所有当用户回到原始页面时,错误信息不回出现。要修正这个问题,可以设置一个标记让原始页面知道,请求已被目标页面拒绝。
protected void Page_Load(object sender, EventArgs e)
{
if (PreviousPage != null)
{
if (!PreviousPage.IsValid)
{
// UrlReferrer: 获取有关客户端上次请求的 URL 的信息(包含许多信息)
// AbsolutePath: 获取 URI 的绝对路径
Response.Redirect(Request.UrlReferrer.AbsolutePath+"?err=true");
}
else
{
// ...
}
}
}
现在,原始页面只需要检查查询字符串中的标记并再次执行验证,验证将显示无效数据的信息。
protected void Page_Load(object sender, EventArgs e)
{
if (Request.QueryString["err"]!=null)
{
Page.Validate();
}
}
还可以做更多来改进页面。比如说,用户已经填写了某一详细表单的一部分,这时候再重新请求页面就不太好,因为这样就清空了用户的输入,用户将不得不重新开始。你应该在响应流中写一些 JavaScript 代码,它使用浏览器的回退功能回到源页面。
这个示例演示了跨页回发通常要比开发人员想象的麻烦。如果没有小心处理,跨页回发会导致生成紧耦合的页面,它们相互有依赖性,这使得它们将来的修改变得困难。