天天看點

【譯】利用Asp.net MVC處理檔案的上傳下載下傳

如果你僅僅隻有Asp.net Web Forms背景轉而學習Asp.net MVC的,我想你的第一個經曆或許是那些曾經讓你的程式設計變得愉悅無比的服務端控件都駕鶴西去了.FileUpload就是其中一個,而這個控件的缺席給我們帶來一些小問題。這篇文章主要說如何在Asp.net MVC中上傳檔案,然後如何再從伺服器中把上傳過的檔案下載下傳下來.

在Web Forms中,當你把一個FileUpload控件拖到設計器中,你或許沒有注意到在生成的HTML中會在form标簽中加入一條額外屬性enctype="multipart/form-data". 而FileUpload控件本身會生成為<input type=”file” />,在MVC的view裡,有許多種方法可以做到同樣效果,第一種的HTML如下:

<form action="/" method="post" enctype="multipart/form-data">

  <input type="file" name="FileUpload1" /><br />

  <input type="submit" name="Submit" id="Submit" value="Upload" />

</form>

注意form标簽已經包括了enctype标簽,而method屬性則設為”post”,這樣設定并不多于因為預設的送出時通過HTTP get方式進行的。下面這種方式,使用Html.BeginForm()擴充方法,會生成和上面同樣的HTML:

<%

  using (Html.BeginForm("", "home", FormMethod.Post, new {enctype="multipart/form-data"})) 

   {%> 

     <input type="file" name="FileUpload1" /><br />

     <input type="submit" name="Submit" id="Submit" value="Upload" />

<% }%>

注意<input type=”file”>标簽的name屬性,我們在後面再讨論,上面代碼會如下圖:

OK,現在我們可以浏覽本地檔案然後通過Upload送出按鈕将檔案送出到伺服器端,下一步就是在伺服器端處理上傳的檔案,在使用fileUpload控件時,你可以很輕松的通過FileUpload的hasFile方法來檢視檔案是否被上傳。但是在Asp.net MVC中貌似就不是這麼友善了,你會和原始的HTTP更接近一些,然而,一個擴充方法可以處理這些:

public static bool HasFile(this HttpPostedFileBase file)

{

  return (file != null && file.ContentLength > 0) ? true : false;

}

當你看到對應的Controller類的代碼時,你會發現Request對象作為HttpRequestBase類型的一個屬性存在。HttpReuqestBase其實是HTTP請求的一個封裝,暴漏了很多屬性,包括Files collection(其實是HttpFileCollectionBase的集合),在集合中的每一個元素都是HttpPostedFileBase的集合,擴充方法是用于確定上傳的檔案是否存在。實際上,這和FileUpload.HasFile()方法的工作原理一緻。

在Controller Action中使用起來其實很容易:

public class HomeController : Controller

  public ActionResult Index()

  {

    foreach (string upload in Request.Files)

    {

      if (!Request.Files[upload].HasFile()) continue;

      string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";

      string filename = Path.GetFileName(Request.Files[upload].FileName);

      Request.Files[upload].SaveAs(Path.Combine(path, filename));

    }

    return View();

  }

多檔案上傳

或許你已經比我更早的想到如何更好的将Request.Files作為一個集合使用。這意味着它不僅僅隻能容納一個檔案,而能容納多個,我們将上面的View改為如下:

     <input type="file" name="FileUpload2" /><br />

     <input type="file" name="FileUpload3" /><br />

     <input type="file" name="FileUpload4" /><br />

     <input type="file" name="FileUpload5" /><br />

效果如下:

在Controller的代碼中已經檢查了是否所有的檔案上傳框中都有檔案,是以即使對于多檔案上傳,我們也不再需要修改Controller的代碼,注意每一個<input type=”file”>都有不同的name屬性,如果你需要調用其中一個,比如說,你需要引用第三個輸入框隻需要使用:Request.Files["FileUpload3"].

存入資料庫

在你沖我狂吼”關注點分離”之前,我想聲明下面的代碼僅僅用于作為說明功能.我将ADO.Net的代碼放入Controller action中,但我們都知道,這并不好。資料通路的代碼應該放在Model中某個部分的資料通路層中.但是,下面這段代碼僅僅可以給大家怎樣将上傳的檔案存入資料庫中一個更直覺的印象,首先,我們需要建立一個資料表(FileTest)并建立一個表:FileStore

CREATE TABLE [dbo].[FileStore](

[ID] [int] IDENTITY(1,1) NOT NULL,

[FileContent] [image] NOT NULL,

[MimeType] [nvarchar](50) NOT NULL,

[FileName] [nvarchar](50) NOT NULL

) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

FileContent域是image資料類型,用于存儲以二進制資料形成的檔案,而Index Action改為:

