透過資料庫上傳下載檔案

在ASP時候,寫個上傳檔案都很麻煩,因為ASP本身沒有支援上傳的物件,必須使用第三方物件來進行上傳功能的實作,到了ASP.NET上傳實在很簡單,拉個FileUpload物件,寫簡單的幾行程式,馬上可以有上傳功能。

有了上傳,那下載呢?

下載就很笨,通常是網頁帶「Link」的方式,例如:「http://localhost/files/KKBruce.zip」這樣的方式來讓人下載。好一點的架個FTP,但還是寫個管理Link資料庫及程式,例如:「http://localhost/files/download.asp?id=1」取出「ftp://localhost/files/bruce/blog.zip」來讓使用者下載。久而久之,檔案一多還是不好管理,又要管檔案,又要管理Link!

所以想就使用「資料庫」來管理,這裡指的資料庫不是那種管理「路徑」的Table,而且將檔案整個存到Table裡,需要時再取出來使用,以下我們就來實作上傳檔案至資料庫,由資料庫取出檔案下載兩部份。

01****** Object:  Table [dbo].[Files]    Script Date: 08/26/2010 17:12:17 ******/
02SET ANSI_NULLS ON
03GO
04 
05SET QUOTED_IDENTIFIER ON
06GO
07 
08SET ANSI_PADDING ON
09GO
10 
11CREATE TABLE [dbo].[Files](
12 [uid] [int] IDENTITY(1,1) NOT NULL,
13 [Name] [nvarchar](50) NOT NULL,
14 [Body] [varbinary](max) NOT NULL,
15 [Size] [decimal](18, 0) NOT NULL,
16 [SaveDateTime] [datetime] NOT NULL,
17 [LastUseDateTime] [datetime] NULL
18) ON [PRIMARY]
19 
20GO
21 
22SET ANSI_PADDING OFF
23GO

我們先建一個資料表,裡面最重要的是「Body」,Body就是要儲存檔案的地方,而一般網路資料或書籍資料都是使用「Image資料類型」,但查看MSDN後發現,如果你是使用SQL Server 2005以後的版本,請改使用請改用 nvarchar(max)、varchar(max) 和 varbinary(max)

在未來的 Microsoft SQL Server 版本中,將移除 ntext、text 和 image 等資料類型。請避免在新的開發工作中使用這些資料類型,並規劃修改目前在使用這些資料類型的應用程式。請改用 nvarchar(max)、varchar(max) 和 varbinary(max)。

以上是要注意的地方。

接下來我們來實作上傳檔案到資料庫,然後再從資料庫下載檔案。

新增Default.aspx在UI部份:

01<div>
02        <asp:FileUpload ID="FU" runat="server" />
03      
04        <asp:Button ID="SaveFile" runat="server" Text="上傳" />
05         
06 
07         
08 
09        編號:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
10 <asp:Button ID="DownFile" runat="server" Text="下載" />
11    </div>

我們針對SaveFile及DownFile來撰寫程式碼:

SaveFile事件:
01Protected Sub SaveFile_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SaveFile.Click
02        ' 先確認有選擇檔案
03        If Not FU.HasFile Then
04            Response.Write("沒有上傳的檔案!")
05            Response.End()
06        End If
07 
08        ' 檔案存進資料庫都是「二進位」,所以使用Byte
09        ' buf陣列依檔案大小來建立
10        ' 陣列由0開始,所以減1
11        Dim buf(Me.FU.PostedFile.ContentLength - 1) As Byte
12        Me.FU.PostedFile.InputStream.Read(buf, 0, Me.FU.PostedFile.ContentLength)
13 
14        Dim conn As New SqlConnection(WebConfigurationManager.ConnectionStrings("SCS").ConnectionString)
15 
16        ' 將參考及欄位對應起來
17        Dim cmd As New SqlCommand("INSERT INTO Files(Name,Body,Size,SaveDateTime) VALUES (@filename,@body,@size, @sdt);", conn)
18        cmd.Parameters.AddWithValue("@filename", FU.PostedFile.FileName)
19        ' 將二進位檔放入資料庫中
20        cmd.Parameters.AddWithValue("@body", buf)
21        cmd.Parameters.AddWithValue("@size", FU.PostedFile.ContentLength.ToString)
22        cmd.Parameters.AddWithValue("@sdt", Now)
23 
24        Try
25            Dim rs As Integer
26            conn.Open()
27            rs = cmd.ExecuteNonQuery
28            Response.Write("新增成功")
29        Catch ex As Exception
30            Response.Write(ex)
31        Finally
32            cmd.Cancel()
33            cmd.Dispose()
34            conn.Close()
35        End Try
36    End Sub

