ASP.NET AJAX(服務器控件 UpdatePanel、Timer、UpdateProgress)


       ASP.NET AJAX 對 Web 服務特性為客戶端代碼訪問服務器提供了一個可貴的窗口。但你要做大多數的困難任務,必須精心打造 Web 方法、合適的時機調用它們、適當更新頁面、除了 JavaScript 什么都不能用。對於復雜的呈現,這個過程就十分單調。

       由於這個原因,ASP.NET 提供了一個更高層的服務器模型,它提供可以直接在 Web 窗體里使用的控件和組件,你可以完全使用服務器端代碼工作

       ASP.NET AJAX 控件會自動注入客戶端腳本,在幕后使用 ASP.NET AJAX 腳本庫。與手動編寫 js 代碼相比,潛在的缺點是降低了靈活性。

       在這篇文章里,我將介紹 ASP.NET 框架中的 3 個 ASP.NET AJAX 控件:UpdatePanel、Timer、UpdateProgress。所有這些控件都支持局部呈現,可以不經過完整回發無縫地更新頁面內容,這是 Ajax 的關鍵概念。

 

UpdaePanel 的局部呈現

       UpdaePanel 能讓你使用服務器端邏輯處理普通頁面並以無閃動的 Ajax 風格刷新自己。

       基本思想是把 Web 頁面分解為一到多個獨立的區域,其中每個區域都包含在一個不可見的 UpdaePanel 里。當 UpdaePanel 中發生通常會觸發一次回發的事件時,UpdaePanel 截取該回發事件代之以執行一次異步回調

       下面是它發生過程的一個示例步驟:

  1. 用戶單擊 UpdaePanel 里的一個按鈕
  2. 一些客戶端 js 代碼(由 ASP.NET AJAX 生成)截取到單擊事件並執行一次服務器回調
  3. 在服務器端,頁面周期正常執行,所有常規事件正常發生。
  4. 最終,頁面被呈現為 HTML 返回瀏覽器
  5. 客戶端 js 得到完整的 HTML 並更新頁面上所有的 UpdaePanel (如果變更的內容不在 UpdaePanel 里,就會被忽略)

 

       UpdaePanel 控件和 ScriptManager 控件一起工作。在使用 UpdaePanel 前,需要把 ScriptManager.EnablePartialRendering 屬性設置為 true(默認值),然后才可以向頁面添加 UpdaePanel 控件(放在 ScriptManager 之后)。

       把控件拖到 UpdaePanel 時,內容出現在 <ContentTemplate> 節,如下:

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
        <asp:Button ID="Button1" runat="server" Text="Button" />
    </ContentTemplate>
</asp:UpdatePanel>

       UpdaePanel 是一個基於模板的控件。呈現自身時,它把自己的 ContentTemplate 節的內容復制到頁面上,因此,不能通過 UpdaePanel.Controls 集合動態添加控件,而要通過 UpdaePanel.ContentTemplateContainer.Controls 集合,這點請注意

 

       UpdaePanel 並非繼承自 Panel,而是直接繼承 Controls。在其生命周期里只有一個角色:作為異步刷新內容的容器。UpdaePanel 沒有可視界面,不支持樣式設置。如果要在 UpdaePanel 之外顯示邊框或者修改背景顏色,那么需要在 UpdaePanel 里放置一個普通的 Panel 或 DIV 標簽

 

       UpdaePanel 把自身呈現為 <div> 標簽。但如果設置 RenderMode 屬性由 Block 改為 Inline,可以配置 UpdaePanel 自身呈現為嵌入元素。如果要創建的 UpdaePanel 處於段落中或者其他塊元素中,就可以進行這樣的設置。

 

       下圖顯示包含 3 個 UpdaePanel 控件的示例頁面,每個 UpdaePanel 都包含相同的內容:一個 Label 和一個 Button 控件。每次頁面傳送到服務器時,Page.Load 事件觸發:

