【答客問】JavaScript修改WebForm DropDownList選項
5 |
【案例】
某個ASP.NET WebForm網頁,加入JavaScript動態修改欄位,送出表單時出現錯誤:
(英文版) Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.
(中文版) 無效的回傳或回呼引數。已在組態中使用 <pages enableEventValidation="true"/> 或在網頁中使用 <%@ Page EnableEventValidation="true" %> 啟用事件驗證。基於安全性理由,這項功能驗證回傳或回呼引數是來自原本呈現它們的伺服器控制項。如果資料為有效並且是必需的,請使用 ClientScriptManager.RegisterForEventValidation 方法註冊回傳或回呼資料,以進行驗證。
追查後發現問題出在JavaScript為<asp:DropDownList>動態新增了選項。
ASP.NET 2.0+為避免原本Server端管控的下拉選單被駭客加料塞入非預期值,故DropDownList會記下原有選項組合,一旦Client讓該欄位送回選項以外的值,便會觸發錯誤。解決之道是透過RegisterForEventValidation()方法向ASP.NET預告該欄位可能出現的值,如此資料送回時比對吻合就能通過驗證。
用一個範例來說明: WebForm網頁上有一個DropDownList,預先宣告C#及VB.NET兩個ListItem,另外再透過JavaScript加入Ruby及JavaScript兩個新<option>。至於Server端,我們需覆寫Render()方法,加入ClientScript.RegisterForEventValidation(),但此處只註冊JavaScript,以觀察Ruby及JavaScript兩個選項產生的結果。程式碼如下:
<%@ Page Language="C#" %>
<!DOCTYPE html>
<script runat="server">
protected void btn_Click(object sender, EventArgs e)
{
Response.Write(ddl.SelectedValue + " " + Request["ddl"]);
Response.End();
}
//為了讓Cient新增的DropDownList選項被接受,Page需覆寫Render方法
//註冊前端可能動態加入的新選項
protected override void Render(HtmlTextWriter writer)
{
ClientScript.RegisterForEventValidation(
ddl.UniqueID, "JavaScript");
base.Render(writer);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Test DropDownList Change in Client Side</title>
<script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.0.min.js"></script>
<script >
$(function () {
//動態增加兩個新選項,注意: 選Ruby送出會出錯,選JavaScript卻OK
$("#ddl")
.append("<option value='Ruby' selected>Ruby</option>")
.append("<option value='JavaScript'>JavaScript</option>");
});
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:DropDownList ID="ddl" runat="server">
<asp:ListItem>C#</asp:ListItem>
<asp:ListItem>VB.NET</asp:ListItem>
</asp:DropDownList>
<asp:Button ID="btn" runat="server" OnClick="btn_Click" Text="Submit" />
</form>
</body>
</html>
執行結果如上圖,下拉選單會出現四個選項,選Ruby按Submit會出錯(如下圖);選C#、VB.NET、JavaScript按Submit則不會出錯。
但是還有一個問題: 選取JavaScript雖然不會出錯,Request["ddl"]也能取得選取結果--"JavaScript",但透過ddl.SelectedValue查到的卻是C#,表示DropDownList.Selected*屬性只接受Server端建立的選項,應用時需留意此一限制。
如此看來,若只是為了在Server端及Client端都能增減下拉選單選項,可以不用DropDownList,改用<select runat="server">更簡單,且後端取值應以Request["…"]為準。
<%@ Page Language="C#" %>
<!DOCTYPE html>
<script runat="server">
protected void btn_Click(object sender, EventArgs e)
{
Response.Write(Request["ddl"]);
Response.End();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Test DropDownList Change in Client Side</title>
<script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.0.min.js"></script>
<script >
$(function () {
//動態增加兩個新選項
$("#ddl")
.append("<option value='Ruby' selected>Ruby</option>")
.append("<option value='JavaScript'>JavaScript</option>");
});
</script>
</head>
<body>
<form id="form1" runat="server">
<select name="ddl" id="ddl" runat="server">
<option>C#</option>
<option>VB.NET</option>
</select>
<asp:Button ID="btn" runat="server" OnClick="btn_Click" Text="Submit" />
</form>
</body>
</html>
Comments
# by 羽落
但如此一來,資料的安全性及正確性就必須要在後端確認了,對嗎?
# by Jeffrey
to 羽落,無論前端加了多少層檢核防護,後端一定要再檢核驗證一次。前端驗證只能用於改善無效資料送回後端才被拒絕的無效率,無法阻擋惡意破解及篡改,如資訊安全及資料正確事關重大,真正的關卡只能設在後端。
# by 羽落
是的,我了解,謝謝Jeffrey大。
# by 老皮
請問一下大大,若是在WebFrom的控制項中我想要多設定一些相關的資訊,有沒有類似在WinForm中有Tag的屬性可以讓我暫時設定相關的資料?或是說有什麼方式可以存放控制項的相關資訊?謝謝
# by Jeffrey
to 老皮, 我想到較相近的機制是ViewState(參考: http://www.dotblogs.com.tw/marcus116/archive/2011/05/24/25923.aspx),它是全Page共用,不像Tag依附在特定Control,但可透過Key/Value概念管理。但要留意,ViewState會被編碼後成為網頁的一部分反覆傳送,要避免放入大型資料拖累傳輸效能。