DownFile事件:
01Protected Sub DownFile_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles DownFile.Click
02        ' 由輸入
03        Dim id As Integer
04 
05        If String.IsNullOrEmpty(Me.TextBox1.Text) Then
06            Response.Write("請輸入下載ID編號")
07            Response.End()
08        Else
09            id = Me.TextBox1.Text
10        End If
11 
12        Dim conn As New SqlConnection(WebConfigurationManager.ConnectionStrings("SCS").ConnectionString)
13        Dim cmd As New SqlCommand("Select * from Files Where uid = @id", conn)
14        cmd.Parameters.AddWithValue("@id", id)
15 
16        Dim dr As SqlDataReader
17 
18        Try
19            conn.Open()
20            dr = cmd.ExecuteReader
21            ' 將資料取出,輸出
22            If dr.Read Then
23                Response.ClearHeaders()
24                Response.Clear()
25                Response.Expires = 0
26                Response.Buffer = True
27 
28                ' Getxxx效能較佳
29                Dim Name As String = dr.GetString(1)
30                ' 進行AddHeader設定
31                Response.AddHeader("Accept-Language", "zh-tw")
32                ' 設定輸出檔案及類型
33                Response.AddHeader("content-disposition", "attachment; filename=" & Chr(34) & Name & Chr(34))
34                Response.ContentType = "Application/octet-stream"
35                ' 進行「二進位」輸出,使用BinaryWrite方法
36                Response.BinaryWrite(dr("Body"))
37                ' 將緩衝輸出
38                Response.Flush()
39                Response.Close()
40                ' 請參考討論,不要使用Response.End()
42                'Response.End()
43            End If
44            Response.End()
45        Catch ex As Exception
46            Response.Write(ex)
47        Finally
48            cmd.Cancel()
49            cmd.Dispose()
50            conn.Close()
51        End Try
52    End Sub

其實不難:
  1. 將檔案轉為Byte,然後存進資料庫的二進位欄位;
  2. 取出資料,進行二進位輸出。
不管是Excel、Word、MP3、Text、ZIP…反正只要是檔案都可以上傳及下載。以上還有很多可以改進的地方,例如統計的部份,或使用其他方式(Session…)來取得下載檔案。

參考:

11 則留言:

  1. 你好:我是你電子報的訂閱戶,我有VB程式的問題想問你可乎?
    1).Txt 檔如果要取得最後一筆資料的 Record number,除一筆一筆直出到最後外,有無更便捷的法方?
    2).陣列如果要傳值至副程式去運用,除了設定 Public 外有無方法?
    謝謝!!

    回覆刪除
  2. Dear Vincent

    我很樂意幫忙,盡我所能。
    1. 非文章主題,可以Mail到「kingkong點bruce(at)gmail點com」來討論。
    2. 如果方便,請將原始程式碼壓縮再mail給我。
    3. 你的問題二,我看不太懂?再說清楚一些。

    回覆刪除
  3. 請問一下
    程式第42行不需要Response.End(),可是為何第44行卻又執行一次呢?
    謝謝.

    回覆刪除
  4. 你好,請問這種上傳下載方式在Office2007相關檔案是否需做什麼調整呢?目前遇到下載excel2007檔案會出現下面訊息:
    Excel在“12.xlsx”中发现不可读取内容。是否恢复工作簿的内容?如果信任此工作簿的来源,请单击“是”。
    单击“是”后:Excel 已完成文件级验证和修复。此工作簿的某些部分可能已被修复或丢弃。
    (Word2007也是同樣錯誤訊息)
    謝謝

    回覆刪除
  5. 禎禎,我的建議是
    1. 資料庫部份,新增一個 MIME 欄位,然後在上傳檔案時,將檔案正確的MIME一併寫入。
    2. 在下載檔案時(DownFile事件 34行),指定正確的 MIME。

    其實,最好的辦法是「上傳前的檔案都要經過壓縮」,那一定沒問題。

    回覆刪除
  6. MIME 與 檔案的對應,你自己上網查一下「MIME」即有很多資料。

    回覆刪除
  7. 請問有C#的版本嗎??因為很需要~~希望前輩不吝指教!!感謝

    回覆刪除
  8. 請問有C#的版本嗎??因為很需要~~希望前輩不吝指教!!感謝

    回覆刪除
  9. http://www.developerfusion.com/tools/convert/csharp-to-vb/

    C#我幫不了你,你可以試著使用線上工具,看能不能動。

    回覆刪除
  10. 不好意思 想請問 我是將圖片存入資料庫
    資料庫是用sql
    我資料表欄位是用varbinary(MAX)

    我要怎麼讀取二進制的值與在前台顯示

    回覆刪除
  11. http://blog.kkbruce.net/p/mvc.html#mvc3upload

    我個人是不建議使用 Database 來儲存圖片。

    假設以下這種情況:
    你想想,如果你的table裡有 10000 筆人事資料好了,每筆有個大頭照,大頭照 5KB 就好,
    在使用類類似 Gridview 時,又沒事先處理好分頁,
    5 * 10000 = 50000 KB,光圖片資料就要傳多久?
    而且資料庫爆量成長也是一個問題。

    當然,每個系統都有自己的理由,
    http://msdn.microsoft.com/zh-tw/magazine/cc163933(en-us).aspx
    這裡有教,

    另外,我附上的 mvc 文章有些討論的 Link 最好也看一下。

    回覆刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。