protected void Page_Load(object sender, EventArgs e)
{
    Label1.Text = DateTime.Now.ToLongTimeString();
    Label2.Text = DateTime.Now.ToLongTimeString();
    Label3.Text = DateTime.Now.ToLongTimeString();
}

image

       這個頁面顯示了異步回調無閃動刷新,單擊任一按鈕,3 個標簽都會安靜的更新。唯一的例外是瀏覽器不支持 XMLHttpRequest 對象時,UpdaePanel 會自動降級為使用完整頁面回發。

 

1. 錯誤處理

       UpdaePanel 執行異步回調后,Web 頁面代碼完全按頁面被回發時那樣運行。唯一的區別在於:

  • 通信方式:頁面通過一個 XMLHttpRequest 異步調用獲取新數據
  • 接收到的數據的處理方式:UpdaePanel 刷新自身內容,頁面其余部分保持不變

       同步回發中可能發生的問題也完全會在執行異步回發時發生,添加下列代碼測試:

if (IsPostBack)
    throw new ApplicationException("This operation failed.");

       Web 頁面拋出一個未處理的異常時,ScriptManager 捕獲這個錯誤並回傳給客戶端,然后 ASP.NET AJAX 客戶端庫在頁面中拋出一個 JavaScript 錯誤。下一步發生什么取決於瀏覽器的設置。如果啟用了腳本調試,VS 會在產生錯誤的那一行中斷。如果沒有使用腳本調試,瀏覽器可能通知也可能不通知你發生了一個問題。現在大多數瀏覽器都被配置為忽略 JavaScript 錯誤。對於 IE,會在狀態欄左下角出現“頁面錯誤”消息。

image image

 

       使用客戶端 js 處理錯誤可以改變這一行為。為此,你需要為 System.Web.PageRequestManager 類的 endRequest 事件注冊一個回調PageRequestManager 是 ASP.NET AJAX 應用程序模型的核心部分,它管理 UpdaePanel 控件的刷新過程並在頁面的生命周期各個階段觸發客戶端事件

       下面這段客戶端腳本塊就是這樣注冊的:

  1. 定義頁面第一次加載時觸發的函數
  2. 不需要使用 onload 事件,只要有 pageLoad() 函數,ASP.NET AJAX 就會自動調用它
  3. 不需要使用 unload 事件,只要有 pageUnload() 函數,ASP.NET AJAX 也會自動調用它
  4. 除了上述 2 個函數,其余函數均須手工綁定

       下面是 pageLoad() 函數,它得到 PageRequestManager 當前實例的引用,並向 endRequest 事件附加第二個函數:

function pageLoad() {
    var pageManager = Sys.WebForms.PageRequestManager.getInstance();
    pageManager.add_endRequest(endRequest);
}

       endRequest 事件在每次異步回發結束時發生。本例中,endRequest() 函數檢查是否有錯誤發生。如果有,把錯誤信息顯示到另一個控件里,並調用 set_errorHandled() 方法來消除 ASP.NET AJAX 標准錯誤處理行為(顯示錯誤消息框)

function endRequest(sender, args) {
    // Handle the error.
    if (args.get_error() != null) {
        $get("lblError").innerHTML = args.get_error().message;
 
        // Suppress the message box.
        args.set_errorHandled(true);
    }
}

       下圖顯示了使用錯誤處理后的界面效果:

image

 

       ASP.NET 里有 2 個不能在 UpdatePanel 中使用的控件:FileInput 和 HtmlInputFile 控件。但它們仍可以在含有 UpdatePanel 控件的頁面中使用

 

2. 條件更新

       如果頁面上有多個 UpdatePanel 控件且都是完全獨立的,那么可以配置 UpdatePanel.UpdateMode 屬性由 Always 改為 Conditional 以獨立更新它們。即,如果將先前示例中的 3 個 UpdatePanel 控件都設置為 UpdateMode="Conditional" ,那么它們將各自獨立工作。

       從技術面而言,單擊某個按鈕時本例中所有的 Label 都會被更新,但是客戶端只有部分頁面會被刷新以顯示相應的效果。多數情況下,這種差別並不重要。但是,它可能會導致異常,因為每個 Label 的值都被保存到了視圖狀態。這樣,下一次頁面被送到服務器端時,所有 Label 都將被置為最近的值

 

