细说Form(表单)

    Form(表单)对于每一个WEB开发人员来讲,应该是再熟悉不过的东西了,可它倒是页面与WEB服务器交互过程当中最重要的信息来源。 虽然Asp.net WebForms框架为了帮助咱们简化开发工做,作了很完美的封装,让咱们只须要简单地使用服务端控件就能够直接操做那些 HTML表单元素了。但我认为了解一些基础的东西,可使咱们没必要束缚在WebForms框架上,以及遇到一些奇怪问题时, 能够更从容地解决它们。javascript

    今天,我将和你们来聊聊表单,这个简单又基础的东西。我将站在HTML和单纯的Asp.net框架的角度来解释它们的工做方式, 所以,本文不演示WebForms服务器控件的相关内容。html

简单的表单,简单的处理方式

好了,让咱们进入今天的主题,看看下面这个简单的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 SP一、3.0 SP一、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上传文件控件来讲,若是它包含了选择的文件,那么它将是一个成功控件。
注意:
  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】中的示例代码。

             Fish Li的不少文章都不错微笑
相关文章
相关标签/搜索