昨天一位網友提出了這麼一個問題:動态建立Disabled的文本輸入框,頁面回發時修改其文本屬性無效:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5CMxMDNzEjYklTYlFGMwEDOxYzXwETMxcTMyAzLcFTMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL0M3Lc9CX6MHc0RHaiojIsJye.jpg)
為了更清楚的分析和解決問題,我們先把代碼和運作效果展示一下。
前台代碼很簡單:
1. 一個表單SimpleForm1,背景會動态添加控件到這裡面來
2. 一個按鈕,點選回發
上面是簡化後的代碼:
1. 一個Page_Init事件,在其中動态建立一個文本輸入框,并添加到SimpleForm1中。
2. 一個按鈕點選事件,找到動态建立的文本輸入框,并修改它的值為最新的時間。
頁面顯示效果:
實際運作時發現,點選【指派】按鈕時,頁面的文本輸入框的值并未改變。
最開始這位網頁也是懷疑 Enabled=false 的問題,是以我就先把代碼改為:
測試發現沒問題了:
好像還真是這麼回事,調試後發現,如果文本輸入框被禁用了,文本輸入框的值是不會送出到背景的,對比一下。
啟用文本輸入框:
禁用文本輸入框:
那是不是說,頁面回發時,隻要我們把禁用的文本輸入框值也回發到背景,不就解決問題了。
是這樣的嗎?
這麼做的确能解決這個問題。因為 ASP.NET 會查找請求參數中的回發資料,并更新控件的值。
問題的關鍵是,這麼做合規嗎?是否合乎HTML的規範,顯示不是的。
參考下這篇文章:https://stackoverflow.com/questions/7357256/disabled-form-inputs-do-not-appear-in-the-request
HTML5 Spec中明确定義了 disabled 控件的行為:
禁用的控件不會接收焦點
禁用的控件在Tab導航中會自動跳過
禁用的控件不會出現在表單送出的請求參數中
換一種思路,我們測測其他控件,将TextBox換成Label,發現同樣的問題:
測試後發現,在點選按鈕時,兩個控件的值都沒有改變。
因為 Label 控件不算是使用者可修改的表單字段,是以表單送出時根本不會将其資料放在請求參數中。說白了這個邏輯和禁用的文本輸入框還是很類似的。
調試了一圈,發現要想解決這個問題,還是要回到動态建立控件上來。
9 年前我就寫過一篇文章,來回顧一下。
9年前的這篇文章對動态建立控件進行了深入的講解:javascript:void(0)
其中 ASP.NET WebForms 頁面的生命周期還是值得我們再次學習一遍:
我們主要關心的是前面 4 個階段,9 年後我們再來回味一下,能感覺到 WebForms 的底層設計還是很巧妙的:
執行個體化階段:處理頁面标簽定義和 Page_Init 中代碼
回發 - 加載視圖狀态:查找頁面中的隐藏字段 __VIEWSTATE,并更新控件屬性
回發 - 加載回發資料:查找請求參數中的資料,并更新控件屬性(本例中從請求參數中找文本輸入框SimpleForm1$Text1的值)
加載階段:執行 Page_Load 中的代碼
上面看起來也很清楚,頁面第一次加載時,執行如下過程:
執行個體化:頁面标簽 + Page_Init
加載:Page_Load
頁面回發時,執行如下過程:
加載視圖狀态:從頁面隐藏字段 __VIEWSTATE 中查找
加載回發資料:從目前 HTTP 的請求參數中查找
如果對上面幾個階段不陌生,那我就要問一個問題了:
這裡有一個非常關鍵的關鍵點,在 9 年前的那篇文章中我反複提到:
當控件完成【加載視圖狀态階段】後,就會立即開始跟蹤其視圖狀态的改變,之後任何對其屬性的改變都會影響最終的控件視圖狀态。
這句話另一層含義就是:在【加載視圖狀态階段】之前,對控件屬性的改變不會被跟蹤,也不會記錄到 __VIEWSTATE 中來。
更加嚴格的說,上面的說法有點問題,因為頁面第一次加載時沒有【加載視圖狀态階段】,更精确的描述:
頁面第一次加載時,将控件添加到層次結構樹之後,即開始跟蹤狀态變化,并記錄到 __VIEWSTATE
頁面回發時,在【加載視圖狀态階段】之後,即開始跟蹤狀态變化,并記錄到 __VIEWSTATE
如果控件是在【加載視圖狀态階段】之後添加到層次結構樹的話,則在将控件添加到層次結構樹之後開始跟蹤狀态變化,并記錄到 __VIEWSTATE
我們再來看一眼最初的代碼:
可以發現問題了:
頁面第一次加載時
在 Page_Init 中首先對Text1指派:Text1.Text="2021-10-29 11:10:00"
但是這個指派操作是在添加到層次結構樹之前進行的,是以Text1.Text值不會被記錄到 __VIEWSTATE 中
10分鐘之後,頁面回發時
在 Page_Init 中首先對Text1指派:Text1.Text="2021-10-29 11:20:00"
加載視圖狀态時,從 __VIEWSTATE 中回複 Text1 之前的狀态,但是 __VIEWSTATE 中沒有找到
經過上面的詳細分析,可以看出,頁面第一次加載時,将 Text1 設定為 11:10,頁面回發時按道理是應該保持這個值的,但是卻被錯誤的更新為了 11:20 !
怎麼為動态添加控件指派呢?我們也提出了一個最佳實踐:
把上面的邏輯搞清楚了,解決問題就不難了:
運作效果: