之前在 ASP.NET 寫過一篇「
透過資料庫上傳下載檔案」,不過此方法有些缺點,例如,資料庫肥大,這些進入資料庫的檔案都是二進位儲存,所以就算是使用資料庫壓縮,會讓資料庫長的肥肥胖胖的。再來,如果你的架構比較大,是將Web Application與Database分開不同伺服器,那麼Web Application與Database之間的頻寬、Disk I/O成本也會大些。
我們來改來方式,將檔案上傳至Web Application特定目錄下,然後將此上傳檔案的資訊儲存至資料庫,這些資訊都是文字,所以對資料庫大小影響不大,就算是上萬十萬筆資訊,文字資料的壓縮率是非常不錯的。當我要下載檔案時,從資料庫取出檔案資訊,然後由Web Application的特定目錄讀出檔案,傳送給Browser進行下載。
以下以ASP.NET MVC實作,會有兩個部分,一是單一檔案上傳,二是多檔案上傳。
資料庫資料
我在 App_Data 裡新檔一個 Files.mdf,表格名稱 FileDown,Schema為
5 | FileVersion, nvarchar(50) |
7 | UploadDate, datetime2(7) |
8 | rowguid, uniqueidentifier |
在 Models 目錄下建立 FileModel.edmx 及 FileModel.Context.tt,FileModel.tt。(Entity Framework 4.1)
MVC 單一檔案上傳
新增 FileController.vb,把我們的架構先寫出來。
03 | Public Class fileController |
04 | Inherits System.Web.Mvc.Controller |
07 | Function Index() As ActionResult |
12 | Function Upload() As ActionResult |
18 | Function Upload(upfile As HttpPostedFileBase, formData As FormCollection) As ActionResult |
23 | Function MultiUpload() As ActionResult |
29 | Function MultiUpload(form As FormCollection) As ActionResult |
34 | Function Download(id As Integer ) As ActionResult |
我們一個一個來處理。
2 | Function Index() As ActionResult |
3 | Dim db As New FilesEntities |
4 | Return View(db.FileDown) |
Index.aspx 內容
01 | <%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of UpDownFileFromDBMvc.FileDown))" %> |
03 | < asp:Content ID = "Content1" ContentPlaceHolderID = "TitleContent" runat = "server" > |
07 | < asp:Content ID = "Content2" ContentPlaceHolderID = "MainContent" runat = "server" > |
11 | < li ><%: Html.ActionLink("單一檔案上傳", "Upload")%></ li > |
12 | < li ><%: Html.ActionLink("多檔案上傳", "MultiUpload")%></ li > |
42 | <% For Each item In Model %> |
43 | <% Dim currentItem = item %> |
46 | <%: Html.DisplayFor(Function(modelItem) currentItem.FileId) %> |
49 | <%: Html.ActionLink(currentItem.FileName, "download", New With {.id = currentItem.FileId})%> |
52 | <%: Html.DisplayFor(Function(modelItem) currentItem.FileSize) %> |
55 | <%: Html.DisplayFor(Function(modelItem) currentItem.FileType) %> |
58 | <%: Html.DisplayFor(Function(modelItem) currentItem.FileVersion) %> |
61 | <%: Html.DisplayFor(Function(modelItem) currentItem.PostDate) %> |
64 | <%: Html.DisplayFor(Function(modelItem) currentItem.UploadDate) %> |
67 | <%: Html.DisplayFor(Function(modelItem) currentItem.rowguid) %> |
要注意的有 <%: Html.ActionLink(currentItem.FileName, "download", New With {.id = currentItem.FileId})%> ,上傳後,我們希望可以直接點擊檔案名稱就可以下載檔案,所以我們把原本的DisplayFor()改寫為Html.ActionLink()。
07 | Function Upload(upfile As HttpPostedFileBase, formData As FormCollection) As ActionResult |
08 | Using db As New FilesEntities |
10 | If upfile IsNot Nothing Then |
12 | Dim MBSize As Integer = upfile.ContentLength / 1000 / 1000 |
13 | If upfile.ContentLength > 0 AndAlso MBSize < 4 Then |
14 | Dim savePath As String = Path.Combine(Server.MapPath( "~/Files/" ), upfile.FileName) |
17 | If Path.GetExtension(savePath) <> ".7z" Then |
18 | ModelState.AddModelError( "upfile" , "檔案必須由 7z 壓縮才能上傳!" ) |
23 | If IsDate(formData( "PostDate" )) = False Then |
24 | ModelState.AddModelError( "PostDate" , "日期格式不正確!" ) |
29 | If formData( "FileVersion" ) = "" Then |
30 | ModelState.AddModelError( "FileVersion" , "請輸入此檔案的版本!" ) |
35 | If System.IO.File.Exists(savePath) = False Then |
38 | Dim FileDuplicate As FileDown = (From f In db.FileDown |
39 | Where (f.FileName = upfile.FileName) |
40 | Select f).FirstOrDefault() |
43 | If FileDuplicate Is Nothing Then |
45 | upfile.SaveAs(savePath) |
48 | Dim file As New FileDown |
49 | file.FileName = upfile.FileName |
50 | file.FileSize = upfile.ContentLength |
51 | file.FileType = upfile.ContentType |
52 | file.PostDate = formData( "PostDate" ) |
53 | file.FileVersion = formData( "FileVersion" ) |
54 | file.UploadDate = Date .Now() |
55 | file.rowguid = Guid.NewGuid() |
61 | ModelState.AddModelError( "upfile" , "資料庫檔案資訊未刪除!" ) |
65 | ModelState.AddModelError( "upfile" , "檔案已存在!" ) |
72 | Return RedirectToAction( "Index" ) |
我們在專案目鍵下建立一個 Files 目錄,用來儲存上傳的檔案。
這裡要注意傳入參數的前後順序,Upload.aspx 的表單順序,如果上傳檔案欄位第一個,那麼 upfile As HttpPostedFileBase 就要在第一個,反之亦然。
Upload.aspx
01 | <%@ Page Title= "" Language= "VB" MasterPageFile= "~/Views/Shared/Site.Master" Inherits = "System.Web.Mvc.ViewPage" %> |
03 | <asp:Content ID= "Content1" ContentPlaceHolderID= "TitleContent" runat= "server" > |
07 | <asp:Content ID= "Content2" ContentPlaceHolderID= "MainContent" runat= "server" > |
09 | <h2>Upload One File</h2> |
11 | <%Using Html.BeginForm( "Upload" , "file" , FormMethod.Post, New With {.enctype = "multipart/form-data" })%> |
12 | <%: Html.ValidationSummary( True )%> |
13 | File:<input id= "upfile" name= "upfile" type= "file" value= "" /> |
14 | <%: Html.ValidationMessage( "upfile" )%> |
17 | PostDate:<%: Html.TextBox( "PostDate" )%> |
18 | <%: Html.ValidationMessage( "PostDate" )%> |
21 | Version:<%: Html.TextBox( "FileVersion" )%> |
22 | <%: Html.ValidationMessage( "FileVersion" )%> |
25 | <input type= "submit" value= "Upload" /> |
MVC 多檔案上傳
接下來我們來看多檔案上傳。
06 | Function MultiUpload(form As FormCollection) As ActionResult |
09 | Dim Msg As String = String .Empty |
10 | For i As Integer = 0 To Request.Files.Count - 1 |
11 | Msg += Request.Files(i).FileName & " 上傳成功! |
13 | Request.Files(i).SaveAs(Server.MapPath( "~/Files/" ) & Request.Files(i).FileName) |
16 | ViewBag.Msg = MvcHtmlString.Create(Msg) |
這裡讓我偷懶一下,
檔案的檢查與資訊新增至資料庫都和Upload差不多,讓你動點手。
01 | <%@ Page Title= "" Language= "VB" MasterPageFile= "~/Views/Shared/Site.Master" Inherits = "System.Web.Mvc.ViewPage" %> |
03 | <asp:Content ID= "Content1" ContentPlaceHolderID= "TitleContent" runat= "server" > |
07 | <asp:Content ID= "Content2" ContentPlaceHolderID= "MainContent" runat= "server" > |
09 | <h2>Upload Multi-Files</h2> |
10 | <%: Html.ActionLink( "Go back Index" , "Index" ) %> |
11 | <% Using (Html.BeginForm( "MultiUpload" , "File" , FormMethod.Post, New With {.enctype = "multipart/form-data" }))%> |
12 | <div class= "uploadfiles" > |
14 | <input type= "file" name= "files" /> |
18 | <a href= "#add" id= "additem" >Add Upload File</a> |
22 | <input type= "submit" value= "Upload" /> |
27 | <script type= "text/javascript" > |
重點在列下方那一段 jQeruy,讓我們重態新增要上傳資料的欄位。
MVC 檔案下載
最後的Download程式。
01 | Function Download(id As Integer ) As ActionResult |
02 | Using db As New FilesEntities |
05 | Dim getfile = (From f In db.FileDown |
07 | Select f).FirstOrDefault() |
09 | If getfile.FileName IsNot Nothing Then |
10 | Dim FilePath As String = Server.MapPath( "~/Files/" & getfile.FileName) |
13 | If System.IO.File.Exists(FilePath) Then |
14 | Return File(FilePath, getfile.FileType, getfile.FileName) |
18 | Return Content( "<span style='color:red'>無法下載檔案!</span>" ) |
22 | Return RedirectToAction( "Index" ) |
我們資料表設計裡有Guid,你也可以設計一個使用Guid來讓使用者下載的方法,這樣使用者就無法使用1,2,3,4這樣的好猜的數字來下載資料。
MVC 檔案刪除
又想偷懶了,程式給你寫。
- 傳入id從資料庫找到檔案名稱。
- 使用Path.Combine與Server.MapPath()組合出完整路徑。
- 使用File.Exists(路徑)判斷檔案是否存在。
- 存在,刪除檔案及資料庫資訊。( File.Delete(路徑) )
這樣一個可上傳、下載、刪除的簡易檔案管理程式就完成了。
參考資料
- ASP.NET MVC 檔案上傳下載是很方便的
- ASP.NET MVC的檔案上傳與下載
- ASP.NET MVC FileUpload 檔案上傳
留個記錄,如果上傳的是圖片檔案,除一般副檔案…等檢查外,最好還加上 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,測試下載程式,一切都正常。