由ASP.NET所謂前臺呼叫後臺、後臺呼叫前臺想到HTTP——實踐篇(二)

謙行發表於2013-09-24

由ASP.NET所謂前臺呼叫後臺、後臺呼叫前臺想到HTTP——理論篇中描述了一下ASP.NET新手的三個問題及相關的HTTP協議內容,在由ASP.NET所謂前臺呼叫後臺、後臺呼叫前臺想到HTTP——實踐篇(一)中已經解答了關於伺服器怎麼改變頁面元素或“呼叫”JavaScript的問題,為了避免上篇囉囉嗦嗦一大推,這次直奔主題——ASP.NET前臺如何呼叫後臺方法

問題細化

所謂前臺呼叫後臺這種問題的提出無非是因為很多新手受了ASP.NET伺服器端控制元件的“誤導”,你不是說前臺沒有把JavaScript語句、頁面元素什麼的傳到後臺,所以前臺呼叫後臺,或者後臺監視頁面變化是扯淡嘛!那ASP.NET中為什麼伺服器端控制元件一點選就能呼叫後臺制定方法呢?按照你前面的理論即使用了伺服器端控制元件,那麼到了瀏覽器頁面也變成最普通不過的HTML文字了,怎麼還能夠呼叫伺服器端方法呢?!

確實,瀏覽器呈現的是HTML文字,我們先不用拘泥於ASP.NET怎麼做到的前臺“呼叫”後臺,我們可以自己實現一下,不過在此之前我們得明白一些過於Post的問題

簡單聊聊Post

我們在看很多網頁的時候都會看到這樣的內容

<form id="form1" method="post" action="Default.aspx">
        <input type="text" name="wd" />
         <input type="submit" name="test" value="test" />
    </form>

這段HTML文字同學們都能看懂,頁面上有一個form,form內有兩個input,點選submit的時候,頁面form內容會以post方式被提交到Default.aspx這個頁面。

HTTP協議中的Post方法會將所處的form提交到form指定頁面,如果form指定的方法為post(還可能是get等),那麼form內有些資料會一併被提交。

既然不是整個form被提交,那麼哪些資料會被提交呢?

1.input的name和value

2.input type=”submit” 只有被點選的時候才將其本身name與value提交

3.對於多個name相同的input type=”checkbox”,只有選中項的值會被提交(多個value以,連線)

4.對於多個name相同的input type=”radio”,只有選中項的value會被提交(同名的radio只能單選)

5.select的name和被選中的option的vaue(不是option的text)

6.textarea 的name與value

這樣被提交的資料可以在提交的頁面的後臺中以Request.Form[“name”]的方式查到其值

input type=”submit”  “呼叫”後臺方法

知道了form和post的最基本知識,我們就可以模擬ASP.NET的button呼叫後臺方法了,首先準備這樣一個頁面

<form id="form1" method="post" action="Default.aspx">
        <input type="submit" name="test" value="test"/>
    </form>

在後臺新增想呼叫的方法

protected void test_Click(object sender, EventArgs e)
        {
            Response.Write("Button Click!");
        }

很簡單的內容,後臺方法的兩個引數完全可以不用(和事件有關),填上只是為了增加其逼真性,程式碼中有幾個需要注意的地方

1.form的方法需要設為post,這個很明顯。。。

2.form的action設為頁面本身,也就是讓頁面提交給自己

3.input要有name

準備工作做好後就可以模擬JavaScript呼叫後臺方法了,在頁面的 OnLoad方法中寫入程式碼

protected override void OnLoad(EventArgs e)
        {
            if (Request.Form["test"]!=null)
            {
                test_Click(this, new EventArgs());
            }
            base.OnLoad(e);
          
        }

聰明的同學肯定可以看出端倪了,在點選test的時候會提交表單,頁面的OnLoad會自動執行,這時候會判斷提交過來的表單中是否包含test(submit被點選後會被提交,不點選不提交,所以可以根據name可以判斷出哪個按鈕被按了),就這麼簡單,點選submit的時候,預定義的方法就被呼叫了,要是頁面上有很多submit,這時候就需要if-else或者switch或者for這樣的東西來判斷到底是哪個submit被點選了,然後呼叫對應方法。

asp: button

對比一下,ASP.NET正好用,首先人家的from是這樣的

<form id="form1" runat="server">
</form>

人家自動就給render成這樣了

image

我們也不需要自己在OnLoad中新增if-else,只要在control的屬性中新增OnClick=”XXX”就可以了,我沒看過.NET原始碼,但是我大膽的揣測,雖然不用再OnLoad裡if-else了,但應該也是類似的方式處理。

不自動提交表單的怎麼辦

細心的同學可能要問了,你這麼做是可以,但是依賴於表單提交,我們知道並不是所有的頁面元素都會點一下就提交表單,像控制元件asp: DropDownList的OnSelectIndexChanged事件,人家也會呼叫回臺方法,那是怎麼做的?

其實非常簡單,對於這種需求,仍然可以用表單提交的方式處理,不能自動提交表單,那麼我們可以用JavaScript幫助其提交,看個select change “呼叫”後臺函式的例子,首先像這樣修改頁面,新增一個select

<form id="form1" method="post" action="Default.aspx">
    <input type="hidden" id="__param" name="__param" />
    <input id="test" name="test" type="submit" value="test" />
    <select id="select1" name="select1">
        <option value="0">0</option>
        <option value="1">1</option>
        <option value="2">2</option>
    </select>
    <script type="text/javascript">
        document.getElementById('select1').onchange = function () {
            document.getElementById('__param').value = this.name;
            document.getElementById('form1').submit();
        };
    </script>
    </form>

我們除了select元素還新增了兩個內容,一個input type=”hidden”的隱藏元素,一段JavaScript指令碼,在JavaScript指令碼中對select的onchange繫結了一個方法,當select選擇項變化的時候,把其name賦值給隱藏域的value,然後提交表單,由於隱藏域可定會被提交,這樣我們可以同樣在頁面的Onload中判斷誰的onchange觸發了,然後呼叫預定義方法,後臺程式碼是這樣的

protected override void OnLoad(EventArgs e)
        {
            string param= Request.Form["__param"];
            if (Request.Form["test"]!=null)
            {
                test_Click(this, new EventArgs());
            }
            else if (param == "select1")
            {
                select_Change(this, new EventArgs());
            }
            base.OnLoad(e);
        }

        protected void test_Click(object sender, EventArgs e)
        {
            Response.Write("Button Click!");
        }

        protected void select_Change(object sender, EventArgs e)
        {
            string newValue = Request.Form["select1"];
            Response.Write("Seclect value chaned to " + newValue);
        }

是不是很簡單呢?先判斷是不是submit,不是的話判斷隱藏域,命中後呼叫預定義方法。

細心的同學仍然會發現問題,要是一個元素上同時又兩個事件怎麼辦?比如還想對這個select新增cilck事件怎麼辦,這時候我們可以修改隱藏域的值,不僅記錄name,還記上事件型別,類似這樣

document.getElementById('select1').onchange = function () {
            document.getElementById('__param').value = this.name+"$ onchange";
            document.getElementById('form1').submit();
        };

        document.getElementById('select1').onclick = function () {
            document.getElementById('__param').value = this.name+"$ onclick";
            document.getElementById('form1').submit();
        };

然後在後臺判斷的時候,我們可以先把引數按$分割為兩個字串,分別判斷元素與事件型別,當然我們也可以再新增一個隱藏域,用來記事件型別,大同小異。

還有同學回問,如果我在客戶端同樣寫了select的onclick,不就把你的程式碼覆蓋了嗎?這樣我就不能post了,對於這個問題我們大可以換種方式繫結事件處理程式,用addEventListener(attachEven)的方式,這樣post的程式碼和自己寫的客戶端的就可以同時存在了,這樣

var select = document.getElementById('select1');
        select.onclick = function () {
            //
            //
            //
        };

        select.addEventListener('clcik', function () {//瀏覽器相容性問題要處理,這裡就不演示了
            document.getElementById('__param').value = this.name + "$ onclick";
            document.getElementById('form1').submit();
        }, false);

 

asp: DropDownList

我們可以看看ASP.NET是怎麼處理的,在頁面上寫一個DropDownList,設起AutoPostBack屬性為true

<form id="form1" runat="server">
        <asp:DropDownList ID="ddlTest" runat="server" AutoPostBack="true">
        <asp:ListItem>1</asp:ListItem>
        <asp:ListItem>2</asp:ListItem>
        <asp:ListItem>3</asp:ListItem>
    </asp:DropDownList>
    </form>

生成的頁面原始碼是這樣的

<html xmlns="http://www.w3.org/1999/xhtml"><head><title>

</title><style type="text/css"></style></head>
<body cz-shortcut-listen="true">
    <form method="post" action="Default.aspx" id="form1">
<div class="aspNetHidden">
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="">
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="">
</div>

<script type="text/javascript">
//<![CDATA[
var theForm = document.forms['form1'];
if (!theForm) {
    theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
//]]>
</script>


<div class="aspNetHidden">

    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdAAWUpg6MZl3t8/pBYubwqWOJqSwDGOXu15HYfC2efhAyHc1gIfz8erLrnvHjMLCTGHuRsEl8lEzJ1SVLtM7lgCLfggLVPRtDk70yN7TcfPOwwBRLeEs10HSMSLT9zq5aYmmjMFX5QrdQAuJDrw+EXFTJ">
</div>
        <select name="ddlTest" onchange="javascript:setTimeout('__doPostBack(\'ddlTest\',\'\')', 0)" id="ddlTest">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>

</select>
    </form>


</body></html>

 

主要是這個方法,認真讀讀是不是和我們剛才寫的類似呢,人家傳了兩個隱藏域,一個用來是Target,一個用來及引數

onchange="javascript:setTimeout('__doPostBack(\'ddlTest\',\'\')', 0)"

 

function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

 

未完待續,與君共勉

通過上面例子分析可以看得出來,JavaScript根本沒有呼叫後臺函式,只是提交了含有預設引數的表單,伺服器根據引數呼叫後臺方法,生成特定HTML,所謂前臺呼叫後臺也就這麼回事兒。

有一個問題值得思考,為什麼這種很想當然的做法PHP程式設計師可以輕鬆想到,很多.NET初級程式設計師卻不行,甚至有時候我們都已經在使用了,比如根據Url的QueryString來呼叫不同方法,或進行不同處理,卻沒有進一步想到更常用的Button、DropDownList是怎麼做到的?還是那句話,.NET程式設計師當自勉,不要被.NET的易用性慣壞,多學習原理知識,不要過分滿足於做出來,而要多追求為什麼,這樣才能不斷進步。

相關文章