我們來改來方式,將檔案上傳至Web Application特定目錄下,然後將此上傳檔案的資訊儲存至資料庫,這些資訊都是文字,所以對資料庫大小影響不大,就算是上萬十萬筆資訊,文字資料的壓縮率是非常不錯的。當我要下載檔案時,從資料庫取出檔案資訊,然後由Web Application的特定目錄讀出檔案,傳送給Browser進行下載。
以下以ASP.NET MVC實作,會有兩個部分,一是單一檔案上傳,二是多檔案上傳。
資料庫資料
我在 App_Data 裡新檔一個 Files.mdf,表格名稱 FileDown,Schema為
FileId, int, PK FileName, nvarchar(50) FileSize, nvarchar(50) FileType, nvarchar(50) FileVersion, nvarchar(50) PostDate, datetime2(7) UploadDate, datetime2(7) rowguid, uniqueidentifier
在 Models 目錄下建立 FileModel.edmx 及 FileModel.Context.tt,FileModel.tt。(Entity Framework 4.1)
MVC 單一檔案上傳
新增 FileController.vb,把我們的架構先寫出來。
Imports System.IO Public Class fileController Inherits System.Web.Mvc.Controller ' 顯示檔案列表 Function Index() As ActionResult Return View() End Function ' 單一檔案上傳表單 Function Upload() As ActionResult Return View() End Function ' 處理單一檔案上傳 <HttpPost()> Function Upload(upfile As HttpPostedFileBase, formData As FormCollection) As ActionResult Return View() End Function ' 多檔案上傳表單 Function MultiUpload() As ActionResult Return View() End Function ' 處理多檔案上傳 <HttpPost()> Function MultiUpload(form As FormCollection) As ActionResult Return View() End Function ' 處理檔案下載 Function Download(id As Integer) As ActionResult Return View() End Function
我們一個一個來處理。
' 顯示檔案列表 Function Index() As ActionResult Dim db As New FilesEntities Return View(db.FileDown) End FunctionIndex.aspx 內容
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of UpDownFileFromDBMvc.FileDown))" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> 檔案列表 </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>檔案列表</h2> <ul> <li><%: Html.ActionLink("單一檔案上傳", "Upload")%></li> <li><%: Html.ActionLink("多檔案上傳", "MultiUpload")%></li> </ul> <table> <tr> <th> FileId </th> <th> FileName </th> <th> FileSize </th> <th> FileType </th> <th> FileVersion </th> <th> PostDate </th> <th> UploadDate </th> <th> rowguid </th> </tr> <% For Each item In Model %> <% Dim currentItem = item %> <tr> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.FileId) %> </td> <td> <%: Html.ActionLink(currentItem.FileName, "download", New With {.id = currentItem.FileId})%> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.FileSize) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.FileType) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.FileVersion) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.PostDate) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.UploadDate) %> </td> <td> <%: Html.DisplayFor(Function(modelItem) currentItem.rowguid) %> </td> </tr> <% Next %> </table> </asp:Content>
要注意的有 <%: Html.ActionLink(currentItem.FileName, "download", New With {.id = currentItem.FileId})%> ,上傳後,我們希望可以直接點擊檔案名稱就可以下載檔案,所以我們把原本的DisplayFor()改寫為Html.ActionLink()。
''' <summary> ''' MVC 單一檔案上傳 ''' </summary> ''' <param name="upfile">上傳檔案</param> ''' <param name="formData">其他表單資料</param> <HttpPost()> Function Upload(upfile As HttpPostedFileBase, formData As FormCollection) As ActionResult Using db As New FilesEntities ' 需有上傳檔案 If upfile IsNot Nothing Then ' 0 < 容量 < 4 MB Dim MBSize As Integer = upfile.ContentLength / 1000 / 1000 If upfile.ContentLength > 0 AndAlso MBSize < 4 Then Dim savePath As String = Path.Combine(Server.MapPath("~/Files/"), upfile.FileName) ' 只能上傳 7z 壓縮的檔案 If Path.GetExtension(savePath) <> ".7z" Then ModelState.AddModelError("upfile", "檔案必須由 7z 壓縮才能上傳!") Return View() End If ' 日期需正確 If IsDate(formData("PostDate")) = False Then ModelState.AddModelError("PostDate", "日期格式不正確!") Return View() End If ' 版本不得空白 If formData("FileVersion") = "" Then ModelState.AddModelError("FileVersion", "請輸入此檔案的版本!") Return View() End If ' 檔案不存在,進行上傳儲存動作 If System.IO.File.Exists(savePath) = False Then ' 進行資料庫確認 Dim FileDuplicate As FileDown = (From f In db.FileDown Where (f.FileName = upfile.FileName) Select f).FirstOrDefault() ' 檔名未重複 If FileDuplicate Is Nothing Then ' 儲存至Disk upfile.SaveAs(savePath) ' 設定檔案資訊 Dim file As New FileDown file.FileName = upfile.FileName file.FileSize = upfile.ContentLength file.FileType = upfile.ContentType file.PostDate = formData("PostDate") file.FileVersion = formData("FileVersion") file.UploadDate = Date.Now() file.rowguid = Guid.NewGuid() ' 將檔案資訊儲存至資料庫 db.FileDown.Add(file) db.SaveChanges() Else ModelState.AddModelError("upfile", "資料庫檔案資訊未刪除!") Return View() End If Else ModelState.AddModelError("upfile", "檔案已存在!") Return View() End If End If End If End Using Return RedirectToAction("Index") End Function
我們在專案目鍵下建立一個 Files 目錄,用來儲存上傳的檔案。這裡要注意傳入參數的前後順序,Upload.aspx 的表單順序,如果上傳檔案欄位第一個,那麼 upfile As HttpPostedFileBase 就要在第一個,反之亦然。
Upload.aspx
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Upload One File </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>Upload One File</h2> <%Using Html.BeginForm("Upload", "file", FormMethod.Post, New With {.enctype = "multipart/form-data"})%> <%: Html.ValidationSummary(True)%> File:<input id="upfile" name="upfile" type="file" value="" /> <%: Html.ValidationMessage("upfile")%> <br /> PostDate:<%: Html.TextBox("PostDate")%> <%: Html.ValidationMessage("PostDate")%> <br /> Version:<%: Html.TextBox("FileVersion")%> <%: Html.ValidationMessage("FileVersion")%> <br /> <input type="submit" value="Upload" /> <%End Using%> </asp:Content>
MVC 多檔案上傳
接下來我們來看多檔案上傳。''' <summary> ''' MVC 多檔案同時上傳 ''' </summary> ''' <param name="form">表單資料</param> <HttpPost()> Function MultiUpload(form As FormCollection) As ActionResult ' 資料檢查,請自行設計 Dim Msg As String = String.Empty For i As Integer = 0 To Request.Files.Count - 1 Msg += Request.Files(i).FileName & " 上傳成功!<br />" Request.Files(i).SaveAs(Server.MapPath("~/Files/") & Request.Files(i).FileName) Next ViewBag.Msg = MvcHtmlString.Create(Msg) Return View() End Function
這裡讓我偷懶一下,檔案的檢查與資訊新增至資料庫都和Upload差不多,讓你動點手。
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Upload Multi-Files </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>Upload Multi-Files</h2> <%: Html.ActionLink("Go back Index","Index") %> <% Using (Html.BeginForm("MultiUpload", "File", FormMethod.Post, New With {.enctype = "multipart/form-data"}))%> <div class="uploadfiles"> <p> <input type="file" name="files" /> </p> </div> <p> <a href="#add" id="additem">Add Upload File</a> <br /><br /> <input type="submit" value="Upload" /> </p> <% End Using%> <%: ViewBag.Msg %> <script type="text/javascript"> $('#additem').live('click', function () { $('.uploadfiles').append($("<p><input type='file' name='files' /></p>")); }); </script> </asp:Content>
重點在列下方那一段 jQeruy,讓我們重態新增要上傳資料的欄位。
MVC 檔案下載
最後的Download程式。Function Download(id As Integer) As ActionResult Using db As New FilesEntities ' 取得檔案資訊 Dim getfile = (From f In db.FileDown Where f.FileId = id Select f).FirstOrDefault() If getfile.FileName IsNot Nothing Then Dim FilePath As String = Server.MapPath("~/Files/" & getfile.FileName) ' 進行下載 If System.IO.File.Exists(FilePath) Then Return File(FilePath, getfile.FileType, getfile.FileName) End If Else ' 回應錯誤 Return Content("<span style='color:red'>無法下載檔案!</span>") End If End Using Return RedirectToAction("Index") End Function
我們資料表設計裡有Guid,你也可以設計一個使用Guid來讓使用者下載的方法,這樣使用者就無法使用1,2,3,4這樣的好猜的數字來下載資料。
MVC 檔案刪除
又想偷懶了,程式給你寫。- 傳入id從資料庫找到檔案名稱。
- 使用Path.Combine與Server.MapPath()組合出完整路徑。
- 使用File.Exists(路徑)判斷檔案是否存在。
- 存在,刪除檔案及資料庫資訊。( File.Delete(路徑) )
留個記錄,如果上傳的是圖片檔案,除一般副檔案…等檢查外,最好還加上 byte() 檢查。
回覆刪除1.http://www.dotnetexpertguide.com/2011/05/validate-uploaded-image-content-in.html
2.http://www.mikekunz.com/image_file_header.html
3.http://www.blueshop.com.tw/board/FUM20041006161839LRJ/BRD20070801180408XHE/2.html
我使用chrome會抓不到檔案
回覆刪除感謝反應,我找個時間測試一下,再回答。(應該是中秋過後)
回覆刪除ASP.NET MVC 開發心得分享 (22):關於 executionTimeout: http://blog.miniasp.com/post/2011/09/08/ASPNET-MVC-Developer-Note-Part-22-About-httpRuntime-executionTimeout.aspx
回覆刪除MVC 預設不理會 timeout 設定,記錄一下。
Dear 匿名者
回覆刪除我在 Chrome 13/14, IE9, FF6,測試下載程式,一切都正常。