您的位置:首页 > 其它

页面无限跳转间如何保存页面状态

2005-10-14 15:40 711 查看
引子
这是一个极其困难的题目,仅仅是描述一遍,都会非常困难,不过我尝试一下,希望能描述清楚:

我们公司是采用list/Detail的页面逻辑,即list页面有一个DataGrid,列出了一些项,点击其中一项后,页面跳转到Detail页面查看该项的详细。Detail页面有一个返回按钮,点击后即返回到list页面。list中的数据列表通常是经过筛选的,如xxx大于20的,然后还经过了排序的。

现在问题是这样的:

用户要求,当从Detail返回到list中时,数据列表中的状态不变。

公司认为,这是一个合理的要求,因为数据量实在太大,谁都不想返回list就又得从第一页看起。

还要求,刚才看的Detail的项目必须是在list的数据列表的当前页,而不一定是进入前list数据列表的那一页;但如果在Detail中把当前项删除,返回则必须是在进入前list数据列表的那一页。

更麻烦的是,在Detail页时用户可能跳转到若干个相关页,这些相关页也可能还有DataDrid,然后用户会在跳入相应的Detail页中,这些相应的状态也要得到保持。

而且还会可能出现一个list页中会有多个DataGrid。

举个例子吧:

用户查看A_list,A_list中有DataGrid_A1和DataGrid_A2,其中的数据都是经过筛选了的;然后他查看项A1.1这条数据,这就进入了A1.1_Detail;接着又跳入了A1.1的相关页面A1.1_Detail2,这个页面有一个DataGrid_A1.1,他进行筛选、排序;再跳入A1.1的另一个相关页面,这个页面是一个list页面A1.1_List,其中有一个DataGrid_A1.11,很显然,这个页面的数据也是筛选过的,用户给他排序;再进入其中一项的Detail页面A1.11.1,修改其中的数据……

好,做过这些之后,他返回。先回到了A1.1_List,要求DataGrid_A1.11的筛选条件、排序方式不变,刚才访问的那一项在DataGrid当前页上,之前可能该项时在第二页上,现在可能是在第四页,那就显示第四页吧;接着返回到A1.1_Detail2,这个页面上的DataGrid_A1.1筛选条件、排序方式、当前页码不变;再返回到A1.1_Detail,删除了这条A1.1数据;最后返回到A_list,这时要求该页上的两个DataGrid的筛选条件、排序方式不变,都显示之前所在的页码。

更可怕的是,真正的用户他未必就会这么原路返回,所以你别想让这些页面状态数据遵循后进先出原则。

请问该怎么做?

出现这个问题的原因
如果是在Windows程序中,这就非常简单了,因为从第一,某记录的Detail及其相关信息,通常是一个窗体的多个选项卡而已;即使是打开另一个窗体,也不过是把本身Hide(隐藏)起来了而已,等返回的时候再Show出来即可。归根结底,Windows程序是一个有状态的应用程序,一切都很简单。

到了Web就不一样的,Web是无状态的,页面的每一次回传,都不知道自己的某一个变量之前是什么值,更别说页面之间了,每一个网页,都不知道自己从哪里(哪个页面)来。

问题分析
这么一个难办的问题,看都看得头晕了,可是要解决问题,还是得用清醒的大脑想问题。

基本思路:保存前面的网页的状态数据,跳转到一个新的页面时,该页面保存前面网页的状态数据,然后在返回时,该网页将原数据取回。

我们先来看一看页面间互传数据该怎么办。

一般而言,都是用Url参数的方式,但在这里显然失去了效用,Url有长度限制,而且这么多的参数,装配Url字符串就会让你头疼死掉。

怎么办呢,只好用另外一种办法了:

从一个页面跳转的时候,不要用Response.Redirect,而应用Server.Transfer,然后再目标网页中使用Context.Handler,如下所示:

前一个网页的类是abc,后一个网页是dbc.aspx,

在前一个网页abc中定义公开字段

public string ccc;

那么在abc中跳转的时候用

Server.Transfer(dbc.aspx)

在后一个网页使用

((abc)Context.Handler).ccc 就可以取出相应的值了。

但是显然这样做还有一个问题,缺乏通用性。多个页面跳转怎么办?而且Context.Handler.GetType()方法是无法使用的,会出现异常。

那就定义接口了。

把有关需要互传的数据定义在接口里,多个网页实现同一个接口。

查询参数、排序条件、要显示的当前页、要显示在当前页的项目ID……都定义在接口的实现里。

但问题是,网页在跳转的时候,数据会不断积累,如你从list1跳转到Detail1,再跳转到list2,再跳转到Detail2,这个时候Detail2要保存前面三个网页的数据,而这三个网页实现同一个接口,这样可能会导致数据覆盖的情况。

一般而言,面对这种情况的时候是设计一个堆栈似的的数据结构,每进入到一个新的网页时,都把原网页的数据推入堆栈,这叫做保护现场。

可惜这也不能用,因为用户并不会一定就会原路返回,如果用后进先出的方式的话,会发现某个网页要取回自己的数据却发现无法取回了,能取到的不是自己的数据。

只能用数组了,而且这个数组还要是动态的。

