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

ContractedBlock.gif ExpandedBlockStart.gif Code
引子 
       这是一个极其困难的题目,仅仅是描述一遍,都会非常困难,不过我尝试一下,希望能描述清楚: 

    我们公司是采用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"); 
  
这样我们需要的功能便实现了。 


本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/Teng_s2000/archive/2005/10/14/503835.aspx

 

 

ContractedBlock.gif ExpandedBlockStart.gif Code
呵呵,很感谢网友们的关心和支持,在这里谢谢大家了。
看了多家的留言,真的学到了很多东西,我很开心!在这里我针对大家的留言做一个回复,以报答大家的热情帮助。
确实,这个解决方案看起来很复杂,但是这是尤其必要的。
不知道大家是否在实际应用中是否碰到了这个问题,是如何解决的,我想其实真的不是那么简单吧,至少我在实际使用的过程中是这样的。
确实,针对一个简单的DataGrid来说,是不用想的太复杂,但问题在于,DataGrid中要保持的东西实在是太多,而且用户又实在是贪得无厌,使得最后变得很复杂了。
查询条件,不要想得查询条件是那么的简单,认为一个查询条件就够了,如果一张表有六七十个字段,用户可能就要求你这六七十个字段全都可以作为查询条件来组合成高级查询,简单的日期就可能要求之前、之后、两日期之间等等各种要求,实际上光是保存查询条件就要求有这么复杂了。
排序,用户会要求DataGrid可以以多种方式顺序、逆序排列,你不能不考虑。
其它参数,作为一个通用的解决方案,这个问题你也不能不考虑,比如我们的用户就有这种要求,某个DataGrid数据是以英文显示,而某个则是中文……
以上就构成了在文章提到的DataInfo和PageInfo的设计。
为什么要在PageInfo中将DataInfo以动态数组方式存放?
您可能认为这个也是复杂了,靠虑的时候认为就是一个DataGrid,查看详细的时候到了Detail页面马上返回,但实际上没有这么简单,一个页面多个DataGrid是很常见的,点击任一个DataGrid中的项到Detail页面是很常见的,当返回之后要能够原来的DataGrid找到自己的信息。即便在Detail页面中,可能还会有很多跳转,到达的页面可能不是一个连个。
而且在不断跳转之中,其它页面也会有DataGrid,这些信息不能够互相混淆。
这些我在文章开始已经说明了。
为什么不是堆栈式或者是链标式的结构?
确实,那样会简单一些,但是用户并不乖,你想让他遵循后进先出原则浏览这些页面后原路返回那是天方夜谭。
以上这些积累,不确定的查询参数、不确定的排序方式、不确定的其他参数、不确定的到达页面数、不确定的DataGrid个数导致了这么复杂的数据结构。
于是也就没有办法用Url参数来传递了,太复杂。
为什么不能弹出新页面?
确实,要是弹出新页面的话,那就简单很多了,我也就不比如此大费脑筋了,但是客户却不会干。首先明确一点,我开发的是ERP软件而不是一个简单的网站,ERP这类软件目前还是C/S的多,而我做的是B/S的,我们的客户却希望在拥有B/S的简单部署特性的同时又有C/S的用户体验,这是麻烦,他们不希望这么多的弹出新页面,除非是弹出选择器:就是弹出一个窗口让你选择一下日期呀,员工啊什么的。所以那个“异常”简单的办法我们没法用,我们只能这么复杂,否则用户不答应。
为什么不用Cookie和Session?
说到底还是客户的原因,如果我用了,用户可能会打开一个窗口然后就不动它了,等上几个小时后再回来发现数据都没了(他可不管你是不是什么Session过期了),他就会打电话来“啊,这是怎么回事啊,你们的软件有问题啊”…… 

 

转载于:https://www.cnblogs.com/mlaaalm/archive/2009/07/07/1518200.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值