Form(表單)對於每一個WEB開發人員來講,應該是再熟悉不過的東西了,可它倒是頁面與WEB服務器交互過程當中最重要的信息來源。 雖然Asp.net WebForms框架爲了幫助咱們簡化開發工做,作了很完美的封裝,讓咱們只須要簡單地使用服務端控件就能夠直接操做那些 HTML表單元素了。但我認爲了解一些基礎的東西,可使咱們沒必要束縛在WebForms框架上,以及遇到一些奇怪問題時, 能夠更從容地解決它們。html
今天,我將和你們來聊聊表單,這個簡單又基礎的東西。我將站在HTML和單純的Asp.net框架的角度來解釋它們的工做方式, 所以,本文不演示WebForms服務器控件的相關內容。jquery
好了,讓咱們進入今天的主題,看看下面這個簡單的HTML表單。git
<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>
在這個HTML表單中,我定義了二個文本輸入框,一個提交按鈕,表單將提交到Handler1.ashx中處理,且以POST的方式。
注意哦,若是咱們想讓純靜態頁面也能向服務器提交數據,就能夠採用這樣方式來處理:將action屬性指向一個服務器能處理的地址。 github
說明:當咱們使用WebForms的服務器表單控件時,通常都會提交到頁面自身來處理(action屬性指向當前頁面), 這樣能夠方便地使用按鈕事件以及從服務器控件訪問從瀏覽器提交的控件輸入結果。
若是在URL重寫時,但願能在頁面回傳時保持URL不變,即:action爲重寫後的URL,那麼能夠Page類中執行如下調用: ajax
Form.Action = Request.RawUrl; // 受如下版本支持:3.5 SP一、3.0 SP一、2.0 SP1
好了,咱們再回到前面那個HTML表單,看一下若是用戶點擊了「提交」按鈕,瀏覽器是如何把表單的內容發出的。 在此,咱們須要Fiddler工具的協助,請在提交表單前啓動好Fiddler。我將這個表單的提交請求過程作了以下截圖。瀏覽器
上圖是將要提交的表單的輸入狀況,下圖是用Fiddler看到的瀏覽器發出的請求內容。安全
在這張圖片中,咱們能夠看到瀏覽器確實將請求發給了我前面在action中指定的地址,且以POST形式發出的。 表單的二個控件的輸入值放在請求體中,且作了【編碼】處理,編碼的方式用請求頭【Content-Type】說明, 這樣,當服務端收到請求後,就知道該如何讀取請求的內容了。 注意:表單的數據是以name1=value1&name2=value2 的形式提交的,其中name,value分別對應了表單控件的相應屬性。服務器
咱們還能夠在Fiddler中,將視圖切換到WebForms選項卡,這樣能更清楚地只查看瀏覽器提交的數據,以下圖。app
看了客戶端的頁面和請求的內容,咱們再來看看在服務端如何獲取瀏覽器提交的表單的輸入吧,代碼以下:框架
string name = context.Request.Form["CustomerName"]; string tel = context.Request.Form["CustomerTel"];
代碼很簡單,直接根據表單控件的name屬性訪問Request.Form就能夠了。
咱們再來看一下瀏覽器是如何提交表單的,或者說,瀏覽器在提交表單時,要作哪些事情。
瀏覽器並非將全部的表單控件所有發送到服務器的,而是會查找全部的【成功控件】,只將這些成功控件的數據發送到服務端, 什麼是成功控件呢?
簡單地來講,成功控件就是:每一個表單中的控件都應該有一個name屬性和」當前值「, 在提交時,它們將以 name=value 的形式作爲提交數據的一部分。
對於一些特殊狀況,成功控件還有如下規定:
1. 控件不能是【禁用】狀態,即指定【disabled="disabled"】。即:禁用的控件將不是成功控件。
2. 若是一個表單包含了多個提交按鍵,那麼僅當用戶點擊的那個提交按鈕纔算是成功控件。
3. 對於checkbox控件來講,只有被用戶勾選的纔算是成功控件。
4. 對於radio button來講,只有被用戶勾選的纔算是成功控件。
5. 對於select控件來講,全部被選擇的選項都作爲成功控件,name由select控件提供。
6. 對於file上傳文件控件來講,若是它包含了選擇的文件,那麼它將是一個成功控件。
此外,瀏覽器不會考慮Reset按鈕以及OBJECT元素。
注意:
1. 對於checkbox, radio button來講,若是它們被確認爲成功控件,但沒有爲控件指定value屬性, 那麼在表單提交時,將會以"on"作爲它們的value
2. 若是在服務端讀不到某個表單控件的值,請檢查它是否知足以上規則。
提交方式:在前面的示例代碼中,我爲form指定了method="post",這個提交方法就決定了瀏覽器在提交數據時,經過什麼方式來傳遞它們。
若是是【post】,那麼表單數據將放在請求體中被髮送出去。
若是是【get】,那麼表單數據將會追加到查詢字符串中,以查詢字符串的形式提交到服務端。
建議:表單一般仍是以post方式提交比較好,這樣能夠不破壞URL,何況URL還有長度限制。
數據的編碼:前面我將瀏覽器的請求細節用Fiddler作了個截圖,從這個圖中咱們能夠看到:控件輸入的內容並非直接發送的, 而是通過一種編碼規則來處理的。目前基本上只會只使用二種編碼規則:application/x-www-form-urlencoded 和 multipart/form-data , 這二個規則的使用場景簡單地說就是:後者在上傳文件時使用,其它情形則使用前者(默認)。
這個規則是在哪裏指定的呢? 其實form還有個enctype屬性,用它就能夠指定編碼規則,當我在VS2008寫代碼時,會有如下提示:
按照我前面說過的編碼規則選擇邏輯,application/x-www-form-urlencoded作爲默認值,因此,通常狀況下咱們並不用顯式指定。 除非咱們要上傳文件了,那麼此時必須設置enctype="multipart/form-data"
好了,說了這麼一大堆理論,咱們再來看一下瀏覽是如何處理表單數據的。這個過程大體分爲4個階段:
1. 識別全部的成功控件。
2. 爲全部的成功控件建立一個數據集合,它們包含 control-name/current-value 這樣的值對。
3. 按照form.enctype指定的編碼規則對前面準備好的數據進行編碼。編碼規則將放在請求中,用【Content-Type】指出。
4. 提交編碼後的數據。此時會區分post,get二種狀況,提交的地址由form.action屬性指定的。
用過Asp.net WebForms框架的人可能都寫過這樣的頁面:一個頁面中包含多個服務端按鈕。處理方式嘛, 也很簡單:在每一個按鈕的事件處理器寫上相應的代碼就完事了,根本不用咱們想太多。
不過,對於不理解這背後處理過程的開發人員來講,當他們轉到MVC框架下,可能會被卡住:MVC框架中可沒有按鈕事件! 即便用不用MVC框架,用ashx通用處理器的方式,也會遇到這種問題,怎麼辦?
對於這個問題,本文將站在HTML角度給出二個最根本的解決辦法。
方法1:根據【成功控件】定義,咱們設置按鈕的name,在服務端用name來區分哪一個按鈕的提交:
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>
服務端處理代碼
// 注意:咱們只要判斷指定的name是否存在就能夠了。
if( string.IsNullOrEmpty(context.Request.Form["btnSave"]) == false ) { // 保存的處理邏輯 } if( string.IsNullOrEmpty(context.Request.Form["btnQuery"]) == false ) { // 查詢的處理邏輯 }
方法2:我將二個按鈕的name設置爲相同的值(根據前面的成功控件規則,只有被點擊的按鈕纔會提交),在服務端判斷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 == "查詢" ) { // 查詢的處理邏輯 }
固然了,解決這個問題的方法不少,咱們還能夠在提交前修改form.action屬性。 對於MVC來講,可能有些人會選擇使用Filter的方式來處理。最終選擇哪一種方法,可根據各自喜愛來選擇。
我可能更喜歡直接使用Ajax提交到一個具體的URL,這樣也很直觀,在服務端也就不用這些判斷了。接着往下看吧。
前面我說到「數據的編碼"提到了form.enctype,這個屬性正是上傳表單與普通表單的區別,請看如下示例代碼:
<form action="Handler2.ashx" method="post" enctype="multipart/form-data"> <p><input type="text" name="str" value="一個字符串,別管它" /></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個小文件
咱們再來看看當我點擊提交按鈕時,瀏覽器發送的請求是個什麼樣子的:
注意我用紅色邊框框出來的部分,以及請求體中的內容。此時請求頭Content-Type的值發生了改變, 並且還多了一個叫boundary的參數,它將告訴服務端:請求體的內容以這個標記來分開。 而且,請求體中每一個分隔標記會單獨佔一行,且具體內容爲:"--" + boundary, 最後結束的分隔符的內容爲:"--" + boundary + "--" 也是獨佔一行。 從圖片中咱們還能夠發現,在請求體的每段數據前,還有一塊描述信息。
具體這些內容是如何生成的,能夠參考本文後面的實現代碼。
再來看看在服務端如何讀取上傳的文件。
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); }
二種方法都行,前者更能體現控件的name與服務端讀取的關係,後者在多文件上傳時有更好的擴展性。
安全問題:注意,上面示例代碼中,這樣的寫法是極不安全的。正確的作法應該是:從新生成一個隨機的文件名, 並且最好能對文件內容檢查,例如,若是是圖片,能夠調用.net的一些圖形類打開文件,而後"另存"文件。 總之,在安全問題面前只有一個原則:不要相信用戶的輸入,必定要檢查或者轉換。
前面的全部示例代碼中都有一個規律:在服務端讀取瀏覽器提交的數據時,都會使用控件的name屬性,基本上在Asp.net中就是這樣處理。 可是在MVC中,MS爲了簡化讀取表單數據的代碼,可讓咱們直接在Controller的方法中直接以傳入參數的形式指定, 此時框架會自動根據方法的參數名查找對應的輸入數據(固然也不止表單數據了)。下面舉個簡單的例子:
<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) { }
以上二種方法都是能夠的,固然了,前者會比較好,但須要事先定義一個Customer類,代碼以下:
public class Customer { public string Name { get; set; } public string Tel { get; set; } }
若是表單簡單或者業務邏輯簡單,咱們或許一直也不會遇到什麼麻煩,以上代碼能很好的工做。 可是,若是哪天咱們有了新的業務須要求,須要在這個表單中同時加上一些其它的內容,例如,要把業務員的資料也一塊兒錄入進去。 其中業務員的實體類定義以下:
public class Salesman { public string Name { get; set; } public string Tel { get; set; } }
Controller的接口須要修改爲:
public ActionResult Submit(Customer customer, Salesman salesman) { }
這時,HTML表單又該怎麼寫呢?恰好,這二個類的(部分)屬性名稱同樣,顯然,前面表單中的Name,Tel就沒法對應了。 此時咱們能夠將表單寫成以下形式:
<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方法中的參數名與HTML表單中的name是有關係的。
剛纔說到了MVC框架,再來講說WebForms框架。之前時常聽到有些人在抱怨用WebForms的表單有F5的刷新重複提交問題。 在此我想爲WebForms說句公道話:這個問題並非WebForms自己的問題,是瀏覽器的問題, 只是若是您一直使用WebForms的較傳統用法,是容易產生這個現象的。那麼什麼叫作【傳統用法】呢?這裏我就給個我本身的定義吧: 所謂的WebForms的傳統用法是說:您的頁面一直使用服務器控件的提交方式(postback),在事件處理後,頁面又進入再一次的重現過程, 或者說:當前頁面一直在使用POST方式向當前頁面提交。
那麼如何避開這個問題呢?辦法大體有2種:
1. PRG模式(Post-Redirect-Get),在事件處理後,調用重定向的操做Response.Redirect(), 而不要在事件處理的後期再去給一些服務器控件綁定數據項了!
建議:按鈕事件只作一些提交數據的處理,將數據綁定的操做放在OnPreRender方法中處理,而不是寫在每一個事件中(遍地開花)。 不過,這種方式下,可能偉大的ViewState就發揮不了太大的做用了,若是您發現ViewState沒用了,在Web.config中全局關掉後, 又發現不少服務器控件的高級事件又不能用了!嗯,杯具備啊。
這個話題說下去又沒完沒了,到此爲止吧,不過,千萬不要覺得這種方法是在倒退哦。
2. 以Ajax方式提交表單,請繼續閱讀本文。
前面一直在說」瀏覽器提交表單",事實上咱們也能夠用JavaScript提交表單,好處也有不少,好比前面所說的F5刷新問題。 以Ajax方式提交表單的更大好處它是異步的,還能夠實現局部刷新,這些特性都是瀏覽器提交方式沒有的。 前面我提到表單在提交時,瀏覽器要實現的4個步驟,基本上用JS來完成這個操做也是同樣的。 可是,前面說的步驟好像很麻煩呢,有沒有簡單的方法來實現這個過程呢? 嗯,有的,這裏我將使用JQuery以及jquery.form.js這個插件來演示這個複雜過程的簡單處理方案。
示例用的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); } }); });
是的,就是這麼簡單,只要調用ajaxForm()就好了。你也能夠傳入任何$.ajax()能接受的參數。
它的做用是:修改表單的提交方式,改爲Ajax方式提交。最終當用戶點擊「提交」按鈕時,此時再也不是瀏覽器的提交行爲了, 而是使用Ajax的方式提交,提交的URL以及提交方法就是在FORM中指定的參數。
若是您但願要用戶點擊某個按鈕或者連接時,也能提交表單(不通過提交按鈕),那麼可使用以下方法:
$(function(){
$("#btnId").click(function(){ $('form').ajaxSubmit({ success: function(responseText){ alert(responseText); } }); }); });
變化很小,只須要將ajaxForm修改爲ajaxSubmit就OK了。 與ajaxForm()不一樣,調用ajaxSubmit()方法將會當即提交表單。
在前面的示例中,咱們看到以Ajax方式提交一個表單是很是容易的,它徹底模擬了瀏覽器的行爲。 不過,有時咱們可能須要只提交表單的一部分,爲的是更好的局部更新,那麼又該如何作呢?
假如我有如下表單的一部分,我只但願在用戶某個按鈕時將它提交到服務端:
<div id="divCustomerInfo"> <p>客戶名稱: <input type="text" name="CustomerName" style="width: 300px" /></p> <p>客戶電話: <input type="text" name="CustomerTel" style="width: 300px" /></p> </div>
咱們能夠這樣來提交這部分表單的數據:
$("#btnId").click(function(){ $.ajax({ url: "Handler1.ashx", type: "POST", data: $('#divCustomerInfo :text').fieldSerialize(), success: function(responseText){ alert(responseText); } }); return false; });
注意關鍵的代碼行:data: $('#divCustomerInfo :text').fieldSerialize()
注意:此時將由您指定一個【JQuery選擇器】來過濾要提交的控件,而不是使用成功控件的篩選邏輯。
或者,您也可使用下面將要介紹的方法,仍然是使用 data: {} 的方式,但須要手工指定數據成員。
JQuery愈來愈流行,以致於在建立MVC項目時,VS IDE會把JQuery也準備好了,可能MS認爲開發WEB項目離不開JQuery了。
的確,JQuery很是方便,尤爲是在處理DOM時,不只如此,在處理AJAX請求時,也很是方便。
不過,有件事卻讓我很納悶:常常看到有人在使用JQuery實現Ajax時,把一堆參數放在URL中傳遞,固然了, 發送GET請求嘛,這樣作不錯,可是,讓我不解的是:URL是拼接起來的,並且代碼又臭又長!
若是是一個簡單的參數:"aaa.aspx?id=" + xxId ,這樣也就罷了。可是當一堆參數拼接在一塊兒時,可能一會兒還看不清楚到底有幾個什麼樣的參數。 並且經驗豐富一些的開發人員會發現這樣作有時會有亂碼問題,可能網上搜事後,知道還有編碼的工做要處理,因而又加了一堆編碼方法。 到此爲止,這段代碼會讓人看起來很累!
若是您平時也是這樣作的,那麼我今天就告訴您:不要再拼接URL了! $.ajax()的參數不是有個data成員嘛,用它吧。看代碼:
$.ajax({
url: "Handler1.ashx", type: "POST", data: { id: 2, name: "aaa", tel: "~!@#$%^&*()_+-=<>?|", xxxx: "要多少還能夠寫多少", encoding: "見鬼去吧。?& :)" }, success: function(responseText) { $("#divResult").html(responseText); } });
你說什麼,只能使用GET ? 哦,那就改一下 type 參數吧。
$.ajax({
url: "Handler1.ashx", type: "GET", data: { id: 2, name: "aaa", tel: "~!@#$%^&*()_+-=<>?|", xxxx: "要多少還能夠寫多少", encoding: "見鬼去吧。?& :)" }, success: function(responseText) { $("#divResult").html(responseText); } });
看了這個示例,您還會繼續拼URL嗎?
說明:爲了排版簡單,我將參數放在一行了,建議實際使用時,不要擠在一行。
一般咱們在寫HTML代碼時,會給控件指定一個id屬性,這個屬性只供JS和CSS使用,在表單提交時,它不起任何做用。
在上面的示例代碼中,可能data {}中的各個value就來源於各個不一樣的控件,那麼爲那些控件指定相應的id屬性將會方便地找到它們。
可是若是不須要用JS和CSS控制的控件,或許它們只是用來顯示一些數據(只讀),那麼就沒有必要指定id屬性, 固然了,name屬性也能夠不用給出(避免提交無心義的數據)。
瀏覽器也是一個普通的應用程序,.net framework也提供一些類也能讓咱們直接發起HTTP請求。 今天我將再次用C#來模擬瀏覽器的提交請求,同時也能夠加深對HTTP請求的理解。
示例代碼分爲二段,一段示範了使用application/x-www-form-urlencoded編碼方式提交, 另外一段則示範了使用multipart/form-data的編碼方式。
爲了讓你們能再次利用這些代碼,我已將關鍵部分寫成獨立方法,但願當您有這方面的需求時能立刻能夠用上。 代碼以下:
1. application/x-www-form-urlencoded
/// <summary>
/// 向指定的URL地址發起一個POST請求,同時能夠上傳一些數據項。 /// </summary> /// <param name="url">要請求的URL地址</param> /// <param name="keyvalues">要上傳的數據項</param> /// <param name="encoding">發送,接收的字符編碼方式</param> /// <returns>服務器的返回結果</returns> static string SendHttpRequestPost(string url, Dictionary<string, string> keyvalues, Encoding encoding) { if( string.IsNullOrEmpty(url) ) throw new ArgumentNullException("url"); string postData = null; // 將數據項轉變成 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(); } } } // 調用上面方法的示例代碼 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 。注意這部分代碼有點複雜,所以我加了不少註釋。
/// <summary>
/// 向指定的URL地址發起一個POST請求,同時能夠上傳一些數據項以及上傳文件。 /// </summary> /// <param name="url">要請求的URL地址</param> /// <param name="keyvalues">要上傳的數據項</param> /// <param name="fileList">要上傳的文件列表</param> /// <param name="encoding">發送數據項,接收的字符編碼方式</param> /// <returns>服務器的返回結果</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方法 // 數據塊的分隔標記,用於設置請求頭,注意:這個地方最好不要使用漢字。 string boundary = "---------------------------" + Guid.NewGuid().ToString("N"); // 數據塊的分隔標記,用於寫入請求體。 // 注意:前面多了一段: "--" ,並且它們將獨佔一行。 byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n"); // 設置請求頭。指示是一個上傳表單,以及各數據塊的分隔標記。 request.ContentType = "multipart/form-data; boundary=" + boundary; // 先獲得請求流,準備寫入數據。 Stream stream = request.GetRequestStream(); if( keyvalues != null && keyvalues.Count > 0 ) { // 寫入非文件的keyvalues部分 foreach( KeyValuePair<string, string> kvp in keyvalues ) { // 寫入數據塊的分隔標記 stream.Write(boundaryBytes, 0, boundaryBytes.Length); // 寫入數據項描述,這裏的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 ) { // 寫入數據塊的分隔標記 stream.Write(boundaryBytes, 0, boundaryBytes.Length); // 寫入文件描述,這裏設置一個通用的類型描述:application/octet-stream,具體的描述在註冊表裏有。 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,對於漢字會有亂碼。 byte[] header = Encoding.UTF8.GetBytes(description); stream.Write(header, 0, header.Length); // 寫入文件內容 byte[] body = File.ReadAllBytes(kvp.Value); stream.Write(body, 0, body.Length); } // 寫入結束標記 boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n"); stream.Write(boundaryBytes, 0, boundaryBytes.Length); stream.Close(); // 開始發起請求,並獲取服務器返回的結果。 using( WebResponse response = request.GetResponse() ) { using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) { return reader.ReadToEnd(); } } } // 調用上面方法的示例代碼 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", "來幾個特殊字符:~!@#$%^&*()-=_+{}[]:;'\"<>?/.,|\\"); 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); }
說明:上面的示例方法中,我並無對KEY編碼,是由於:我想你們選用的KEY應該是不須要編碼的(英文字母與數字的組合)。
並且,我也沒加入對Cookie處理的那部分代碼,若是您須要在發送請求時,保留Cookie,那麼請參考我上一篇博客 【細說Cookie】中的示例代碼。
W3C Forms
http://www.w3.org/TR/html4/interact/forms.html
jquery.form.js
https://github.com/malsup/form
更多的jquery.form演示
http://www.cnblogs.com/fish-li/archive/2011/05/02/2034010.html