那就把刚才的接口修改一下,改成一个类DataInfo,然后再实现一个接口,接口里有一个动态数组,用来存放DataInfo。

可是动态数组的元素是object,范围太广,我希望要严格一些,只允许存放而不是杂乱无章的堆在数组里,要是真的在接口里就实现一个动态数组,那你往里面塞DataInfo,我往里面塞别的东东,那还不乱套了。

那就再动态数组外面包装一下,使得它只允许存放DataInfo。不能直接在接口中包装,因为我要加入一些代码来校验存放进去的是否只是DataInfo,还是其他的什么东东。怎么办呢,那就再自定义一个类DataInfoList。并且这些类都要标记为Serializable,可序列化,这样才可以在ViewState中保存状态。

解决方法的实现
以上分析过了之后,我们来看如何实现他:
class DataInfo:这个类用于保存数据状态,一般而言,也就是每个DataGrid对应一个:
[Serializable()]

public class DataInfo
{
private string dataName;
private Hashtable searchParams;
private Hashtable otherParams;
private int currentPage;
private string sortExpression;
private string itemID;

public DataInfo(string dataName)
{
this.dataName = dataName;
}

/// <summary>
/// 数据名
/// </summary>
public string DataName
{
get { return dataName; }
}
/// <summary>
/// 查询参数
/// </summary>
public Hashtable SearchParams
{
get { return searchParams; }
set { searchParams = value; }
}

/// <summary>
/// 获取其他参数
/// </summary>
public Hashtable OtherParams
{
get { return otherParams; }
set { otherParams = value; }
}

/// <summary>
/// 获取当前页
/// </summary>
public int CurrentPage
{
get { return currentPage; }
set { currentPage = value; }
}

/// <summary>
///获取排序方式
/// </summary>
public string SortExpression
{
get { return sortExpression ;}
set { sortExpression = value; }
}

/// <summary>
/// 获取要显示在当前页的项的ID
/// </summary>
public string ItemID
{
get { return itemID; }
set { itemID = value; }
}
}

class DataInfoList:这个类包装承载DataInfo的动态数组,限定数组输入输出的数据类型
[Serializable()]

public class DataInfoList
{
private ArrayList dataInfoList = new ArrayList();

public DataInfo this[int index]
{
get
{
return (DataInfo)dataInfoList[index];
}
set
{
if (((DataInfo)dataInfoList[index]).DataName == value.DataName || this[value.DataName] == null)
{
dataInfoList[index] = value;
}
else
{
throw new Exception("There have a DataInfo used this Name yet!");
}
}
}

public DataInfo this[string dataName]
{
get
{
for (int i = 0; i < dataInfoList.Count; i++)
{
if (this[i].DataName == dataName)
{
return this[i];
}
}
return null;
}
set
{
for (int i = 0; i < dataInfoList.Count; i++)
{
if (this[i].DataName == dataName)
{
this[i] = value;
return;
}
}
this.Add(value);
}
}

public void Remove(DataInfo value)
{
dataInfoList.Remove(value);
}

public void Remove(string dataName)
{
DataInfo dataInfo = this[dataName];
if (dataInfo != null)
{
dataInfoList.Remove(dataInfo);
}
}

public bool Contains(DataInfo value)
{
return dataInfoList.Contains(value);
}

public bool Contains(string dataName)
{
DataInfo datainfo = this[dataName];
if (datainfo != null)
{
return true;
}
return false;
}

public void Clear()
{
dataInfoList.Clear();
}

public int Add(DataInfo value)
{
if (this[value.DataName] == null)
{
return dataInfoList.Add(value);
}
else
{
throw new Exception("There have a DataInfo used this Name yet!");
}
}

public int Count
{
get
{
return dataInfoList.Count;
}
}
}

interface IPageInfo:这个接口用在页面中,以实现页面间的数据通信。

public interface IPageInfo
{
/// <summary>
/// 页面名
/// </summary>
string PageName
{
get;
}
/// <summary>
/// 获取数据信息
/// </summary>
DataInfoList DataInfos
{
get;
}

/// <summary>
/// 获取其他参数
/// </summary>
Hashtable OtherParams
{
get;
}
}

在页面上的使用,定义好了以上这些之后,在页面中该怎样用呢?
首先,在List页面中实现IPageInfo接口:
public class RoleList : System.Web.UI.Page,IPageInfo
然后针对每一个DataGrid实例化一个DataInfo对象:
protected DataInfo dataInfo = new DataInfo("Role");
接着写一些处理DataGrid状态的代码,我是使用的属性:
#region 数据网格状态信息
private System.Collections.Hashtable SearchParams
{
get
{
if (ViewState["SearchParams"] != null)
{
return (Hashtable)ViewState["SearchParams"];
}
else
return null;
}
set
{
ViewState["SearchParams"] = value;
}
}

private System.Collections.Hashtable OtherDataParams
{
get
{
if (ViewState["OtherDataParams"] != null)
{
return (Hashtable)ViewState["OtherDataParams"];
}
else
return null;
}
set
{
ViewState["OtherDataParams"] = value;
}
}

