在ASP.NET中,通过HTTP POST批量上传文件到服务器。


在写 ASP.NET 应用的时候, 往往会碰到客户端上传文件的情况,这个时候客户一般希望能够想 windows 应用一样, 能够选择文件夹, 浏览所要下载的文件,批量上传, 这个时候. 有几个特征:



  • 1. 客户可以自由的浏览本地的文件夹, 选择多个文件同时上传.
  • 2. 上传之前用户无法预知上传文件的数目.
  • 3. 因为是 ASP.NET 应用, 客户端可能没有装 .NET Framework.

其实,我们知道.如果要跟 IE 端客户文件系统交互的话,代码必须在客户端执行. 这个时候我们可以写一个 Activex 控件来实现选择文件夹和上传.


一般我们常用两种方式往服务端上传文件


1. FTP , 可以调用一些现成的 FTP 组件, 在 VB 里面可以调用 Internet Transfer Control


2. HTTP , 使用 HTTP Post application/octet-stream 格式的字节流给服务端.


FTP 很容易实现,我们不予考虑,着重提一下HTTP 的方式.


 


我们在单个文件上传的时候,一般都有以下的代码:


<%@ Page Language=”C#” AutoEventWireup=”True” %>


<html>
<head>
 
   <script runat=”server”>
 
      void Button1_Click(object sender, EventArgs e)
      {


         // Get the HtmlInputFile control from the Controls collection
         // of the PlaceHolder control.
         HtmlInputFile file = (HtmlInputFile)Place.FindControl(“File1”);
 
         // Make sure a file was submitted.
         if (Text1.Value == “”)
         {
            Span1.InnerHtml = “Error: you must enter a file name”;
            return;
         }
 
         // Save file to server.
         if (file.PostedFile != null)
         {
            try
            {
               file.PostedFile.SaveAs(“c:temp”+Text1.Value);
               Span1.InnerHtml = “File uploaded successfully to ” +
                  “<b>c:temp” + Text1.Value + “</b> on the Web server”;
            }
            catch (Exception exc)
            {
               Span1.InnerHtml = “Error saving file <b>c:temp” +
                                 Text1.Value + “</b><br>” + exc.ToString();
            }
         }
      }


      void Page_Load(object sender, EventArgs e)
      {


         // Create a new HtmlInputFile control.
         HtmlInputFile file = new HtmlInputFile();
         file.ID = “File1”;


         // Add the control to the Controls collection of the
         // PlaceHolder control.
         Place.Controls.Clear();
         Place.Controls.Add(file);


      }
 
   </script>
 
</head>
<body>
 
   <h3>HtmlInputFile Constructor Example</h3>
 
   <form enctype=”multipart/form-data” runat=”server”>
 
      Specify the file to upload:
      <asp:PlaceHolder id=”Place” runat=”server”/>
 
      <p>
      Save as file name (no path):
      <input id=”Text1″
             type=”text”
             runat=”server”>
 
      <p>
      <span id=Span1
            style=”font: 8pt verdana;”
            runat=”server” />


      <p>
      <input type=button
             id=”Button1″
             value=”Upload”
             OnServerClick=”Button1_Click”
             runat=”server”>
 
   </form>
 
</body>
</html>


代码有两部分操作:


1. 客户端, 通过用户选择要上传的文件, post 到服务端,这个时候注意 Post 的不是一些普通的表单,而是一个octet-stream  格式的流.


2. 服务端, 如果服务端 是 ASP.NET 处理程序的话, Request 对象有一个 Files 熟悉个您, Files 里面会包含你上传的各个文件内容和文件信息.


你需要做的就是调用一下 File.Save ,把文件复制到你的服务器目录.


我们模拟一下这个过程:


对于客户端, 我们写一个 windows 应用程序.


主要是以下几行代码:


