不知你有沒有遇過以下的錯誤?

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方法註冊回傳或回呼資料,以進行驗證。

知道它是怎麼冒出來的嗎? 或者你每次都是依著提示將enableEventValidation設成false就當作沒事? 這裡花一點篇幅介紹一下。

首先,這個錯誤源自ASP.NET 2.0所加入的新功能,主要的著眼於防範資料在傳送前被駭客篡改,以強化資安。例如: 你有個下拉選單,決定會員要加到哪個群組,如果駭客戶得知還有個系統管理者群組,偷偷把它加進選項裡,那那那... (不可否認,這類防護挺煩人的,但很不幸地,"用麻煩換資安"一向是鐵律,唉~~~)

我最常看到的情境,多半都是開發者用AJAX或純Javascript方式,依使用者的需求動態變更了下拉選單的內容,接著在送出表單時,噹! Exception~~ 這都肇因於ASPX不認得這些Client-Side動態加入的下拉選項,抱著寧可錯殺一百不可錯放一個的心態,質疑這是遭駭客篡改的結果,用Exception阻擋"入侵"。

我用一個簡單的例子來說明:

<%@ Page Language="C#" AutoEventWireup="true"%>
<html><head><title>Event Validation Test</title></head>
<script type="text/C#" runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            DropDownList1.Items.Add(new ListItem("1"));
            DropDownList1.Items.Add(new ListItem("2"));
        }
    }
</script> 
<body onload="init();">
    <form id="form1" runat="server">
    <div>
        <asp:DropDownList ID="DropDownList1" runat="server">
        </asp:DropDownList>
        <asp:Button ID="Button1" runat="server" Text="Button" />
    </div>
    <script type="text/javascript">
    function init() {
        var sel = document.getElementById("DropDownList1");
        sel.options[sel.options.length] = new Option("3");
    }
    </script>
    </form>
</body>
</html>

在上面的例子中,我們在Server-Side為DropDownList1加上選項1, 2,在Client-Side以Javascript加上選項3。實際執行時,如果你選1, 選2,按Button1時一切正常,若選3再按Button1,就會見到前述的Exception。問題出在ASP.NET並不認得我們在Client-Side另行加入的選項3。

除了修改enableEventValidation停用整個網頁的事件檢核防護,錯誤訊息中提到的另一個做法是用ClientScriptManager.RegisterForEventValidation讓ASP.NET認得"3"這個在Client-Side偷生的小孩,寫法如下:

<script type="text/C#" runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            DropDownList1.Items.Add(new ListItem("1"));
            DropDownList1.Items.Add(new ListItem("2"));
        }
    }
    //在Render()加入特別申報邏輯
    protected override void Render(HtmlTextWriter writer)
    {
        //另外為"3"報戶口
        ClientScript.RegisterForEventValidation(
            DropDownList1.UniqueID,
            "3");
        //不要忘了呼叫原來的Render()
        base.Render(writer);
    }
</script> 

經過這番修改,選3後再按Button1便不再出錯。不過你應該也會注意到了,Postback後,下拉選單並不會留在3上,畢竟在ASP.NET的認知中,DropDownList1的選項只有1, 2而已。

以上的做法,雖然可以讓"3"就地合法,但如果選項內容是透過AJAX在執行期間呼叫其他Web Page或Web Service取得,在開發ASPX時根本無從得知,則此一解決方式豈不淪為空談? 沒錯! 用EnableEventValidation排除無關資安的情境看來較為最簡便,但每次一設就是整個Web Page,難道不能只針對某些會動態變更的Server Control設定? 不行!

的確,在這個資安議題上,看來沒有簡單有效的折衷之道。有一種做法是開發自訂元件,以不宣告SupportsEventValidation來避開EventValidation,要為此要另外開發元件難稱簡便。我個人實務上慣用的做法是: 既然是AJAX動態管理的下拉選單,何苦執著於要建成DropDownList? 反正前面說過,DropDownList選了動態加入的選項,在Postback時也無法享用自動停在該選項的方便性,還不如直接使用HTML的<SELECT>就好,再設法將選取結果同步到特定HIDDEN INPUT跟Server-Side溝通,這樣就可省去跟資安機制拼命的困擾!

雖然EventValidation會帶來一些困擾,基於資安的考量,還是很鼔勵大家靠它來杜絕駭客動手腳,儘量不要任意停用。


Comments

# by 小陳

請問一下,假使DropDownList都是由資料庫給值 而當我選擇了DDL的某一項資料會觸發一個TEXTBOX的行為 這樣引起Invalid postback or callback argument 我該如何處理? 拜託幫忙了