private int CurrentPage
{
get
{
return MyDataGrid.CurrentPageIndex;
}
set
{
MyDataGrid.CurrentPageIndex = value;
MyDataGrid.DataBind();
navigateRole.CurrentPage = MyDataGrid.CurrentPageIndex + 1;
navigateRole.TotalPages = MyDataGrid.PageCount;
}
}

private string SortExpression
{
get
{
return dsSystem.Role.DefaultView.Sort;
}
set
{
dsSystem.Role.DefaultView.Sort = value;
MyDataGrid.DataBind();
navigateRole.TotalPages = MyDataGrid.PageCount;
}
}

private string ItemID
{
get
{
if (MyDataGrid.SelectedIndex >= 0)
{
return MyDataGrid.DataKeys[MyDataGrid.SelectedIndex].ToString();
}
else
return null;
}
set
{
int pageIndex = MyDataGrid.CurrentPageIndex;
bool find = false;
for( int j = 0; j < MyDataGrid.PageCount && find == false; j++)
{
MyDataGrid.CurrentPageIndex = j;
MyDataGrid.DataBind();
for(int i = 0; i < MyDataGrid.Items.Count; i++)
{
if (MyDataGrid.DataKeys[i].ToString() == value)
{
find = true;
break;
}
}
}
if (find == false)
{
MyDataGrid.CurrentPageIndex = pageIndex;
MyDataGrid.DataBind();
}
navigateRole.CurrentPage = MyDataGrid.CurrentPageIndex + 1;
navigateRole.TotalPages = MyDataGrid.PageCount;
}
}
#endregion

在PageLoad中取出前一页面的数据,进行处理,注意,从前一页面过来用的是Server.Transfer方法:
IPageInfo pageInfo = null;
//取出前一页面的信息并保存数据网格状态的信息
try
{
pageInfo = (IPageInfo)Context.Handler;
}
catch {}
if (pageInfo != null)
{
if (pageInfo.OtherParams != null)
OtherParams = pageInfo.OtherParams;
if (pageInfo.DataInfos != null)
{
//保存全部DataGrid信息
DataInfos = pageInfo.DataInfos;
//取出当前DataGrid的信息
if (pageInfo.DataInfos[dataInfo.DataName] != null)
{
dataInfo = pageInfo.DataInfos[dataInfo.DataName];
}
}
}
把数据取出来了然后自然就是处理了,我是设置前面那些属性的值的,实际上方法有很多种,这里就不详述了。
IPageInfo的实现,其中处理DataInfos属性时要更新页面上每一个DataGrid对应的DataInfo的信息,以反映最近的更改:
public string PageName
{
get
{
return "RoleList";
}
}

public Hashtable OtherParams
{
get
{
if (ViewState["OtherParams"] != null)
{
return (Hashtable)ViewState["OtherParams"];
}
else
return null;
}
set
{
ViewState["OtherParams"] = value;
}
}

public DataInfoList DataInfos
{
get
{
//更新数据网格状态信息
DataInfoList dataInfoList;
if (ViewState["DataInfos"] != null)
dataInfoList = (DataInfoList)ViewState["DataInfos"];
else
dataInfoList = new DataInfoList();
dataInfo.CurrentPage = CurrentPage;
dataInfo.ItemID = ItemID;
dataInfo.OtherParams = OtherDataParams;
dataInfo.SearchParams = SearchParams;
dataInfo.SortExpression = SortExpression;
dataInfoList[dataInfo.DataName] = dataInfo;
return dataInfoList;
}
set
{
ViewState["DataInfos"] = value;
}
}

跳转到其他页面(如详细页面):
Server.Transfer("RoleDetail.aspx");

对于Detail页面,会比较简单一些,因为基本上没有DatInfo更新的问题,旨在删除和新增时需要修改ItemID:
也是先实现接口:
public class RoleDetail : System.Web.UI.Page,IPageInfo
接着定义两个变量,一个保存前页来的数据,一个定义当前的数据类别,也就是要对哪一个DataInfo实例进行操作:
protected IPageInfo pageInfo;
protected string dataName = "Role";

PageLoad取出前页数据并处理当前项数据:
try
{
pageInfo = (IPageInfo)Context.Handler;
}
catch
{
}
//取出当前项的ID
if (pageInfo != null && pageInfo.DataInfos != null)
{
DataInfos = pageInfo.DataInfos;
}
//取详细数据
if (DataInfos != null && DataInfos[dataName].ItemID != null)
GetItemData();
else
GetNullData();

接口的实现:
public string PageName
{
get
{
return "RoleDetail";
}
}

public DataInfoList DataInfos
{
get
{
if (ViewState["DataInfos"] != null)
{
return (DataInfoList)ViewState["DataInfos"];
}
else
return null;
}
set
{
ViewState["DataInfos"] = value;
}
}

public Hashtable OtherParams
{
get
{
if (ViewState["OtherParams"] != null)
{
return (Hashtable)ViewState["OtherParams"];
}
else
return null;
}
set
{
ViewState["OtherParams"] = value;
}
}

#endregion

跳转到其他页面(如返回List):
Server.Transfer("RoleList.aspx");

这样我们需要的功能便实现了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: