新聞中心
雖然Asp.net WebForms框架為了幫助我們簡(jiǎn)化開發(fā)工作,做了很完美的封裝,讓我們只需要簡(jiǎn)單地使用服務(wù)端控件就可以直接操作那些 HTML表單元素了。但我認(rèn)為了解一些基礎(chǔ)的東西,可以使我們不必束縛在WebForms框架上,以及遇到一些奇怪問(wèn)題時(shí), 可以更從容地解決它們。

我們提供的服務(wù)有:成都網(wǎng)站建設(shè)、網(wǎng)站制作、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、湟源ssl等。為上千余家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的湟源網(wǎng)站制作公司
今天,我將和大家來(lái)聊聊表單,這個(gè)簡(jiǎn)單又基礎(chǔ)的東西。我將站在HTML和單純的Asp.net框架的角度來(lái)解釋它們的工作方式, 因此,本文不演示W(wǎng)ebForms服務(wù)器控件的相關(guān)內(nèi)容。
簡(jiǎn)單的表單,簡(jiǎn)單的處理方式
好了,讓我們進(jìn)入今天的主題,看看下面這個(gè)簡(jiǎn)單的HTML表單。
客戶名稱:
客戶電話:
在這個(gè)HTML表單中,我定義了二個(gè)文本輸入框,一個(gè)提交按鈕,表單將提交到Handler1.ashx中處理,且以POST的方式。
注意哦,如果我們想讓純靜態(tài)頁(yè)面也能向服務(wù)器提交數(shù)據(jù),就可以采用這樣方式來(lái)處理:將action屬性指向一個(gè)服務(wù)器能處理的地址。
說(shuō)明:當(dāng)我們使用WebForms的服務(wù)器表單控件時(shí),一般都會(huì)提交到頁(yè)面自身來(lái)處理(action屬性指向當(dāng)前頁(yè)面), 這樣可以方便地使用按鈕事件以及從服務(wù)器控件訪問(wèn)從瀏覽器提交的控件輸入結(jié)果。
如果在URL重寫時(shí),希望能在頁(yè)面回傳時(shí)保持URL不變,即:action為重寫后的URL,那么可以Page類中執(zhí)行以下調(diào)用:
- Form.Action = Request.RawUrl; // 受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1
好了,我們?cè)倩氐角懊婺莻€(gè)HTML表單,看一下如果用戶點(diǎn)擊了“提交”按鈕,瀏覽器是如何把表單的內(nèi)容發(fā)出的。 在此,我們需要Fiddler工具的協(xié)助,請(qǐng)?jiān)谔峤槐韱吻皢?dòng)好Fiddler。我將這個(gè)表單的提交請(qǐng)求過(guò)程做了如下截圖。
上圖是將要提交的表單的輸入情況,下圖是用Fiddler看到的瀏覽器發(fā)出的請(qǐng)求內(nèi)容。
在這張圖片中,我們可以看到瀏覽器確實(shí)將請(qǐng)求發(fā)給了我前面在action中指定的地址,且以POST形式發(fā)出的。 表單的二個(gè)控件的輸入值放在請(qǐng)求體中,且做了【編碼】處理,編碼的方式用請(qǐng)求頭【Content-Type】說(shuō)明, 這樣,當(dāng)服務(wù)端收到請(qǐng)求后,就知道該如何讀取請(qǐng)求的內(nèi)容了。 注意:表單的數(shù)據(jù)是以name1=value1&name2=value2 的形式提交的,其中name,value分別對(duì)應(yīng)了表單控件的相應(yīng)屬性。
我們還可以在Fiddler中,將視圖切換到WebForms選項(xiàng)卡,這樣能更清楚地只查看瀏覽器提交的數(shù)據(jù),如下圖。
看了客戶端的頁(yè)面和請(qǐng)求的內(nèi)容,我們?cè)賮?lái)看看在服務(wù)端如何獲取瀏覽器提交的表單的輸入吧,代碼如下:
- string name = context.Request.Form["CustomerName"];
- string tel = context.Request.Form["CustomerTel"];
代碼很簡(jiǎn)單,直接根據(jù)表單控件的name屬性訪問(wèn)Request.Form就可以了。
#p#
表單提交,成功控件
我們?cè)賮?lái)看一下瀏覽器是如何提交表單的,或者說(shuō),瀏覽器在提交表單時(shí),要做哪些事情。
瀏覽器并不是將所有的表單控件全部發(fā)送到服務(wù)器的,而是會(huì)查找所有的【成功控件】,只將這些成功控件的數(shù)據(jù)發(fā)送到服務(wù)端, 什么是成功控件呢?
簡(jiǎn)單地來(lái)說(shuō),成功控件就是:每個(gè)表單中的控件都應(yīng)該有一個(gè)name屬性和”當(dāng)前值“, 在提交時(shí),它們將以 name=value 的形式做為提交數(shù)據(jù)的一部分。
對(duì)于一些特殊情況,成功控件還有以下規(guī)定:
1. 控件不能是【禁用】狀態(tài),即指定【disabled="disabled"】。即:禁用的控件將不是成功控件。
2. 如果一個(gè)表單包含了多個(gè)提交按鍵,那么僅當(dāng)用戶點(diǎn)擊的那個(gè)提交按鈕才算是成功控件。
3. 對(duì)于checkbox控件來(lái)說(shuō),只有被用戶勾選的才算是成功控件。
4. 對(duì)于radio button來(lái)說(shuō),只有被用戶勾選的才算是成功控件。
5. 對(duì)于select控件來(lái)說(shuō),所有被選擇的選項(xiàng)都做為成功控件,name由select控件提供。
6. 對(duì)于file上傳文件控件來(lái)說(shuō),如果它包含了選擇的文件,那么它將是一個(gè)成功控件。
此外,瀏覽器不會(huì)考慮Reset按鈕以及OBJECT元素。
注意:
1. 對(duì)于checkbox, radio button來(lái)說(shuō),如果它們被確認(rèn)為成功控件,但沒(méi)有為控件指定value屬性, 那么在表單提交時(shí),將會(huì)以"on"做為它們的value
2. 如果在服務(wù)端讀不到某個(gè)表單控件的值,請(qǐng)檢查它是否滿足以上規(guī)則。
提交方式:在前面的示例代碼中,我為form指定了method="post",這個(gè)提交方法就決定了瀏覽器在提交數(shù)據(jù)時(shí),通過(guò)什么方式來(lái)傳遞它們。
如果是【post】,那么表單數(shù)據(jù)將放在請(qǐng)求體中被發(fā)送出去。
如果是【get】,那么表單數(shù)據(jù)將會(huì)追加到查詢字符串中,以查詢字符串的形式提交到服務(wù)端。
建議:表單通常還是以post方式提交比較好,這樣可以不破壞URL,況且URL還有長(zhǎng)度限制。
數(shù)據(jù)的編碼:前面我將瀏覽器的請(qǐng)求細(xì)節(jié)用Fiddler做了個(gè)截圖,從這個(gè)圖中我們可以看到:控件輸入的內(nèi)容并不是直接發(fā)送的, 而是經(jīng)過(guò)一種編碼規(guī)則來(lái)處理的。目前基本上只會(huì)只使用二種編碼規(guī)則:application/x-www-form-urlencoded 和 multipart/form-data , 這二個(gè)規(guī)則的使用場(chǎng)景簡(jiǎn)單地說(shuō)就是:后者在上傳文件時(shí)使用,其它情形則使用前者(默認(rèn))。
這個(gè)規(guī)則是在哪里指定的呢? 其實(shí)form還有個(gè)enctype屬性,用它就可以指定編碼規(guī)則,當(dāng)我在VS2008寫代碼時(shí),會(huì)有以下提示:
按照我前面說(shuō)過(guò)的編碼規(guī)則選擇邏輯,application/x-www-form-urlencoded做為默認(rèn)值,所以,一般情況下我們并不用顯式指定。 除非我們要上傳文件了,那么此時(shí)必須設(shè)置enctype="multipart/form-data"
好了,說(shuō)了這么一大堆理論,我們?cè)賮?lái)看一下瀏覽是如何處理表單數(shù)據(jù)的。這個(gè)過(guò)程大致分為4個(gè)階段:
1. 識(shí)別所有的成功控件。
2. 為所有的成功控件創(chuàng)建一個(gè)數(shù)據(jù)集合,它們包含 control-name/current-value 這樣的值對(duì)。
3. 按照f(shuō)orm.enctype指定的編碼規(guī)則對(duì)前面準(zhǔn)備好的數(shù)據(jù)進(jìn)行編碼。編碼規(guī)則將放在請(qǐng)求中,用【Content-Type】指出。
4. 提交編碼后的數(shù)據(jù)。此時(shí)會(huì)區(qū)分post,get二種情況,提交的地址由form.action屬性指定的。
#p#
多提交按鈕的表單
用過(guò)Asp.net WebForms框架的人可能都寫過(guò)這樣的頁(yè)面:一個(gè)頁(yè)面中包含多個(gè)服務(wù)端按鈕。處理方式嘛, 也很簡(jiǎn)單:在每個(gè)按鈕的事件處理器寫上相應(yīng)的代碼就完事了,根本不用我們想太多。
不過(guò),對(duì)于不理解這背后處理過(guò)程的開發(fā)人員來(lái)說(shuō),當(dāng)他們轉(zhuǎn)到MVC框架下,可能會(huì)被卡住:MVC框架中可沒(méi)有按鈕事件! 即使用不用MVC框架,用ashx通用處理器的方式,也會(huì)遇到這種問(wèn)題,怎么辦?
對(duì)于這個(gè)問(wèn)題,本文將站在HTML角度給出二個(gè)最根本的解決辦法。
方法1:根據(jù)【成功控件】定義,我們?cè)O(shè)置按鈕的name,在服務(wù)端用name來(lái)區(qū)分哪個(gè)按鈕的提交:
HTML代碼
客戶名稱:
客戶電話:
服務(wù)端處理代碼
- // 注意:我們只要判斷指定的name是否存在就可以了。
- if( string.IsNullOrEmpty(context.Request.Form["btnSave"]) == false ) {
- // 保存的處理邏輯
- }
- if( string.IsNullOrEmpty(context.Request.Form["btnQuery"]) == false ) {
- // 查詢的處理邏輯
- }
方法2:我將二個(gè)按鈕的name設(shè)置為相同的值(根據(jù)前面的成功控件規(guī)則,只有被點(diǎn)擊的按鈕才會(huì)提交),在服務(wù)端判斷value,示例代碼如下:
客戶名稱:
客戶電話:
- string action = context.Request.Form["submit"];
- if( action == "保存" ) {
- // 保存的處理邏輯
- }
- if( action == "查詢" ) {
- // 查詢的處理邏輯
- }
當(dāng)然了,解決這個(gè)問(wèn)題的方法很多,我們還可以在提交前修改form.action屬性。 對(duì)于MVC來(lái)說(shuō),可能有些人會(huì)選擇使用Filter的方式來(lái)處理。最終選擇哪種方法,可根據(jù)各自喜好來(lái)選擇。
我可能更喜歡直接使用Ajax提交到一個(gè)具體的URL,這樣也很直觀,在服務(wù)端也就不用這些判斷了。接著往下看吧。
上傳文件的表單
前面我說(shuō)到“數(shù)據(jù)的編碼"提到了form.enctype,這個(gè)屬性正是上傳表單與普通表單的區(qū)別,請(qǐng)看以下示例代碼:
要上傳的文件1
要上傳的文件2
我將上傳2個(gè)小文件
我們?cè)賮?lái)看看當(dāng)我點(diǎn)擊提交按鈕時(shí),瀏覽器發(fā)送的請(qǐng)求是個(gè)什么樣子的:
注意我用紅色邊框框出來(lái)的部分,以及請(qǐng)求體中的內(nèi)容。此時(shí)請(qǐng)求頭Content-Type的值發(fā)生了改變, 而且還多了一個(gè)叫boundary的參數(shù),它將告訴服務(wù)端:請(qǐng)求體的內(nèi)容以這個(gè)標(biāo)記來(lái)分開。 并且,請(qǐng)求體中每個(gè)分隔標(biāo)記會(huì)單獨(dú)占一行,且具體內(nèi)容為:"--" + boundary, 最后結(jié)束的分隔符的內(nèi)容為:"--" + boundary + "--" 也是獨(dú)占一行。 從圖片中我們還可以發(fā)現(xiàn),在請(qǐng)求體的每段數(shù)據(jù)前,還有一塊描述信息。
具體這些內(nèi)容是如何生成的,可以參考本文后面的實(shí)現(xiàn)代碼。
再來(lái)看看在服務(wù)端如何讀取上傳的文件。
- HttpPostedFile file1 = context.Request.Files["file1"];
- if( file1 != null && string.IsNullOrEmpty(file1.FileName) == false )
- file1.SaveAs(context.Server.MapPath("~/App_Data/") + file1.FileName);
- HttpPostedFile file2 = context.Request.Files["file2"];
- if( file2 != null && string.IsNullOrEmpty(file2.FileName) == false )
- file2.SaveAs(context.Server.MapPath("~/App_Data/") + file2.FileName);
或者
- HttpFileCollection files = context.Request.Files;
- foreach( string key in files.AllKeys ) {
- HttpPostedFile file = files[key];
- if( string.IsNullOrEmpty(file.FileName) == false )
- file.SaveAs(context.Server.MapPath("~/App_Data/") + file.FileName);
- }
二種方法都行,前者更能體現(xiàn)控件的name與服務(wù)端讀取的關(guān)系,后者在多文件上傳時(shí)有更好的擴(kuò)展性。
安全問(wèn)題:注意,上面示例代碼中,這樣的寫法是極不安全的。正確的做法應(yīng)該是:重新生成一個(gè)隨機(jī)的文件名, 而且最好能對(duì)文件內(nèi)容檢查,例如,如果是圖片,可以調(diào)用.net的一些圖形類打開文件,然后"另存"文件。 總之,在安全問(wèn)題面前只有一個(gè)原則:不要相信用戶的輸入,一定要檢查或者轉(zhuǎn)換。
#p#
MVC Controller中多個(gè)自定義類型的傳入?yún)?shù)
前面的所有示例代碼中都有一個(gè)規(guī)律:在服務(wù)端讀取瀏覽器提交的數(shù)據(jù)時(shí),都會(huì)使用控件的name屬性,基本上在Asp.net中就是這樣處理。 但是在MVC中,MS為了簡(jiǎn)化讀取表單數(shù)據(jù)的代碼,可以讓我們直接在Controller的方法中直接以傳入?yún)?shù)的形式指定, 此時(shí)框架會(huì)自動(dòng)根據(jù)方法的參數(shù)名查找對(duì)應(yīng)的輸入數(shù)據(jù)(當(dāng)然也不止表單數(shù)據(jù)了)。下面舉個(gè)簡(jiǎn)單的例子:
客戶名稱:
客戶電話:
Conntroller中的方法的簽名:
- public ActionResult Submit(Customer customer)
- {
- }
- public ActionResult Submit(string name, string tel)
- {
- }
以上二種方法都是可以的,當(dāng)然了,前者會(huì)比較好,但需要事先定義一個(gè)Customer類,代碼如下:
- public class Customer
- {
- public string Name { get; set; }
- public string Tel { get; set; }
- }
如果表單簡(jiǎn)單或者業(yè)務(wù)邏輯簡(jiǎn)單,我們或許一直也不會(huì)遇到什么麻煩,以上代碼能很好的工作。 但是,如果哪天我們有了新的業(yè)務(wù)需要求,需要在這個(gè)表單中同時(shí)加上一些其它的內(nèi)容,例如,要把業(yè)務(wù)員的資料也一起錄入進(jìn)去。 其中業(yè)務(wù)員的實(shí)體類定義如下:
Controller的接口需要修改成:
- public class Salesman
- {
- public string Name { get; set; }
- public string Tel { get; set; }
- }
這時(shí),HTML表單又該怎么寫呢?剛好,這二個(gè)類的(部分)屬性名稱一樣,顯然,前面表單中的Name,Tel就無(wú)法對(duì)應(yīng)了。 此時(shí)我們可以將表單寫成如下形式:
客戶名稱:
客戶電話:
銷售員名稱:
銷售員電話:
注意Controller方法中的參數(shù)名與HTML表單中的name是有關(guān)系的。
F5刷新問(wèn)題并不是WebForms的錯(cuò)
剛才說(shuō)到了MVC框架,再來(lái)說(shuō)說(shuō)WebForms框架。以前時(shí)常聽到有些人在抱怨用WebForms的表單有F5的刷新重復(fù)提交問(wèn)題。 在此我想為WebForms說(shuō)句公道話:這個(gè)問(wèn)題并不是WebForms本身的問(wèn)題,是瀏覽器的問(wèn)題, 只是如果您一直使用WebForms的較傳統(tǒng)用法,是容易產(chǎn)生這個(gè)現(xiàn)象的。那么什么叫做【傳統(tǒng)用法】呢?這里我就給個(gè)我自己的定義吧: 所謂的WebForms的傳統(tǒng)用法是說(shuō):您的頁(yè)面一直使用服務(wù)器控件的提交方式(postback),在事件處理后,頁(yè)面又進(jìn)入再一次的重現(xiàn)過(guò)程, 或者說(shuō):當(dāng)前頁(yè)面一直在使用POST方式向當(dāng)前頁(yè)面提交。
那么如何避開這個(gè)問(wèn)題呢?辦法大致有2種:
1. PRG模式(Post-Redirect-Get),在事件處理后,調(diào)用重定向的操作Response.Redirect(), 而不要在事件處理的后期再去給一些服務(wù)器控件綁定數(shù)據(jù)項(xiàng)了!
建議:按鈕事件只做一些提交數(shù)據(jù)的處理,將數(shù)據(jù)綁定的操作放在OnPreRender方法中處理,而不是寫在每個(gè)事件中(遍地開花)。 不過(guò),這種方式下,可能偉大的ViewState就發(fā)揮不了太大的作用了,如果您發(fā)現(xiàn)ViewState沒(méi)用了,在Web.config中全局關(guān)掉后, 又發(fā)現(xiàn)很多服務(wù)器控件的高級(jí)事件又不能用了!嗯,杯具有啊。
這個(gè)話題說(shuō)下去又沒(méi)完沒(méi)了,到此為止吧,不過(guò),千萬(wàn)不要以為這種方法是在倒退哦。
2. 以Ajax方式提交表單,請(qǐng)繼續(xù)閱讀本文。
#p#
以Ajax方式提交整個(gè)表單
前面一直在說(shuō)”瀏覽器提交表單",事實(shí)上我們也可以用JavaScript提交表單,好處也有很多,比如前面所說(shuō)的F5刷新問(wèn)題。 以Ajax方式提交表單的更大好處它是異步的,還可以實(shí)現(xiàn)局部刷新,這些特性都是瀏覽器提交方式?jīng)]有的。 前面我提到表單在提交時(shí),瀏覽器要實(shí)現(xiàn)的4個(gè)步驟,基本上用JS來(lái)完成這個(gè)操作也是一樣的。 但是,前面說(shuō)的步驟好像很麻煩呢,有沒(méi)有簡(jiǎn)單的方法來(lái)實(shí)現(xiàn)這個(gè)過(guò)程呢? 嗯,有的,這里我將使用JQuery以及jquery.form.js這個(gè)插件來(lái)演示這個(gè)復(fù)雜過(guò)程的簡(jiǎn)單處理方案。
示例用的HTML表單還是我前面用的代碼,完全不需要修改:
客戶名稱:
客戶電話:
JS代碼如下:
- $(function(){
- $('form').ajaxForm({
- success: function(responseText){
- alert(responseText);
- }
- });
- });
是的,就是這么簡(jiǎn)單,只要調(diào)用ajaxForm()就行了。你也可以傳入任何$.ajax()能接受的參數(shù)。
它的作用是:修改表單的提交方式,改成Ajax方式提交。最終當(dāng)用戶點(diǎn)擊“提交”按鈕時(shí),此時(shí)不再是瀏覽器的提交行為了, 而是使用Ajax的方式提交,提交的URL以及提交方法就是在FORM中指定的參數(shù)。
如果您希望要用戶點(diǎn)擊某個(gè)按鈕或者鏈接時(shí),也能提交表單(不經(jīng)過(guò)提交按鈕),那么可以使用如下方法:
- $(function(){
- $("#btnId").click(function(){
- $('form').ajaxSubmit({
- success: function(responseText){
- alert(responseText);
- }
- });
- });
- });
變化很小,只需要將ajaxForm修改成ajaxSubmit就OK了。 與ajaxForm()不同,調(diào)用ajaxSubmit()方法將會(huì)立即提交表單。
以Ajax方式提交部分表單
在前面的示例中,我們看到以Ajax方式提交一個(gè)表單是非常容易的,它完全模擬了瀏覽器的行為。 不過(guò),有時(shí)我們可能需要只提交表單的一部分,為的是更好的局部更新,那么又該如何做呢?
假如我有以下表單的一部分,我只希望在用戶某個(gè)按鈕時(shí)將它提交到服務(wù)端:
客戶名稱:
客戶電話:
我們可以這樣來(lái)提交這部分表單的數(shù)據(jù):
- $("#btnId").click(function(){
- $.ajax({
- url: "Handler1.ashx", type: "POST",
- data: $('#divCustomerInfo :text').fieldSerialize(),
- success: function(responseText){
- alert(responseText);
- }
- });
- return false;
- });
注意關(guān)鍵的代碼行:data: $('#divCustomerInfo :text').fieldSerialize()
注意:此時(shí)將由您指定一個(gè)【JQuery選擇器】來(lái)過(guò)濾要提交的控件,而不是使用成功控件的篩選邏輯。
或者,您也可以使用下面將要介紹的方法,仍然是使用 data: {} 的方式,但需要手工指定數(shù)據(jù)成員。
#p#
使用jQuery,就不要再拼URL了!
jQuery越來(lái)越流行,以至于在創(chuàng)建MVC項(xiàng)目時(shí),VS IDE會(huì)把jQuery也準(zhǔn)備好了,可能MS認(rèn)為開發(fā)WEB項(xiàng)目離不開jQuery了。
的確,jQuery非常方便,尤其是在處理DOM時(shí),不僅如此,在處理AJAX請(qǐng)求時(shí),也非常方便。
不過(guò),有件事卻讓我很納悶:經(jīng)??吹接腥嗽谑褂胘Query實(shí)現(xiàn)Ajax時(shí),把一堆參數(shù)放在URL中傳遞,當(dāng)然了, 發(fā)送GET請(qǐng)求嘛,這樣做不錯(cuò),但是,讓我不解的是:URL是拼接起來(lái)的,而且代碼又臭又長(zhǎng)!
如果是一個(gè)簡(jiǎn)單的參數(shù):"aaa.aspx?id=" + xxId ,這樣也就罷了。但是當(dāng)一堆參數(shù)拼接在一起時(shí),可能一下子還看不清楚到底有幾個(gè)什么樣的參數(shù)。 而且經(jīng)驗(yàn)豐富一些的開發(fā)人員會(huì)發(fā)現(xiàn)這樣做有時(shí)會(huì)有亂碼問(wèn)題,可能網(wǎng)上搜過(guò)后,知道還有編碼的工作要處理,于是又加了一堆編碼方法。 到此為止,這段代碼會(huì)讓人看起來(lái)很累!
如果您平時(shí)也是這樣做的,那么我今天就告訴您:不要再拼接URL了! $.ajax()的參數(shù)不是有個(gè)data成員嘛,用它吧??创a:
- $.ajax({
- url: "Handler1.ashx", type: "POST",
- data: { id: 2, name: "aaa", tel: "~!@#$%^&*()_+-=<>?|", xxxx: "要多少還可以寫多少", encoding: "見鬼去吧。?& :)" },
- success: function(responseText) {
- $("#divResult").html(responseText);
- }
- });
你說(shuō)什么,只能使用GET ? 哦,那就改一下 type 參數(shù)吧。
- $.ajax({
- url: "Handler1.ashx", type: "GET",
- data: { id: 2, name: "aaa", tel: "~!@#$%^&*()_+-=<>?|", xxxx: "要多少還可以寫多少", encoding: "見鬼去吧。?& :)" },
- success: function(responseText) {
- $("#divResult").html(responseText);
- }
- });
看了這個(gè)示例,您還會(huì)繼續(xù)拼URL嗎?
說(shuō)明:為了排版簡(jiǎn)單,我將參數(shù)放在一行了,建議實(shí)際使用時(shí),不要擠在一行。
id, name 有什么關(guān)系
通常我們?cè)趯慔TML代碼時(shí),會(huì)給控件指定一個(gè)id屬性,這個(gè)屬性只供JS和CSS使用,在表單提交時(shí),它不起任何作用。
在上面的示例代碼中,可能data {}中的各個(gè)value就來(lái)源于各個(gè)不同的控件,那么為那些控件指定相應(yīng)的id屬性將會(huì)方便地找到它們。
但是如果不需要用JS和CSS控制的控件,或許它們只是用來(lái)顯示一些數(shù)據(jù)(只讀),那么就沒(méi)有必要指定id屬性, 當(dāng)然了,name屬性也可以不用給出(避免提交無(wú)意義的數(shù)據(jù))。
使用C#模擬瀏覽器提交表單
瀏覽器也是一個(gè)普通的應(yīng)用程序,.net framework也提供一些類也能讓我們直接發(fā)起HTTP請(qǐng)求。 今天我將再次用C#來(lái)模擬瀏覽器的提交請(qǐng)求,同時(shí)也可以加深對(duì)HTTP請(qǐng)求的理解。
示例代碼分為二段,一段示范了使用application/x-www-form-urlencoded編碼方式提交, 另一段則示范了使用multipart/form-data的編碼方式。
為了讓大家能再次利用這些代碼,我已將關(guān)鍵部分寫成獨(dú)立方法,希望當(dāng)您有這方面的需求時(shí)能馬上可以用上。 代碼如下:
1. application/x-www-form-urlencoded
- ///
- /// 向指定的URL地址發(fā)起一個(gè)POST請(qǐng)求,同時(shí)可以上傳一些數(shù)據(jù)項(xiàng)。
- ///
- /// 要請(qǐng)求的URL地址
- /// 要上傳的數(shù)據(jù)項(xiàng)
- /// 發(fā)送,接收的字符編碼方式
- ///
服務(wù)器的返回結(jié)果 - static string SendHttpRequestPost(string url, Dictionary
keyvalues, Encoding encoding) - {
- if( string.IsNullOrEmpty(url) )
- throw new ArgumentNullException("url");
- string postData = null;
- // 將數(shù)據(jù)項(xiàng)轉(zhuǎn)變成 name1=value1&name2=value2 的形式
- if( keyvalues != null && keyvalues.Count > 0 ) {
- postData = string.Join("&",
- (from kvp in keyvalues
- let item = kvp.Key + "=" + HttpUtility.UrlEncode(kvp.Value)
- select item
- ).ToArray()
- );
- }
- if( encoding == null )
- encoding = Encoding.UTF8;
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
- request.Method = "POST";
- request.ContentType = "application/x-www-form-urlencoded; charset=" + encoding.WebName;
- if( postData != null ) {
- byte[] buffer = encoding.GetBytes(postData);
- Stream stream = request.GetRequestStream();
- stream.Write(buffer, 0, buffer.Length);
- stream.Close();
- }
- using( WebResponse response = request.GetResponse() ) {
- using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) {
- return reader.ReadToEnd();
- }
- }
- }
- // 調(diào)用上面方法的示例代碼
- string Test_SendHttpRequestPost()
- {
- string url = "http://localhost:1272/FormWebSite1/Handler1.ashx";
- Dictionary
keyvalues = new Dictionary (); - keyvalues.Add("CustomerName", "我是李奇峰,$%@+& ?#^/");
- keyvalues.Add("CustomerTel", "1381723505x");
- return SendHttpRequestPost(url, keyvalues, null);
- }
2. multipart/form-data 。注意這部分代碼有點(diǎn)復(fù)雜,因此我加了很多注釋。
- ///
- /// 向指定的URL地址發(fā)起一個(gè)POST請(qǐng)求,同時(shí)可以上傳一些數(shù)據(jù)項(xiàng)以及上傳文件。
- ///
- /// 要請(qǐng)求的URL地址
- /// 要上傳的數(shù)據(jù)項(xiàng)
- /// 要上傳的文件列表
- /// 發(fā)送數(shù)據(jù)項(xiàng),接收的字符編碼方式
- ///
服務(wù)器的返回結(jié)果 - static string SendHttpRequestPost(string url, Dictionary
keyvalues, - Dictionary
fileList, Encoding encoding) - {
- if( fileList == null )
- return SendHttpRequestPost(url, keyvalues, encoding);
- if( string.IsNullOrEmpty(url) )
- throw new ArgumentNullException("url");
- if( encoding == null )
- encoding = Encoding.UTF8;
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
- request.Method = "POST"; // 要上傳文件,一定要是POST方法
- // 數(shù)據(jù)塊的分隔標(biāo)記,用于設(shè)置請(qǐng)求頭,注意:這個(gè)地方最好不要使用漢字。
- string boundary = "---------------------------" + Guid.NewGuid().ToString("N");
- // 數(shù)據(jù)塊的分隔標(biāo)記,用于寫入請(qǐng)求體。
- // 注意:前面多了一段: "--" ,而且它們將獨(dú)占一行。
- byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
- // 設(shè)置請(qǐng)求頭。指示是一個(gè)上傳表單,以及各數(shù)據(jù)塊的分隔標(biāo)記。
- request.ContentType = "multipart/form-data; boundary=" + boundary;
- // 先得到請(qǐng)求流,準(zhǔn)備寫入數(shù)據(jù)。
- Stream stream = request.GetRequestStream();
- if( keyvalues != null && keyvalues.Count > 0 ) {
- // 寫入非文件的keyvalues部分
- foreach( KeyValuePair
kvp in keyvalues ) { - // 寫入數(shù)據(jù)塊的分隔標(biāo)記
- stream.Write(boundaryBytes, 0, boundaryBytes.Length);
- // 寫入數(shù)據(jù)項(xiàng)描述,這里的Value部分可以不用URL編碼
- string str = string.Format(
- "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}",
- kvp.Key, kvp.Value);
- byte[] data = encoding.GetBytes(str);
- stream.Write(data, 0, data.Length);
- }
- }
- // 寫入要上傳的文件
- foreach( KeyValuePair
kvp in fileList ) { - // 寫入數(shù)據(jù)塊的分隔標(biāo)記
- stream.Write(boundaryBytes, 0, boundaryBytes.Length);
- // 寫入文件描述,這里設(shè)置一個(gè)通用的類型描述:application/octet-stream,具體的描述在注冊(cè)表里有。
- string description = string.Format(
- "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" +
- "Content-Type: application/octet-stream\r\n\r\n",
- kvp.Key, Path.GetFileName(kvp.Value));
- // 注意:這里如果不使用UTF-8,對(duì)于漢字會(huì)有亂碼。
- byte[] header = Encoding.UTF8.GetBytes(description);
- stream.Write(header, 0, header.Length);
- // 寫入文件內(nèi)容
- byte[] body = File.ReadAllBytes(kvp.Value);
- stream.Write(body, 0, body.Length);
- }
- // 寫入結(jié)束標(biāo)記
- boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
- stream.Write(boundaryBytes, 0, boundaryBytes.Length);
- stream.Close();
- // 開始發(fā)起請(qǐng)求,并獲取服務(wù)器返回的結(jié)果。
- using( WebResponse response = request.GetResponse() ) {
- using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) {
- return reader.ReadToEnd();
- }
- }
- }
- // 調(diào)用上面方法的示例代碼
- string Test_SendHttpRequestPost2()
- {
- string url = "http://localhost:1272/FormWebSite1/Handler2.ashx";
- Dictionary
keyvalues = new Dictionary (); - keyvalues.Add("Key1", "本示例代碼由 Fish Li 提供");
- keyvalues.Add("Key2", "http://www.cnblogs.com/fish-li");
- keyvalues.Add("Key3", "來(lái)幾個(gè)特殊字符:~!@#$%^&*()-=_+{}[]:;'\"<>?/.,|\\");
- Dictionary
fileList = new Dictionary (); - fileList.Add("file1", @"H:\AllTempFiles\ascx中文字.gif");
- fileList.Add("file2", @"H:\AllTempFiles\asax中文字.gif");
- return SendHttpRequestPost(url, keyvalues, fileList, Encoding.UTF8);
名稱欄目:細(xì)說(shuō)Form(表單)
分享網(wǎng)址:http://www.dlmjj.cn/article/cdihgio.html


咨詢
建站咨詢