3. 被中斷的更新

       有一點需要說明,如果執行的是一個很耗時的更新,那么更新可能會被其他更新中斷。ASP.NET AJAX 異步回發頁面,因此用戶能夠在回發進行過程中繼續單擊頁面的其他按鈕。ASP.NET AJAX 不允許並發更新,因為它需要確保其他信息(如視圖狀態、會話 cookie 等)保持一致。當一個新的異步回發被啟動后,前一個異步回發就被取消了。

       大部分情況下,這正是你希望的行為。但為了防止用戶打斷進行中的異步回發,可以通過 js 代碼在異步回發的過程中禁用控件。你需要為 beginRequest 事件附加一個事件處理程序,這和錯誤處理程序示例中給 endRequest 事件添加處理程序一樣。

 

4. 觸發器

       如果使用的是條件更新模式,還有其他一些辦法來觸發更新。其中一個辦法就是使用觸發器告訴 UpdatePanel 在頁面上的某個特定控件的特定事件發生時來呈現自身。

       從技術上而言,UpdatePanel 總是使用觸發器。UpdatePanel 中所有控件自動成為 UpdatePanel 的觸發器。在先前的示例中,當 Button.Click 事件發生時,就會發生一次異步回發。然而,這同時也能和所有 Web 控件的默認事件(控件里用 DefaultEvent 特性標記的事件)一起工作,只要那個事件回發了頁面。例如,在 UpdatePanel 里放置一個 TextBox,設置 AutoPostBack 屬性為 True,那么 TextBox.TextChanged 事件就會觸發一次異步回發,同時 UpdatePanel 也將被更新。

       觸發器可以按兩種方式改變這個行為。首先,它允許把觸發器關聯到 Panel 之外的某個控件上。例如頁面其他位置有一個按鈕,這個按鈕本將觸發一次完整的回發,但把它關聯到 UpdatePanel 之后,它將執行一次異步回發。要實現這一設計,只需給 UpdatePanel 增加 AsyncPostBackTrigger,指定要監聽的控件的 ID 以及要觸發刷新的事件:

<div>
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <br />
    <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <div style="background-color: #FFFFDD; padding: 20px">
                <asp:Label ID="Label1" runat="server" Font-Bold="True"></asp:Label>
            </div>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="cmdOutsideUpdate" EventName="Click" />
        </Triggers>
    </asp:UpdatePanel>
    <br />
    <asp:Button ID="cmdOutsideUpdate" runat="server" Text="Update" />
</div>

       EventName 指定要監聽的事件,通常不需指定,默認監聽控件的默認事件,不過,顯式設置是一個好的選擇

       現在單擊按鈕,單擊在客戶端被攔截,並且 PageRequestManager 執行一次異步回發,會發生如下情況:

  • 所有 UpdateMode 設置為 Always 的 UpdatePanel 控件都會被刷新
  • 所有 UpdateMode 設置為 Conditional 並且有針對 cmdOutsideUpdate 的 AsyncPostBackTrigger 的 UpdatePanel 控件都會被刷新
  • 所有 UpdateMode 設置為 Conditional 但不監聽按鈕的 UpdatePanel 不會被刷新

       注意:

  • 為同一 UpdatePanel 添加多個觸發器,其中任一事件都將觸發更新
  • 將同一觸發器添加到多個 UpdatePanel,此時一個事件同時更新所有 UpdatePanel
  • 在條件更新的 UpdatePanel 里混合匹配觸發器和內嵌控件,這樣內嵌控件的所有事件及觸發器里的事件都將引發更新

 

       還可以按另一種方式使用觸發器。假設 UpdatePanel 里有一個按鈕,單擊將觸發一次異步請求和局部更新。如果希望觸發一次完整的頁面回發,只要添加 PostBackTrigger 即可

