現在的位置: 首頁 > 綜合 > 正文

細說 Form (表單)

2019年10月29日 ⁄ 綜合 ⁄ 共 15697字 ⁄ 字號 評論關閉

Form(表單)對於每個WEB開發人員來說,應該是再熟悉不過的東西了,可它卻是頁面與WEB服務器交互過程中最重要的信息來源。 雖然Asp.net WebForms框架為了幫助我們簡化開發工作,做了很完美的封裝,讓我們只需要簡單地使用服務端控件就可以直接操作那些 HTML表單元素了。但我認為了解一些基礎的東西,可以使我們不必束縛在WebForms框架上,以及遇到一些奇怪問題時, 可以更從容地解決它們。

今天,我將和大家來聊聊表單,這個簡單又基礎的東西。我將站在HTML和單純的Asp.net框架的角度來解釋它們的工作方式, 因此,本文不演示WebForms服務器控件的相關內容。

簡單的表單,簡單的處理方式

好了,讓我們進入今天的主題,看看下面這個簡單的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>

在這個HTML表單中,我定義了二個文本輸入框,一個提交按鈕,表單將提交到Handler1.ashx中處理,且以POST的方式。
注意哦,如果我們想讓純靜態頁面也能向服務器提交數據,就可以採用這樣方式來處理:將action屬性指向一個服務器能處理的地址。

說明:當我們使用WebForms的服務器表單控件時,一般都會提交到頁面自身來處理(action屬性指向當前頁面), 這樣可以方便地使用按鈕事件以及從服務器控件訪問從瀏覽器提交的控件輸入結果。
如果在URL重寫時,希望能在頁面回傳時保持URL不變,即:action為重寫後的URL,那麼可以Page類中執行以下調用:

Form.Action = Request.RawUrl;    // 受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1

好了,我們再回到前面那個HTML表單,看一下如果用戶點擊了“提交”按鈕,瀏覽器是如何把表單的內容發出的。 在此,我們需要Fiddler工具的協助,請在提交表單前啟動好Fiddler。我將這個表單的提交請求過程做了如下截圖。

上圖是將要提交的表單的輸入情況,下圖是用Fiddler看到的瀏覽器發出的請求內容。

在這張圖片中,我們可以看到瀏覽器確實將請求發給了我前面在action中指定的地址,且以POST形式發出的。 表單的二個控件的輸入值放在請求體中,且做了【編碼】處理,編碼的方式用請求頭【Content-Type】說明, 這樣,當服務端收到請求後,就知道該如何讀取請求的內容了。 注意:表單的數據是以name1=value1&name2=value2 的形式提交的,其中name,value分別對應了表單控件的相應屬性。

我們還可以在Fiddler中,將視圖切換到WebForms選項卡,這樣能更清楚地只查看瀏覽器提交的數據,如下圖。

看了客戶端的頁面和請求的內容,我們再來看看在服務端如何獲取瀏覽器提交的表單的輸入吧,代碼如下:

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的一些圖形類打開文件,然後"另存"文件。 總之,在安全問題面前只有一個原則:不要相信用戶的輸入,一定要檢查或者轉換。

MVC Controller中多個自定義類型的傳入參數

前面的所有示例代碼中都有一個規律:在服務端讀取瀏覽器提交的數據時,都會使用控件的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是有關係的。

F5刷新問題並不是WebForms的錯

剛才說到了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方式提交表單,請繼續閱讀本文。

以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方式提交部分表單

在前面的示例中,我們看到以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,就不要再拼URL了!

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嗎?

說明:為了排版簡單,我將參數放在一行了,建議實際使用時,不要擠在一行。

id, name 有什麼關係

通常我們在寫HTML代碼時,會給控件指定一個id屬性,這個屬性只供JS和CSS使用,在表單提交時,它不起任何作用。

在上面的示例代碼中,可能data {}中的各個value就來源於各個不同的控件,那麼為那些控件指定相應的id屬性將會方便地找到它們。
但是如果不需要用JS和CSS控制的控件,或許它們只是用來顯示一些數據(只讀),那麼就沒有必要指定id屬性, 當然了,name屬性也可以不用給出(避免提交無意義的數據)。

使用C#模擬瀏覽器提交表單

瀏覽器也是一個普通的應用程序,.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

分類: Asp.net

抱歉!評論已關閉.