Form(表單)對(duì)于每個(gè)WEB開發(fā)人員來說,應(yīng)該是再熟悉不過的東西了,可它卻是頁面與WEB服務(wù)器交互過程中最重要的信息來源。雖然Asp.net WebForms框架為了幫助我們簡(jiǎn)化開發(fā)工作,做了很完美的封裝,讓我們只需要簡(jiǎn)單地使用服務(wù)端控件就可以直接操作那些 HTML表單元素了。但我認(rèn)為了解一些基礎(chǔ)的東西,可以使我們不必束縛在WebForms框架上,以及遇到一些奇怪問題時(shí),可以更從容地解決它們。 今天,我將和大家來聊聊表單,這個(gè)簡(jiǎn)單又基礎(chǔ)的東西。我將站在HTML和單純的Asp.net框架的角度來解釋它們的工作方式,因此,本文不演示W(wǎng)ebForms服務(wù)器控件的相關(guān)內(nèi)容。 簡(jiǎn)單的表單,簡(jiǎn)單的處理方式好了,讓我們進(jìn)入今天的主題,看看下面這個(gè)簡(jiǎn)單的HTML表單。 <form action="Handler1.ashx" method="post" > <p>客戶名稱: <input type="text" name="CustomerName" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="CustomerTel" style="width: 300px" /></p> <p><input type="submit" value="提交" /></p> </form> 在這個(gè)HTML表單中,我定義了二個(gè)文本輸入框,一個(gè)提交按鈕,表單將提交到Handler1.ashx中處理,且以POST的方式。 說明:當(dāng)我們使用WebForms的服務(wù)器表單控件時(shí),一般都會(huì)提交到頁面自身來處理(action屬性指向當(dāng)前頁面),這樣可以方便地使用按鈕事件以及從服務(wù)器控件訪問從瀏覽器提交的控件輸入結(jié)果。 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)求過程做了如下截圖。 上圖是將要提交的表單的輸入情況,下圖是用Fiddler看到的瀏覽器發(fā)出的請(qǐng)求內(nèi)容。 在這張圖片中,我們可以看到瀏覽器確實(shí)將請(qǐng)求發(fā)給了我前面在action中指定的地址,且以POST形式發(fā)出的。表單的二個(gè)控件的輸入值放在請(qǐng)求體中,且做了【編碼】處理,編碼的方式用請(qǐng)求頭【Content-Type】說明,這樣,當(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ù),如下圖。 看了客戶端的頁面和請(qǐng)求的內(nèi)容,我們?cè)賮砜纯丛诜?wù)端如何獲取瀏覽器提交的表單的輸入吧,代碼如下: string name = context.Request.Form["CustomerName"]; string tel = context.Request.Form["CustomerTel"]; 代碼很簡(jiǎn)單,直接根據(jù)表單控件的name屬性訪問Request.Form就可以了。 表單提交,成功控件我們?cè)賮砜匆幌聻g覽器是如何提交表單的,或者說,瀏覽器在提交表單時(shí),要做哪些事情。 瀏覽器并不是將所有的表單控件全部發(fā)送到服務(wù)器的,而是會(huì)查找所有的【成功控件】,只將這些成功控件的數(shù)據(jù)發(fā)送到服務(wù)端,什么是成功控件呢? 注意: 提交方式:在前面的示例代碼中,我為form指定了method="post",這個(gè)提交方法就決定了瀏覽器在提交數(shù)據(jù)時(shí),通過什么方式來傳遞它們。 數(shù)據(jù)的編碼:前面我將瀏覽器的請(qǐng)求細(xì)節(jié)用Fiddler做了個(gè)截圖,從這個(gè)圖中我們可以看到:控件輸入的內(nèi)容并不是直接發(fā)送的,而是經(jīng)過一種編碼規(guī)則來處理的。目前基本上只會(huì)只使用二種編碼規(guī)則:application/x-www-form-urlencoded 和 multipart/form-data ,這二個(gè)規(guī)則的使用場(chǎng)景簡(jiǎn)單地說就是:后者在上傳文件時(shí)使用,其它情形則使用前者(默認(rèn))。 按照我前面說過的編碼規(guī)則選擇邏輯,application/x-www-form-urlencoded做為默認(rèn)值,所以,一般情況下我們并不用顯式指定。除非我們要上傳文件了,那么此時(shí)必須設(shè)置enctype="multipart/form-data" 好了,說了這么一大堆理論,我們?cè)賮砜匆幌聻g覽是如何處理表單數(shù)據(jù)的。這個(gè)過程大致分為4個(gè)階段: 多提交按鈕的表單用過Asp.net WebForms框架的人可能都寫過這樣的頁面:一個(gè)頁面中包含多個(gè)服務(wù)端按鈕。處理方式嘛,也很簡(jiǎn)單:在每個(gè)按鈕的事件處理器寫上相應(yīng)的代碼就完事了,根本不用我們想太多。 方法1:根據(jù)【成功控件】定義,我們?cè)O(shè)置按鈕的name,在服務(wù)端用name來區(qū)分哪個(gè)按鈕的提交: HTML代碼 <form action="Handler1.ashx" method="post"> <p>客戶名稱: <input type="text" name="CustomerName" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="CustomerTel" style="width: 300px" /></p> <p><input type="submit" name="btnSave" value="保存" /> <input type="submit" name="btnQuery" value="查詢" /> </p> </form> 服務(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,示例代碼如下: <form action="Handler1.ashx" method="post"> <p>客戶名稱: <input type="text" name="CustomerName" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="CustomerTel" style="width: 300px" /></p> <p><input type="submit" name="submit" value="保存" /> <input type="submit" name="submit" value="查詢" /> </p> </form> string action = context.Request.Form["submit"]; if( action == "保存" ) { // 保存的處理邏輯 } if( action == "查詢" ) { // 查詢的處理邏輯 } 當(dāng)然了,解決這個(gè)問題的方法很多,我們還可以在提交前修改form.action屬性。對(duì)于MVC來說,可能有些人會(huì)選擇使用Filter的方式來處理。最終選擇哪種方法,可根據(jù)各自喜好來選擇。 上傳文件的表單前面我說到“數(shù)據(jù)的編碼"提到了form.enctype,這個(gè)屬性正是上傳表單與普通表單的區(qū)別,請(qǐng)看以下示例代碼: <form action="Handler2.ashx" method="post" enctype="multipart/form-data"> <p><input type="text" name="str" value="一個(gè)字符串,別管它" /></p> <p>要上傳的文件1<input type="file" name="file1"/></p> <p>要上傳的文件2<input type="file" name="file2"/></p> <p><input type="submit" value="提交" /></p> </form> 我將上傳2個(gè)小文件 我們?cè)賮砜纯串?dāng)我點(diǎn)擊提交按鈕時(shí),瀏覽器發(fā)送的請(qǐng)求是個(gè)什么樣子的: 注意我用紅色邊框框出來的部分,以及請(qǐng)求體中的內(nèi)容。此時(shí)請(qǐng)求頭Content-Type的值發(fā)生了改變,而且還多了一個(gè)叫boundary的參數(shù),它將告訴服務(wù)端:請(qǐng)求體的內(nèi)容以這個(gè)標(biāo)記來分開。 并且,請(qǐng)求體中每個(gè)分隔標(biāo)記會(huì)單獨(dú)占一行,且具體內(nèi)容為:"--" + boundary,最后結(jié)束的分隔符的內(nèi)容為:"--" + boundary + "--" 也是獨(dú)占一行。從圖片中我們還可以發(fā)現(xiàn),在請(qǐng)求體的每段數(shù)據(jù)前,還有一塊描述信息。 再來看看在服務(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ò)展性。 安全問題:注意,上面示例代碼中,這樣的寫法是極不安全的。正確的做法應(yīng)該是:重新生成一個(gè)隨機(jī)的文件名,而且最好能對(duì)文件內(nèi)容檢查,例如,如果是圖片,可以調(diào)用.net的一些圖形類打開文件,然后"另存"文件。總之,在安全問題面前只有一個(gè)原則:不要相信用戶的輸入,一定要檢查或者轉(zhuǎn)換。 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)單的例子: <form action="/Home/Submit" method="post"> <p>客戶名稱: <input type="text" name="Name" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="Tel" style="width: 300px" /></p> <p><input type="submit" value="提交" /></p> </form> 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í)體類定義如下: public class Salesman { public string Name { get; set; } public string Tel { get; set; } } Controller的接口需要修改成: public ActionResult Submit(Customer customer, Salesman salesman) { } 這時(shí),HTML表單又該怎么寫呢?剛好,這二個(gè)類的(部分)屬性名稱一樣,顯然,前面表單中的Name,Tel就無法對(duì)應(yīng)了。此時(shí)我們可以將表單寫成如下形式: <form action="/Home/Submit" method="post"> <p>客戶名稱: <input type="text" name="customer.Name" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="customer.Tel" style="width: 300px" /></p> <p>銷售員名稱: <input type="text" name="salesman.Name" style="width: 300px" /></p> <p>銷售員電話: <input type="text" name="salesman.Tel" style="width: 300px" /></p> <p><input type="submit" value="提交" /></p> </form> 注意Controller方法中的參數(shù)名與HTML表單中的name是有關(guān)系的。 F5刷新問題并不是WebForms的錯(cuò)剛才說到了MVC框架,再來說說WebForms框架。以前時(shí)常聽到有些人在抱怨用WebForms的表單有F5的刷新重復(fù)提交問題。在此我想為WebForms說句公道話:這個(gè)問題并不是WebForms本身的問題,是瀏覽器的問題,只是如果您一直使用WebForms的較傳統(tǒng)用法,是容易產(chǎn)生這個(gè)現(xiàn)象的。那么什么叫做【傳統(tǒng)用法】呢?這里我就給個(gè)我自己的定義吧:所謂的WebForms的傳統(tǒng)用法是說:您的頁面一直使用服務(wù)器控件的提交方式(postback),在事件處理后,頁面又進(jìn)入再一次的重現(xiàn)過程,或者說:當(dāng)前頁面一直在使用POST方式向當(dāng)前頁面提交。 那么如何避開這個(gè)問題呢?辦法大致有2種: 1. PRG模式(Post-Redirect-Get),在事件處理后,調(diào)用重定向的操作Response.Redirect(),而不要在事件處理的后期再去給一些服務(wù)器控件綁定數(shù)據(jù)項(xiàng)了! 2. 以Ajax方式提交表單,請(qǐng)繼續(xù)閱讀本文。 以Ajax方式提交整個(gè)表單前面一直在說”瀏覽器提交表單",事實(shí)上我們也可以用JavaScript提交表單,好處也有很多,比如前面所說的F5刷新問題。以Ajax方式提交表單的更大好處它是異步的,還可以實(shí)現(xiàn)局部刷新,這些特性都是瀏覽器提交方式?jīng)]有的。前面我提到表單在提交時(shí),瀏覽器要實(shí)現(xiàn)的4個(gè)步驟,基本上用JS來完成這個(gè)操作也是一樣的。但是,前面說的步驟好像很麻煩呢,有沒有簡(jiǎn)單的方法來實(shí)現(xiàn)這個(gè)過程呢?嗯,有的,這里我將使用JQuery以及jquery.form.js這個(gè)插件來演示這個(gè)復(fù)雜過程的簡(jiǎn)單處理方案。 示例用的HTML表單還是我前面用的代碼,完全不需要修改: <form action="Handler1.ashx" method="post" > <p>客戶名稱: <input type="text" name="CustomerName" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="CustomerTel" style="width: 300px" /></p> <p><input type="submit" value="提交" /></p> </form> JS代碼如下: $(function(){ $('form').ajaxForm({ success: function(responseText){ alert(responseText); } }); }); 是的,就是這么簡(jiǎn)單,只要調(diào)用ajaxForm()就行了。你也可以傳入任何$.ajax()能接受的參數(shù)。 如果您希望要用戶點(diǎn)擊某個(gè)按鈕或者鏈接時(shí),也能提交表單(不經(jīng)過提交按鈕),那么可以使用如下方法: $(function(){ $("#btnId").click(function(){ $('form').ajaxSubmit({ success: function(responseText){ alert(responseText); } }); }); }); 變化很小,只需要將ajaxForm修改成ajaxSubmit就OK了。 與ajaxForm()不同,調(diào)用ajaxSubmit()方法將會(huì)立即提交表單。 以Ajax方式提交部分表單在前面的示例中,我們看到以Ajax方式提交一個(gè)表單是非常容易的,它完全模擬了瀏覽器的行為。不過,有時(shí)我們可能需要只提交表單的一部分,為的是更好的局部更新,那么又該如何做呢? <div id="divCustomerInfo"> <p>客戶名稱: <input type="text" name="CustomerName" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="CustomerTel" style="width: 300px" /></p> </div> 我們可以這樣來提交這部分表單的數(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() 或者,您也可以使用下面將要介紹的方法,仍然是使用 data: {} 的方式,但需要手工指定數(shù)據(jù)成員。 使用JQuery,就不要再拼URL了!JQuery越來越流行,以至于在創(chuàng)建MVC項(xiàng)目時(shí),VS IDE會(huì)把JQuery也準(zhǔn)備好了,可能MS認(rèn)為開發(fā)WEB項(xiàng)目離不開JQuery了。 不過,有件事卻讓我很納悶:經(jīng)??吹接腥嗽谑褂肑Query實(shí)現(xiàn)Ajax時(shí),把一堆參數(shù)放在URL中傳遞,當(dāng)然了,發(fā)送GET請(qǐng)求嘛,這樣做不錯(cuò),但是,讓我不解的是:URL是拼接起來的,而且代碼又臭又長(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ǎng)上搜過后,知道還有編碼的工作要處理,于是又加了一堆編碼方法。到此為止,這段代碼會(huì)讓人看起來很累! 如果您平時(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); } }); 你說什么,只能使用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嗎? 說明:為了排版簡(jiǎn)單,我將參數(shù)放在一行了,建議實(shí)際使用時(shí),不要擠在一行。 id, name 有什么關(guān)系通常我們?cè)趯慔TML代碼時(shí),會(huì)給控件指定一個(gè)id屬性,這個(gè)屬性只供JS和CSS使用,在表單提交時(shí),它不起任何作用。 在上面的示例代碼中,可能data {}中的各個(gè)value就來源于各個(gè)不同的控件,那么為那些控件指定相應(yīng)的id屬性將會(huì)方便地找到它們。 使用C#模擬瀏覽器提交表單瀏覽器也是一個(gè)普通的應(yīng)用程序,.net framework也提供一些類也能讓我們直接發(fā)起HTTP請(qǐng)求。今天我將再次用C#來模擬瀏覽器的提交請(qǐng)求,同時(shí)也可以加深對(duì)HTTP請(qǐng)求的理解。 示例代碼分為二段,一段示范了使用application/x-www-form-urlencoded編碼方式提交,另一段則示范了使用multipart/form-data的編碼方式。 1. application/x-www-form-urlencoded /// <summary> /// 向指定的URL地址發(fā)起一個(gè)POST請(qǐng)求,同時(shí)可以上傳一些數(shù)據(jù)項(xiàng)。 /// </summary> /// <param name="url">要請(qǐng)求的URL地址</param> /// <param name="keyvalues">要上傳的數(shù)據(jù)項(xiàng)</param> /// <param name="encoding">發(fā)送,接收的字符編碼方式</param> /// <returns>服務(wù)器的返回結(jié)果</returns> static string SendHttpRequestPost(string url, Dictionary<string, string> 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<string, string> keyvalues = new Dictionary<string, string>(); keyvalues.Add("CustomerName", "我是李奇峰,$%@+& ?#^/"); keyvalues.Add("CustomerTel", "1381723505x"); return SendHttpRequestPost(url, keyvalues, null); }
2. multipart/form-data 。注意這部分代碼有點(diǎn)復(fù)雜,因此我加了很多注釋。 /// <summary> /// 向指定的URL地址發(fā)起一個(gè)POST請(qǐng)求,同時(shí)可以上傳一些數(shù)據(jù)項(xiàng)以及上傳文件。 /// </summary> /// <param name="url">要請(qǐng)求的URL地址</param> /// <param name="keyvalues">要上傳的數(shù)據(jù)項(xiàng)</param> /// <param name="fileList">要上傳的文件列表</param> /// <param name="encoding">發(fā)送數(shù)據(jù)項(xiàng),接收的字符編碼方式</param> /// <returns>服務(wù)器的返回結(jié)果</returns> static string SendHttpRequestPost(string url, Dictionary<string, string> keyvalues, Dictionary<string, string> 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<string, string> 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<string, string> 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<string, string> keyvalues = new Dictionary<string, string>(); keyvalues.Add("Key1", "本示例代碼由 Fish Li 提供"); keyvalues.Add("Key2", "http://www.cnblogs.com/fish-li"); keyvalues.Add("Key3", "來幾個(gè)特殊字符:~!@#$%^&*()-=_+{}[]:;'\"<>?/.,|\\"); Dictionary<string, string> fileList = new Dictionary<string, string>(); fileList.Add("file1", @"H:\AllTempFiles\ascx中文字.gif"); fileList.Add("file2", @"H:\AllTempFiles\asax中文字.gif"); return SendHttpRequestPost(url, keyvalues, fileList, Encoding.UTF8); } 說明:上面的示例方法中,我并沒有對(duì)KEY編碼,是因?yàn)椋何蚁氪蠹疫x用的KEY應(yīng)該是不需要編碼的(英文字母與數(shù)字的組合)。 |
|