<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<br />
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <div style="background-color: #FFFFDD; padding: 20px">
            <asp:Label ID="Label1" runat="server" Font-Bold="True"></asp:Label>
            <br />
            <br />
            <asp:Button ID="cmdOutsideUpdate" runat="server" Text="Update" />
        </div>
    </ContentTemplate>
    <Triggers>
        <asp:PostBackTrigger ControlID="cmdOutsideUpdate" />
    </Triggers>
</asp:UpdatePanel>

       這項技術應用不太常見。但是當 UpdatePanel 中的多個控件只執行很少的更新(因此使用異步回發),而有一個控件執行整個頁面的大片更新(因此使用完整回發)時它就很有用了

 

5. 優化 UpdatePanel

       UpdatePanel 有時會因帶寬因素讓人有不好的印象。這是因為 UpdatePanel 幾乎總是傳輸超過你所需要的信息量

       假設你創建一個頁面用於顯示一張表。實現這個頁面最有效的辦法就是使用 ASP.NET AJAX 和 Web 服務。頁面會調用服務器端的一個 Web 服務獲取數據,你需要編寫客戶端的 js 代碼並將它轉換為 HTML。

       與上面的辦法相比,類似的解決方案是使用一個帶有富數據控件的 UpdatePanel,比如 GridView。這個方案讓你可以避免寫大部分的代碼,但是,UpdatePanel 需要請求更多信息來刷新自身。即 Web 服務器需要發送完整的 GridView 呈現內容,和 UpdatePanel 中其他控件的呈現內容以及頁面的完整視圖狀態。這些信息遠多於你采用 Web 服務方式所需的信息量

       為了獲得最佳的 UpdatePanel 性能,了解它的本質是必須的。一些最佳的做法如下:

  • 盡可能的壓縮視圖狀態數據,使用 EnableViewState 屬性關閉那些有變化內容但是不需要用視圖狀態保存的控件的視圖狀態。
  • 在 UpdatePanel 里只放置最少的內容,如上例中,只因放 GridView,而使用觸發器特性將其他可能觸發更新的控件放置在 UpdatePanel 之外。
  • 如果頁面中有多個可更新區域,應放置在獨立的 UpdatePanel 中,並設置 Conditional 模式。Web 服務器應答一個回調時,將呈現的標記發送至 UpdatePanel 和頁面中所有非 Conditional 的 UpdatePanel。如果你僅僅需要為其中一個面板獲取新內容,那就不需要為其他的面板獲取內容。
  • 在使用了多個更新面板的復雜頁面中,考慮用手動的方式刷新它們。要做到這一點,將面板設置為 Conditional 並將 ChildrenAdTriggers 屬性設置為 false。現在唯一能引發刷新動作的就是在一個回調請求期間在一個或更多 UpdatePanel 控件上顯式調用 Update() 方法。

 

       UpdatePanel 方式永遠不會比頁面回發的方式差,因為頁面回發方式總是將整個頁面進行回發。UpdatePanel 方式僅僅在與手工進行 ASP.NET AJAX 編程相比時顯得不夠好

 

 

使用 Timer 定時刷新

       有時候可能希望用戶沒有動作的情況下強制完成一次完整或部分頁面的刷新。例如一個股價的頁面,你會希望定期刷新股價區域。ASP.NET AJAX 的 Timer 控件可以實現這種設計。

       Timer 控件的使用非常簡單,只要把它的 Interval 屬性設置為頁面更新間隔的最大毫秒數即可。不過要知道,Timer 控件可能會大大增加 Web 應用程序的負載並降低它的可擴展性。

       Timer 控件會引發服務器端 Tick 事件,可以在該事件中更新頁面。但此事件的使用也不是強制的,因為定時器觸發時會執行完整的頁面生命周期,也可以響應頁面和其他控件事件,比如 Page.Load 。

       要在部分呈現中使用 Timer 控件,可把頁面的可更新部分包裝到 UpdatePanel 控件並設置 UpdateMode 為 Conditional,定時器觸發時會強制更新:

<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
    ...
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="cmdOutsideUpdate" EventName="Tick" />
    </Triggers>