Dim wc As New System.Net.WebClient
wc.Credentials = New System.Net.NetworkCredential(“username”, “password”, “domain”)
wc.UploadFile(“http://localhost/aspnet/UPloadFile/WebForm1.aspx“, “c:localfile.txt”)


注意: 很多人用 UploadFile ,希望传两个参数,第一个是服务器文件存放的位置, 第二个是本地文件路径.


这个时候, 如果你改为wc.UploadFile(“http://localhost/remotefile.txt”, “c:LocalFile.txt”), 一般会报错 405, “Methods are not allowed”, 错误有一点误导. 主要是告诉你 Remotefile.txt 无法处理你的 Post 方法. 而 IIS 的log 也会有类似的提示:


2004-08-13 03:37:57 127.0.0.1 POST /remotefile.txt – 80 – 127.0.0.1 – 405 0 1


所以这个时候明确一点, 服务端必须有一个文件能够处理这个post 过去的数据.


 


模拟服务端:


服务端就很简单了, 跟上传代码差不多.


新建一个 web form, html 代码如下.


<form id=”Form1″ encType=”multipart/form-data” runat=”server”>
   
 </form>


逻辑代码:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ‘在此处放置初始化页的用户代码
        Dim b() As Byte = Request.BinaryRead(Request.ContentLength)


        Dim fs As New System.IO.FileStream(“c:k.txt”, IO.FileMode.Append) //把request 请求的数据dump 到一个文件中
        Dim sw As New System.IO.BinaryWriter(fs)
        sw.Write(b)
        sw.Close()
        fs.Close()


        Request.Files(0).SaveAs(“C:t” & Request.Files(0).FileName) //另存客户端传上来的文件


    End Sub


运行这个程序. 你会发现客户端发过去的 请求,起始很有规则:



———————–8c64f47716481f0  //时间戳


Content-Disposition: form-data; name=”file”; filename=”a.txt”  //文件名


Content-Type: application/octet-stream


 


//文件的内容


———————–8c64f47716481f0


这个是.NET 测试的一些结果. 起始我们看一下 WebClient.UploadFile , 他起始内部就是整理形成一个 request 流.以下是对 webclient.uploadFile 反编译后看到的结果.


public byte[] UploadFile(string address, string method, string fileName)
{
      string text1;
      string text2;
      WebRequest request1;
      string text3;
      byte[] buffer1;
      byte[] buffer2;
      long num1;
      byte[] buffer3;
      int num2;
      WebResponse response1;
      byte[] buffer4;
      DateTime time1;
      long num3;
      string[] textArray1;
      FileStream stream1 = null;
      try
      {
            fileName = Path.GetFullPath(fileName);
            time1 = DateTime.Now;
            num3 = time1.Ticks;
            text1 = “———————” + num3.ToString(“x”);
            if (this.m_headers == null)
            {
                  this.m_headers = new WebHeaderCollection();
            }
            text2 = this.m_headers[“Content-Type”];
            if (text2 != null)
            {
                  if (text2.ToLower(CultureInfo.InvariantCulture).StartsWith(“multipart/”))
                  {
                        throw new WebException(SR.GetString(“net_webclient_Multipart”));
                  }
            }
            else
            {
                  text2 = “application/octet-stream”;
            }
            this.m_headers[“Content-Type”] = “multipart/form-data; boundary=” + text1;
            this.m_responseHeaders = null;
            stream1 = new FileStream(fileName, FileMode.Open, FileAccess.Read);
            request1 = WebRequest.Create(this.GetUri(address));
            request1.Credentials = this.Credentials;
            this.CopyHeadersTo(request1);
            request1.Method = method;
            textArray1 = new string[7];
            textArray1[0] = “–“;
            textArray1[1] = text1;
            textArray1[2] = “rnContent-Disposition: form-data; name=”file”; filename=””;
            textArray1[3] = Path.GetFileName(fileName);
            textArray1[4] = “”rnContent-Type: “;
            textArray1[5] = text2;
            textArray1[6] = “rnrn”;
            text3 = string.Concat(textArray1);
            buffer1 = Encoding.UTF8.GetBytes(text3);
            buffer2 = Encoding.ASCII.GetBytes(“rn–” + text1 + “rn”);
            num1 = 9223372036854775807;
            try
            {
                  num1 = stream1.Length;
                  request1.ContentLength = ((num1 + ((long) buffer1.Length)) + ((long) buffer2.Length));
            }
            catch
            {
            }
            buffer3 = new byte[Math.Min(((int) 8192), ((int) num1))];
            using (Stream stream2 = request1.GetRequestStream())
            {
                  stream2.Write(buffer1, 0, buffer1.Length);
                  do
                  {
                        num2 = stream1.Read(buffer3, 0, buffer3.Length);
                        if (num2 != 0)
                        {
                              stream2.Write(buffer3, 0, num2);
                        }
                  }
                  while ((num2 != 0));
                  stream2.Write(buffer2, 0, buffer2.Length);
            }
            stream1.Close();
            stream1 = null;
            response1 = request1.GetResponse();
            this.m_responseHeaders = response1.Headers;
            return this.ResponseAsBytes(response1);
      }
      catch (Exception exception1)
      {
            if (stream1 != null)
            {
                  stream1.Close();
                  stream1 = null;
            }
            if ((exception1 is WebException) || (exception1 is SecurityException))
            {
                  throw;
            }
            throw new WebException(SR.GetString(“net_webclient”), exception1);
      }
      return buffer4;
}


还是很容易看懂的.


如果写 Activex 的话, 在 VB 中可以调用 Internet Transfer 控件, 我们模拟发一个文件给服务端.


Private Sub Command1_Click()
    Dim s As String
    s = s & “———————–8c64f47716481f0” & vbCrLf
    s = s & “Content-Disposition: form-data; name=””file””; filename=””a.txt””” & vbCrLf
    s = s & “Content-Type: application/octet-stream” & vbCrLf & vbCrLf
   
    s = s & “Hello , World” & vbCrLf
    s = s & “———————–8c64f47716481f0”
    Inet1.Execute “http://localhost/aspnet/UPloadFile/WebForm1.aspx“, “POST”, s
End Sub


这时候服务端就会收到一个文件.


需要说明的是:



  1. 仅仅为了说明 HTTP Post 的原理,没有考虑很多的代码细节.
  2. 使用的时候请做适当的调整.

Comments are closed.

Post Navigation