public ActionResult Index()

  foreach (string upload in Request.Files)

    if (!Request.Files[upload].HasFile()) continue;

    string mimeType = Request.Files[upload].ContentType;

    Stream fileStream = Request.Files[upload].InputStream;

    string fileName = Path.GetFileName(Request.Files[upload].FileName);

    int fileLength = Request.Files[upload].ContentLength;

    byte[] fileData = new byte[fileLength];

    fileStream.Read(fileData, 0, fileLength);

    const string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";

    using (var conn = new SqlConnection(connect))

      var qry = "INSERT INTO FileStore (FileContent, MimeType, FileName) VALUES (@FileContent, @MimeType, @FileName)";

      var cmd = new SqlCommand(qry, conn);

      cmd.Parameters.AddWithValue("@FileContent", fileData);

      cmd.Parameters.AddWithValue("@MimeType", mimeType);

      cmd.Parameters.AddWithValue("@FileName", fileName);

      conn.Open();

      cmd.ExecuteNonQuery();

  return View();

修改後的代碼會以循環的方式周遊Web頁面中所有的上傳檔案,并檢查<input type=”file”>中是否已經加入檔案,然後,從檔案中提取出3個資訊:檔案名,MIME類型(檔案的類型),HTTP Request中的二進制流。二進制資料被轉換為byte數組,并以image資料類型存入資料庫。MIME類型和檔案名對于使用者從資料庫中提取檔案來說非常重要。

将資料庫中的檔案傳回給使用者:

你如何将檔案傳送給使用者取決于你最開始如何存儲它,如果你将檔案存入資料庫,你會用流的方式将檔案返還給使用者,如果你将檔案存在硬碟中,你隻需要提供一個超連結即可,或者也可以以流的方式。每當你需要以流的方式将檔案送到浏覽器中,你都的使用到File()方法的重載(而不是使用我們先前一直使用的View()方法),對于File()方法有3類傳回類型:FilePathResult,FileContentResult和FileStreamResult,第一種類型用于直接從磁盤傳回檔案;第二種類型用于将byte數組傳回用戶端;而第三種方式将已經生成并打開的流對象的内容傳回用戶端。

如果你還記得的話,我們将上傳的檔案存入了資料庫,并以byte數組的形式存入FileContent域内.而當需要提取時,它仍然會以一個byte數組進行提取,這意味着我們使用傳回FileContentResult的File()重載,如果我們想讓提取的檔案名更有意義,我們使用接受3個參數的重載,三個參數是:byte數組,MIME類型,檔案名:

public FileContentResult GetFile(int id)

  SqlDataReader rdr; byte[] fileContent = null; 

  string mimeType = "";string fileName = "";

  const string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";

  using (var conn = new SqlConnection(connect))

    var qry = "SELECT FileContent, MimeType, FileName FROM FileStore WHERE ID = @ID";

    var cmd = new SqlCommand(qry, conn);

    cmd.Parameters.AddWithValue("@ID", id);

    conn.Open();

    rdr = cmd.ExecuteReader();

    if (rdr.HasRows)

      rdr.Read();

      fileContent = (byte[])rdr["FileContent"];

      mimeType = rdr["MimeType"].ToString();

      fileName = rdr["FileName"].ToString();

  return File(fileContent, mimeType, fileName);

在View中最簡單的使用來使用這個Action隻需提供一個超連結:

<a href="/GetFile/1">Click to get file</a>

如果在資料庫中存儲的圖檔是圖檔類型,和使用超連結不同的是,我們通過指向Controller action的一個帶有src屬性的<image>标簽來擷取:

<img src="/GetFile/1" alt="My Image" />

下面再讓我們來看看使用FilePathResult(用于從硬碟提取檔案)是多簡單的事:

public FilePathResult GetFileFromDisk()

  string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";

  string fileName = "test.txt";

  return File(path + fileName, "text/plain", "test.txt");

而這也可以用過超連結提取:

<a href="/GetFileFromDisk">Click to get file</a>

而最後一個選擇FileStreamResult也可以從磁盤中提取檔案:

public FileStreamResult StreamFileFromDisk()

  return File(new FileStream(path + fileName, FileMode.Open), "text/plain", fileName);

FilePathResult和FileStreamResult的差別是什麼?我們又該如何取舍呢?主要的差別是FilePathResult使用HttpResponse.TransmitFile來将檔案寫入Http輸出流。這個方法并不會在伺服器記憶體中進行緩沖,是以這對于發送大檔案是一個不錯的選擇。他們的差別很像DataReader和DataSet的差別。于此同時, TransmitFile還有一個bug,這可能導緻檔案傳到用戶端一半就停了,甚至無法傳送。而FileStreamResult在這方面就很棒了。比如說:傳回Asp.net Chart 控件在記憶體中生成的圖表圖檔,而這并不需要将圖檔存到磁盤中.

------------------------------------------------

原文連結:http://www.mikesdotnetting.com/Article/125/ASP.NET-MVC-Uploading-and-Downloading-Files

Translated by:CareySon

分類: Asp.net MVC

本文轉自CareySon部落格園部落格,原文連結:http://www.cnblogs.com/CareySon/archive/2009/12/23/1630902.html,如需轉載請自行聯系原作者