</asp:UpdatePanel>
<asp:Timer ID="Timer1" runat="server" Interval="60000" ontick="Unnamed1_Tick"></asp:Timer>

       頁面的其他部分保持不變,或者如果要響應其他動作而更新它們,可以把它們包裝到具有不同觸發器的條件更新的 UpdatePanel 控件里。

       如果要停止一個定時器,只要在服務器端代碼里設置 Enabled 屬性為 false 即可。例如下面的代碼在 10 次更新后禁用定時器:

protected void Unnamed1_Tick(object sender, EventArgs e)
{
    int tickCount = 0;
    if (ViewState["TickCount"] != null)
    {
        tickCount = (int)ViewState["TickCount"];
    }
    tickCount++;
    if (tickCount > 10)
    {
        Timer1.Enabled = false;
    }
}

 

 

使用 UpdateProgress 的耗時更新

       ASP.NET AJAX 還包括 UpdateProgress 控件,它和 UpdatePanel 的部分呈現一起工作。UpdateProgress 控件的名稱不太准確。它並不指示進度,而是提供一條等待信息讓用戶知道頁面還在工作,最后的請求還在繼續處理中。

       添加 UpdateProgress 控件后,就能夠指定異步請求開始后顯示某些內容,而這些內容在異步請求結束時又將自動消失。這些內容可以包括固定的消息或圖片。通常,會用一個動畫 GIF 來模擬進度條。

       下面是一個使用了 UpdateProgress 控件的頁面在其生命周期 3 個不同階段的畫面:

image image image

       頁面標記中定義了一個 UpdatePanel,隨后跟着一個  UpdateProgress 。UpdateProgress 控件包含一個取消按鈕,稍后會詳細介紹它:

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <div style="background-color: #FFFFE0; padding: 20px">
            <asp:Label ID="lblTime" runat="server" Font-Bold="True"></asp:Label>
            <br />
            <br />
            <asp:Button ID="cmdRefreshTime" runat="server" Text="Start the Refresh Process" 
            OnClick="cmdRefreshTime_Click" />
        </div>
    </ContentTemplate>
</asp:UpdatePanel>
<br />
 
<asp:UpdateProgress runat="server" ID="updateProgress1">
    <ProgressTemplate>
        <div style="font-size: xx-small">
            Contacting Server ...<img src="wait.gif" alt="Wait" />
            <input id="Button1" onclick="AbortPostBack()" type="button" value="Cancel" 
            style="font-size: xx-small;" />
        </div>
    </ProgressTemplate>
</asp:UpdateProgress>

 

       為了模擬一個較慢的過程,可添加一行延時 10 秒的代碼:

protected void cmdRefreshTime_Click(object sender, EventArgs e)
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
    lblTime.Text = DateTime.Now.ToLongTimeString();
}

       不需要把 UpdateProgress 控件顯式關聯到 UpdatePanel 控件。無論哪一個 UpdatePanel 開始回調,UpdateProgress 都會自動顯示它的 ProgressTemplate。不過,如果頁面很復雜且具有多個 UpdatePanel,可以選擇讓 UpdateProgress 只關注其中某一個。要這么做的話,只要把 UpdateProgress.AssociatedUpdatePanelID 的屬性設為相應的 UpdatePanel 的 ID 即可甚至可以為同一頁面添加多個 UpdateProgress 控件,然后分別關聯到不同的 UpdatePanel

 

取消

       UpdateProgress 控件還支持另一個細節,取消按鈕。用戶單擊取消時,異步回調就會立即被取消,UpdateProgress 的內容將消失,頁面重新回到它的原始狀態。

       添加取消按鈕是一個兩步過程。首先,要添加一段執行取消的 js 代碼。下面的代碼執行這一工作(必須放在 ScriptManager 控件之后):

var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_initializeRequest(InitializeRequest);
 
function InitializeRequest(sender, args) {
    if (prm.get_isInAsyncPostBack()) {
        args.set_cancel(true);
    }
}
 