# by Jeffrey

to 小陳,你有用Javascript動態增減DDL的內容嗎? 恐怕要多提供一些程式寫法,我們才能探索問題所在。另外,你也可試著在MSDN論壇提問看看,但記得要多提供一點問題的細節(例如: 會出錯的程式範例),大家比較能幫得上忙。ASP.NET討論區: http://social.msdn.microsoft.com/Forums/zh-TW/236/threads

# by lsk

還是可以停留在3,透過Request.Form["DropDownList1"]就可以抓到選取的value,再用javascript去設定selectedIndex就好了 因為你的例子是body onload="init();"比較特別一點 修改後,程式碼如下 protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { DropDownList1.Items.Add(new ListItem("1")); DropDownList1.Items.Add(new ListItem("2")); } string value = Request.Form["DropDownList1"]; string script = "function init() {\n"; script += " var sel = document.getElementById(\"DropDownList1\");\n"; script += " sel.options[sel.options.length] = new Option(\"3\");\n"; script += " for(var i=0;i<sel.options.length;i++){\n"; script += " if(sel.options[i].text=='" + value + "')sel.options[i].selected=true;\n"; script += " }\n"; script += "}"; ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "", script, true); }

# by jerry

我在做搜尋關鍵字時,將資料傳遞到另一個網頁,然後比對資料庫標題欄位,如果有的話畫把商品明細呈現在GridView上,就這樣單純的資料繫結,就出現這個錯誤訊息,請問版大該如何解?「原本在學校跑是正常無誤的(VS2012)」,家裡安裝的是VS2013就會出現這錯誤訊息,作業系統環境一樣都是Win8,目前我是先暫時設為false再研究怎麼解決。 看很多人說GridView在DataBind時要寫在if(!IsPostBack)中可解,但加了之後仍然出現一樣訊息,想請教版大如果要像您一樣在Render()加入特別申報邏輯,該如何寫呢? ClientScript.RegisterForEventValidation(GridView.UniqueID); 這樣嗎?但UniqueID後面參數我不知道要帶甚麼?因為我的情況跟您的不太一樣說...

# by Jeffrey

to jerry, 感覺你提到的狀況跟文章所說的JavaScript動態修改欄位內容不太相同。 依我的直覺,問題跟"傳遞到另一個網頁"有關,做法有很多種: QueryString, Server.Transfer, PostBack到其他網頁... 不知你使用的是哪一種? 恐怕得再提供更多資訊(如果包含程式碼細節更好)才能判斷。

# by x

請問如果需要在GridView 裡面做兩個DropDownList 的連動更新該如何做? 目前嘗試過由後端將資料用XML打包給前端進行資料連動更新 但是不知道如何正確的在Render()加入特別申報邏輯 當DropDownList 的Name 變更時,替換同行Empno的內容已由JavaScript動態修改實現了 以下是申報時所用的code protected override void Render(System.Web.UI.HtmlTextWriter writer) { foreach (GridViewRow gvr in this.Choose_GV.Rows) { DropDownList this_DropDownList = (DropDownList)gvr.Cells[7].FindControl("Name"); this_DropDownList.Attributes.Add("onchange", "ch(\"Choose_GV_ctl0" + (gvr.RowIndex + 2).ToString() + "_Empno\",this.value);"); DropDownList this_DropDownList_Empno = (DropDownList)gvr.Cells[7].FindControl("Empno"); for (int i = 0; i < Target_Empno.Rows.Count; i++) { //ClientScript.RegisterForEventValidation(this_DropDownList_Empno.UniqueID, Target_Empno.Rows[i]["key"].ToString()); ClientScript.RegisterForEventValidation(this_DropDownList_Empno.UniqueID, Target_Empno.Rows[i]["value"].ToString()); } } }

# by Jeffrey

to x, 我會選擇讓事情單純一點,將動態變化的下拉選單改成純粹的<select>不要用<asp:DropDownList> WebControl,再將其結果填入<asp:TextBox>或<input type="hidden" runat="server">。

# by x

原來如此,其實曾往此方向思考過,但因不熟GridView 應用而作罷。 使用控制項就變得很難選擇該走他的規則,還是嘗試混搭繞開複雜的部份...

# by QTE

您好,最近也遇到此問題 那如果我直接從<asp:dropdownlist>改成<select>是不是對於這個下拉選單就沒有了檢查機制了

# by Jeffrey

to QTE,是的,改為<select>變成純前端的元素,Server就管不到了。

Post a comment