透過資料庫上傳下載檔案

在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裡,需要時再取出來使用,以下我們就來實作上傳檔案至資料庫,由資料庫取出檔案下載兩部份。

****** Object:  Table [dbo].[Files]    Script Date: 08/26/2010 17:12:17 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[Files](
 [uid] [int] IDENTITY(1,1) NOT NULL,
 [Name] [nvarchar](50) NOT NULL,
 [Body] [varbinary](max) NOT NULL,
 [Size] [decimal](18, 0) NOT NULL,
 [SaveDateTime] [datetime] NOT NULL,
 [LastUseDateTime] [datetime] NULL
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

我們先建一個資料表,裡面最重要的是「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部份:

<div>
        <asp:FileUpload ID="FU" runat="server" />
     
        <asp:Button ID="SaveFile" runat="server" Text="上傳" />
        <br />
        <br />
        編號:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
 <asp:Button ID="DownFile" runat="server" Text="下載" />
    </div>

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

SaveFile事件:
Protected Sub SaveFile_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SaveFile.Click
        ' 先確認有選擇檔案
        If Not FU.HasFile Then
            Response.Write("沒有上傳的檔案!")
            Response.End()
        End If

        ' 檔案存進資料庫都是「二進位」,所以使用Byte
        ' buf陣列依檔案大小來建立
        ' 陣列由0開始,所以減1
        Dim buf(Me.FU.PostedFile.ContentLength - 1) As Byte
        Me.FU.PostedFile.InputStream.Read(buf, 0, Me.FU.PostedFile.ContentLength)

        Dim conn As New SqlConnection(WebConfigurationManager.ConnectionStrings("SCS").ConnectionString)

        ' 將參考及欄位對應起來
        Dim cmd As New SqlCommand("INSERT INTO Files(Name,Body,Size,SaveDateTime) VALUES (@filename,@body,@size, @sdt);", conn)
        cmd.Parameters.AddWithValue("@filename", FU.PostedFile.FileName)
        ' 將二進位檔放入資料庫中
        cmd.Parameters.AddWithValue("@body", buf)
        cmd.Parameters.AddWithValue("@size", FU.PostedFile.ContentLength.ToString)
        cmd.Parameters.AddWithValue("@sdt", Now)

        Try
            Dim rs As Integer
            conn.Open()
            rs = cmd.ExecuteNonQuery
            Response.Write("新增成功")
        Catch ex As Exception
            Response.Write(ex)
        Finally
            cmd.Cancel()
            cmd.Dispose()
            conn.Close()
        End Try
    End Sub

DownFile事件:
Protected Sub DownFile_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles DownFile.Click
        ' 由輸入
        Dim id As Integer

        If String.IsNullOrEmpty(Me.TextBox1.Text) Then
            Response.Write("請輸入下載ID編號")
            Response.End()
        Else
            id = Me.TextBox1.Text
        End If

        Dim conn As New SqlConnection(WebConfigurationManager.ConnectionStrings("SCS").ConnectionString)
        Dim cmd As New SqlCommand("Select * from Files Where uid = @id", conn)
        cmd.Parameters.AddWithValue("@id", id)

        Dim dr As SqlDataReader

        Try
            conn.Open()
            dr = cmd.ExecuteReader
            ' 將資料取出,輸出
            If dr.Read Then
                Response.ClearHeaders()
                Response.Clear()
                Response.Expires = 0
                Response.Buffer = True

                ' Getxxx效能較佳
                Dim Name As String = dr.GetString(1)
                ' 進行AddHeader設定
                Response.AddHeader("Accept-Language", "zh-tw")
                ' 設定輸出檔案及類型
                Response.AddHeader("content-disposition", "attachment; filename=" & Chr(34) & Name & Chr(34))
                Response.ContentType = "Application/octet-stream"
                ' 進行「二進位」輸出,使用BinaryWrite方法
                Response.BinaryWrite(dr("Body"))
                ' 將緩衝輸出
                Response.Flush()
                Response.Close()
                ' 請參考討論,不要使用Response.End()
                ' http://www.blueshop.com.tw/board/FUM20041006161839LRJ/BRD20100824142739D3F.html
                'Response.End()
            End If
            Response.End()
        Catch ex As Exception
            Response.Write(ex)
        Finally
            cmd.Cancel()
            cmd.Dispose()
            conn.Close()
        End Try
    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 最好也看一下。

    回覆刪除

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