function AbortPostBack() {
    if (prm.get_isInAsyncPostBack()) {
        prm.abortPostBack();
    }
}

       添加這段代碼后,就可以隨時通過 JavaScript 代碼調用 AbortPostBack() 函數取消回調:

<input id="Button1" onclick="AbortPostBack()" type="button" value="Cancel" />

       典型情況下,會把這個按鈕(或者類似的元素)放到 UpdateProgress 控件的 ProgressTemplate 里,因為它只在回調進行時才被應用

 

       對那些能夠被安全取消的任務提供取消按鈕是有意義的,因為它們不影響外部的狀態。例如,用戶應該能夠取消耗時的查詢。但是,為更新操作提供取消則不是什么好主意,因為服務器會持續執行直到它結束更新,即使客戶端已經停止監聽響應時也是如此。

 

 

管理瀏覽器歷史

       每次頁面執行一次全面回發,Web 瀏覽器就會將其作為一個頁面導航來處理,並在歷史列表中加入一個新項。然而,當你使用 UpdatePanel 來執行異步回發的操作時,歷史列表不會被更新。這一缺點在 ASP.NET AJAX 頁面執行一個復雜的多步驟操作的時候變得尤為明顯。如果用戶稍不注意單擊了“后退”按鈕回退到前一步的話,瀏覽器會跳回到前一個頁面,並且剛才用戶所做的所有工作都會丟失。

       如果已經安裝了 ASP.NET 4,那么有一個合適的解決方法。通過使用 ScriptManager,你可以控制瀏覽器的歷史列表。你可以在需要的時候新增瀏覽器列表項,並在用戶單擊“后退”或“前進”時作出響應,確保頁面狀態是被正確恢復的。

       事實上,與普通 Web 表單的導航相比,這個處理過程工作的相當好,因為不會提示用戶重新填寫之前頁面上的信息。如同大多數 ASP.NET AJAX 特性一樣,歷史列表支持特性給了你一個很好的方式來獲得這些功能,而無需你自己來編寫惱人的復雜代碼並且無需考慮瀏覽器的兼容性。

 

       為了看到 ScriptManager 的瀏覽器歷史特性是如何工作的,可以使用 Wizard 控件來創建一個簡單的例子。下面是一個分為 3 步向導的簡化例子,Wizard 控件包含在 UpdatePanel 中:

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <asp:Wizard ID="Wizard1" runat="server" ...>
            <WizardSteps>
                <asp:WizardStep runat="server" Title="Step 1">
                    This is Step 1.
                </asp:WizardStep>
                <asp:WizardStep runat="server" Title="Step 2">
                    This is Step 2.
                </asp:WizardStep>
                <asp:WizardStep runat="server" Title="Step 3">
                    This is Step 3.
                </asp:WizardStep>
            </WizardSteps>
            ...
        </asp:Wizard>
    </ContentTemplate>
</asp:UpdatePanel>

       這個頁面充分利用了 ASP.NET AJAX 的優勢。現在單擊向導中的鏈接進行導航,頁面不會出現山東。然而,歷史列表也不會有任何變化。

 

1. 新增歷史點

       首先,將 ScriptManager.EnableHistory 屬性設為 true,這將允許使用 ScriptManager 來新增歷史點並對歷史導航作出響應。

       接下來,需要在服務器端代碼中調用 ScriptManager.AddHistoryPoint() 方法將條目加入到歷史列表中。並需要提供下面 3 個參數

  • State:狀態值是一個字符串,用來存儲與歷史點相關的信息。當用戶返回到這個歷史點時,可以獲取到相關的狀態。比如在這個例子中,它會存儲向導的當前步驟索引。
  • Key:鍵值是一個唯一的字符串名字,用來存儲狀態信息。允許頁面存儲多個不沖突的狀態值。這對於擁有多個使用歷史列表的控件的情況很有用。比如,你有兩個 Wizard 控件在同一頁面中,只要每一個 Wizard 都使用了一個不同的鍵值,就可以同時存儲各自當前步驟的索引。
  • Title:頁面標題顯示在瀏覽器窗口頂部並被記錄在歷史列表中。你也可以省略標題參數,此時歷史點將會使用當前的頁面標題。

       本例中,在控件更改到一個新步驟時新增一個歷史點是比較合適的。在向導的 ActiveStepChanged 事件中來做這個動作:

protected void Wizard1_ActiveStepChanged(object sender, EventArgs e)
{
    if ((ScriptManager1.IsInAsyncPostBack) && (!ScriptManager1.IsNavigating))
    {
        string currentStep = Wizard1.ActiveStepIndex.ToString();
        ScriptManager1.AddHistoryPoint("Wizard1", currentStep, "Step " + (Wizard1.ActiveStepIndex + 1).ToString());
    }
}

       在新增歷史點之前,代碼要檢測兩個細節。首先檢測是不是作為一個異步回調操作的一部分進行變更的

       這可以確保不會在下列情況下新增歷史點:

  • 頁面首次被創建且屬性 ActiveStepIndex 第一次被賦值
  • 使用“前進”或“后退”按鈕時,IsNavigating 屬性為 true

       上述情況發生的時候,不需要存儲狀態,相反,需要通過處理 ScriptManager.Navigate 事件來還原它(后面會介紹)。

       當新增歷史點時,代碼使用索引名字 Wizard1(匹配控件的名稱)存儲當前步驟的索引並將頁面標題設置為一個說明性字符串,比如“Step 1”。如果現在運行頁面會看到歷史表中出現新的項,然而單擊一個歷史項則什么也不會發生,這是因為還沒有編寫代碼來還原頁面狀態。

 

2. 還原頁面狀態

       當用戶在歷史列表中前后移動時,ScriptManager 執行一個異步回調來刷新頁面。這時可以處理 ScriptManager.Navigate 事件。當處理 Navigate 事件時,由你決定使用 HistoryEventArgs.State 集合來獲取需要的狀態以及當你首次新增歷史點的時候所使用的狀態值,表明用戶可能返回到了頁面的第一個書簽,這意味着你應當返回到向導控件的初始狀態:

protected void ScriptManager1_Navigate(object sender, HistoryEventArgs e)
{
    if (e.State["Wizard1"] == null)
    {
        // Restore default state of page (for exmaple, for first page).
        Wizard1.ActiveStepIndex = 0;
    }
    else
    {
        Wizard1.ActiveStepIndex = Int32.Parse(e.State["Wizard1"]);
    }
    Page.Title = "Step " + (Wizard1.ActiveStepIndex + 1).ToString();
}

       這段代碼同時更新了頁面標題以與歷史記錄相匹配,因為 ScriptManager 不會自己執行這個操作。

       現在向導控件已經能夠很好的工作了:

image

 

3. 狀態是如何在 URL 中存儲的

       事實上,ScriptManager 將狀態值放在了頁面的 URL 里。它使用 Base64 編碼來混淆這些值並在最后使用散列碼來做校驗。下面是向導例子中的 URL:

http://localhost:41265/WebSite1/BrowserHistory.aspx#&&/wEXAQUHV2l6YXJkMQUBMqLoS2mYPwglQKajqSotKCxCQwjHojLXz4FLVZhH3Q3q

       狀態信息被放置在 URL 符號 # 之后。因此不會打亂任何正在使用的查詢字符串參數,這些參數出現在 # 號前。

 

       歷史狀態使用與視圖狀態同樣的編碼機制。這意味着用戶能毫不費力的獲取到你的狀態值,但是用戶並不能篡改這些值,因為他們不能在沒有 Web 服務器私鑰的情況下產生一個正確的散列碼。

       和視圖狀態不一樣的地方是,你不能加密狀態值。然而,如果你想更清晰的查看 URL,可以移除這些編碼和散列碼。只需設置 ScriptManager.EnableSecureHistoryState 為 false 即可。然后 URL 看起來就像下面這樣:

http://localhost:41265/WebSite1/BrowserHistory.aspx#&&Wizard1=2

       當然,這就完全使得用戶可以通過修改 URL 來更改一個狀態值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM