top
Loading...
用ASP.NET2.0在數據庫中存儲二進制文件
一、 引言

在構建數據驅動的應用程序時,經常需要捕獲文本和二進制數據。這樣的程序可能需要存儲圖像,PDF,Word文件或其它二進制數據。能夠使用兩種方式來存儲這些二進制數據:存儲在web服務器的文件系統上并添加一個對數據庫中相應文件的引用;或直接存儲在數據庫本身。

文本數據,例如字符串,數字,日期,GUID,貨幣值,等等-在數據庫系統中都有適當的和相應的數據類型定義。例如,在Microsoft SQL Server中,你可以使用int數據類型來存儲一個整數值;而為了存儲一個字符串值,你可以使用一個varchar或nvarchar類型。另外,數據庫還提供了用于存儲二進制數據的類型定義。在Microsoft SQL SERVER 2000及早期版本中,使用image數據類型來存儲二進制數據;而在SQL SERVER 2005中,使用varbinary(MAX)數據類型。使用上面兩種方式中的任何一種,這些數據類型都能夠存儲可達2GB大小的二進制數據。

不過,當直接把二進制數據存儲在數據庫時,需要增加一些額外工作來實現插入、更新和檢索二進制數據。幸好,我們可以通過更高級的數據存取庫-例如ADO.NET-對這種復雜的低級T-SQL操作加以抽象,從而使問題變得相當簡單。然而,通過ADO.NET方式使用二進制數據與使用文本數據的確有點不同。在本文中,我們將分析如何使用ADO.NET和ASP.NET 2.0 SqlDataSource控件直接通過一個數據庫來存儲和檢索圖像文件。請接著往下閱讀!

二、 把數據存儲在數據庫中與存儲在文件系統中的比較

正如剛才介紹的,當捕獲一個應用程序中的二進制數據時,該二進制數據既可以直接存儲在數據庫中也可以作為一個文件保存在web服務器的文件系統中-僅保持一個對數據庫中文件的引用。根據我的體驗,我發現大多數開發者更喜歡在文件系統中存儲二進制數據,這主要基于下列原因:

· 需要較少的工作-存儲和檢索存儲在數據庫中的二進制需要更多的編碼工作。而且,更新這些二進制數據也會更為容易-不需要與數據庫通訊,只須直接修改文件即可!

· 指向文件的URL更為直接-正如我們將在本文中所看到的,為了提供存取存儲在一個數據庫中的二進制數據,我們需要創建另一個能夠返回該數據的ASP.NET頁面。典型地,會把相應于數據庫中對應記錄(返回它的二進制數據)的一個唯一的標識符傳遞給這個頁面。結果是,為了存取該二進制數據-比方說一個上傳的圖像-該URL看上去如http://www.yourserver.com/ShowImage.aspx?ID=4352的形式,而如果該圖像直接存儲在文件系統中,URL將更為直接些-例如http://www.yourserver.com/UploadedImages/Sam.jpg。

· 為顯示圖像提供更好的工具支持-如果你在使用ASP.NET 2.0,那么,你可以在GridView或DetailsView控件中使用ImageField控件來顯示一個圖像(它的圖像路徑存儲在數據庫中)。然而,遺憾的是,這個ImageField卻無法直接顯示數據庫中的圖像數據(既然它要求查詢一個外部頁面并且返回相應的數據)。

· 性能-既然二進制文件存儲在web服務器的文件系統而不是存儲在數據庫中,那么,應用程序可以訪問數據庫中較少的數據,從而減少了對數據庫的要求,也相應地減少了存在于web和數據庫服務器之間的網絡擁擠。

把數據直接存儲在數據庫的主要優點在于,它能夠使數據成為"自包含的"。既然所有的數據都包含在數據庫中,那么,數據支持、數據在數據庫服務器間的移動以及數據庫復制等等就容易得多了,因為不存在擔心復制或備份存儲在文件系統中的二進制內容這樣的問題。

如往常一樣,至于選擇哪種存儲方案要具體依賴于實際的使用場所和業務需要。例如,我開發過一個客戶端,其中的二進制數據必須存儲在數據庫中,因為它們使用的報告軟件僅能夠在報告中包括二進制數據-如果它來自于數據庫的話。在另一種情況下,我的一個同事在開發一個工程,其中的二進制文件需要為web應用程序使用并且可經由FTP使用,這種情況很有必要把二進制數據存儲在文件系統中。

三、 創建一個存儲二進制數據的數據庫表格

本文中的其它部分將分析一個簡單的ASP.NET2.0圖像畫廊應用程序,我使用微軟SQL Server 2005 Express Edition編寫的,用于展示本文所闡述的直接從一個數據庫中存儲和檢索二進制數據的相關概念。

這個圖像畫廊應用程序的數據模型包括一個表格-Pictures,其中的每一個記錄對應畫廊中的一幅圖片。這個Pictures表格的MIMEType域中存儲了上載圖像(對于JPG文件是image/jpeg,對于GIF文件是image/gif,等等)的MIME類型;這里的MIME類型向瀏覽器指定如何生成該二進制數據。其中的ImageData列則存儲了該圖片實際的二進制內容。

四、 上傳一個圖像并使用ADO.NET代碼存儲二進制數據

這個圖像畫廊允許訪問者上傳圖片文件(GIF,JPG和PNG格式)到這個應用程序中。一旦上傳,一個新的記錄將被添加到Pictures表格并且該圖像文件的內容即被存儲在新的記錄的ImageData列內。為了實現在ASP.NET 2.0中把文件從web瀏覽器端上傳到web服務器,本示例中使用了FileUpload控件。FileUpload控件的使用方法是很簡單的事情-只需要把它從工具欄拖動到你的頁面上即可。最終,這個FileUpload控件將在用戶的瀏覽器端生成為標準的文件上傳形式-一個"Browse"按鈕(當點擊它時)允許用戶從他們的硬盤中選擇一個文件上傳到web服務器。

例如,為了創建一個接口以實現添加一個新的圖像,我使用一個TextBox控件來捕獲圖片的標題,還有一個FileUpload控件用于允許用戶指定要上傳的圖像:

<b>Title:</b>
<asp:TextBox ID="PictureTitle" runat="server" />
<br />
<b>Picture:</b>
<asp:FileUpload ID="UploadedFile" runat="server" />
<br />
<asp:LinkButton ID="btnInsert" runat="server" Text="Insert" />
<asp:LinkButton ID="btnCancel" runat="server" Text="Cancel" />

上面的代碼創建一個頁面,用戶能夠從他們的硬盤上指定一個要上傳到web服務器的文件。



一旦用戶選擇了一個文件并且寄送了這個表單(例如通過點擊"Insert"按鈕),那么,指定文件的二進制內容即被寄送到web服務器。從服務器端代碼中,這種二進制數據通過FileUpload控件的PostedFile.InputStream屬性成為可用,這正如下面的標記和代碼所展示的:

Protected Sub btnInsert_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnInsert.Click
'確保一個文件被成功上傳
If UploadedFile.PostedFile Is Nothing OrElse String.IsNullOrEmpty(UploadedFile.PostedFile.FileName) OrElse UploadedFile.PostedFile.InputStream Is Nothing Then
... 顯示錯誤信息...
Exit Sub
End If
'確保我們在操作一個JPG或者GIF文件
Dim extension As String = Path.GetExtension(UploadedFile.PostedFile.FileName).ToLower()
Dim MIMEType As String = Nothing
Select Case extension
Case ".gif"
MIMEType = "image/gif"
Case ".jpg", ".jpeg", ".jpe"
MIMEType = "image/jpeg"
Case ".png"
MIMEType = "image/png"
Case Else
'無效的文件類型上傳
... 顯示錯誤信息...
Exit Sub
End Select

'與數據庫連接并且把一條新記錄插入到Products表格中
Using myConnection As New SqlConnection(ConfigurationManager.ConnectionStrings("ImageGalleryConnectionString").ConnectionString)

Const SQL As String = "INSERT INTO [Pictures] ([Title], [MIMEType], [ImageData]) VALUES (@Title, @MIMEType, @ImageData)"
Dim myCommand As New SqlCommand(SQL, myConnection)
myCommand.Parameters.AddWithValue("@Title", PictureTitle.Text.Trim())
myCommand.Parameters.AddWithValue("@MIMEType", MIMEType)

'把FileUpload控件的InputStream加載到字節數組中
Dim imageBytes(UploadedFile.PostedFile.InputStream.Length) As Byte
UploadedFile.PostedFile.InputStream.Read(imageBytes, 0, imageBytes.Length)
myCommand.Parameters.AddWithValue("@ImageData", imageBytes)
myConnection.Open()
myCommand.ExecuteNonQuery()
myConnection.Close()
End Using
End Sub

在此,這個事件處理器首先確保已經上傳一個文件。然后,它根據被上傳的文件的擴展名來決定MIME類型。

上面最值得注意的是那些設置@ImageData參數的代碼部分。首先,創建一個名為imageBytes的字節數組并且使其長度為被上傳的文件相應的InputStream。然后,從InputStream中使用Read方法把二進制內容填入這個字節數組。注意,正是這個字節數組被指定為@ImageData的值。

五、上傳圖像并使用ASP.NET 2.0數據源控件代碼存儲二進制數據

盡管ADO.NET方法工作在一個ASP.NET 2.0應用程序環境下,但是,你還能夠使用ASP.NET 2.0的數據源控件來把二進制數據存儲到一個數據庫,這不要求你編寫ADO.NET代碼。在這個演示程序中所使用的SqlDataSource控件包含了一個InsertCommand,以及相應于Title,MIMEType和ImageData值的參數:

<asp:SqlDataSource ID="UploadPictureDataSource" runat="server"
ConnectionString="..."
InsertCommand="INSERT INTO [Pictures] ([Title], [MIMEType], [ImageData]) VALUES (@Title, @MIMEType, @ImageData)">

<InsertParameters>
<asp:Parameter Name="Title" Type="String" />
<asp:Parameter Name="MIMEType" Type="String" />
<asp:Parameter Name="ImageData" />
</InsertParameters>
</asp:SqlDataSource>

注意,在此,ImageData參數并沒有指定一個類型。如果你試圖使用GUI向導來構建SqlDataSource的語法,那么,它將可能給它指定Type="Object",然而,這個Type="Object"將會產生一個sql_variant類型的參數。然而,該sql_variants類型不能用來存儲圖像或varbinary(MAX)數據類型,因為該sql_variant的內在數據大小不能超過8,000個字節。(如果你試圖使用Type="Object",然后試圖保存超過8,000字節大小的二進制數據,那么,系統將拋出一個異常并顯示消息"Parameter '@ImageData' exceeds the size limit for the sql_variant datatype";如果你試圖添加不到8,000字節大小的二進制數據,那么,該異常將顯示消息"Implicit conversion from data type sql_variant to varbinary(max) is not allowed")。

另外,DetailsView控件包含了兩個TemplateField。其中,一個TemplateField中使用一個TextBox控件來顯示標題欄;另一個使用一個FileUpload控件來表示ImageData欄。最終結果是得到一個看上去類似于在"上傳圖像并使用ADO.NET代碼存儲二進制數據"一節中的用戶接口。當點擊DetailsView的"Insert"按鈕時,它的Inserting事件激發,這時二進制數據必須從FileUpload控件中獲取,讀到一個字節數組中,并且賦值給適當的參數:

Protected Sub UploadPictureUI_ItemInserting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DetailsViewInsertEventArgs) Handles UploadPictureUI.ItemInserting
'引用FileUpload控件
Dim UploadedFile As FileUpload = CType(UploadPictureUI.FindControl("UploadedFile"), FileUpload)

'確保已經成功上傳一個文件
If UploadedFile.PostedFile Is Nothing OrElse String.IsNullOrEmpty(UploadedFile.PostedFile.FileName) OrElse UploadedFile.PostedFile.InputStream Is Nothing Then
...顯示錯誤信息...
e.Cancel = True
Exit Sub
End If

'確保我們在處理一個JPG或GIF文件
Dim extension As String = Path.GetExtension(UploadedFile.PostedFile.FileName).ToLower()
Dim MIMEType As String = Nothing
Select Case extension
Case ".gif"
MIMEType = "image/gif"
Case ".jpg", ".jpeg", ".jpe"
MIMEType = "image/jpeg"
Case ".png"
MIMEType = "image/png"
Case Else
'無效文件類型上載
... 顯示錯誤信息...
e.Cancel = True
Exit Sub
End Select

"指定MIMEType和ImageData參數的值
e.Values("MIMEType") = MIMEType
'把FileUpload的InputStream加載進字節數組中
Dim imageBytes(UploadedFile.PostedFile.InputStream.Length) As Byte
UploadedFile.PostedFile.InputStream.Read(imageBytes, 0, imageBytes.Length)
e.Values("ImageData") = imageBytes
End Sub

就象前面的"Insert"按鈕的Click事件處理器一樣,該DetailsView的Inserting事件處理器也執行相同的邏輯-只有一些小小的語法差別。首先,既然FileUpload控件位于一個模板內,所以,必須使用FindControl("controlID")方法以編程方式來引用它。一旦對它進行了引用,即對之進行相同的檢查以確保一個文件被成功上傳,并且允許相應的擴展名。對于DetailsView的Inserting事件處理器存在一個微小的區別是,如果出現了錯誤,那么,我們需要通知該DetailsView停止相應的插入工作-這是通過把e.Cancel屬性設置為True實現的。

檢查完之后,MIMEType和ImageData參數將被使用e.Values("parameterName")=value語法進行賦值。就象在前面的ADO.NET示例中一樣,首先把該二進制數據讀取到一個字節數組中,然后把該字節數組賦值給該參數。

六、 顯示二進制內容

無論你使用什么技術把數據存儲在數據庫中,為了檢索并顯示二進制數據,我們需要創建一個新的ASP.NET頁面。這個名字為ShowPicture.aspx的頁面,將通過querystring把一個PictureID傳遞給它,并且從指定的產品的ImageData域中返回該二進制數據。一旦完成,通過訪問/ShowPicture.aspx?PictureID=picutreID地址即可看到一個特定的圖片。因此,為了把一個圖像顯示在一個web頁面上,我們可以使用一個圖像控件并把它的ImageUrl屬性設置成適當的URL。

注意,這個ShowPicture.aspx在其.aspx頁面中并沒有包括任何HTML標記。在code-behind類的Page_Load事件處理器中,將使用ADO.NET代碼從數據庫中檢索指定的Pictures行的MIMEType和ImageData。然后,該頁面的ContentType被設置為MIMEType域的值,并且使用Response.BinaryWrite(ImageData)輸出該二進制數據:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim PictureID As Integer = Convert.ToInt32(Request.QueryString("PictureID"))

'與數據庫連接并且返回指定的圖片的圖像內容和MIME類型
Using myConnection As New SqlConnection(ConfigurationManager.ConnectionStrings("ImageGalleryConnectionString").ConnectionString)

Const SQL As String = "SELECT [MIMEType], [ImageData] FROM [Pictures] WHERE [PictureID] = @PictureID"
Dim myCommand As New SqlCommand(SQL, myConnection)
myCommand.Parameters.AddWithValue("@PictureID", PictureID)

myConnection.Open()
Dim myReader As SqlDataReader = myCommand.ExecuteReader

If myReader.Read Then
Response.ContentType = myReader("MIMEType").ToString()
Response.BinaryWrite(myReader("ImageData"))
End If

myReader.Close()
myConnection.Close()
End Using
End Sub

當ShowPicture.aspx頁面完成后,你就可以通過直接訪問URL或通過一個圖像web控件(或經由靜態<img src="ShowPicture.aspx?ProductID=productID" .http://www.zhujiangroad.com/>標記)來觀看圖像。下面的第一幅屏幕快照展示了當直接通過ShowPicture.aspx觀察時的一個圖像;第二個屏幕快照展示了該圖像畫廊的Default.aspx頁面-程序中在一個FormView控件內使用了一個Image Web控件,從而允許用戶按頁面瀏覽畫廊中的圖像。



七、 結論

當構建數據驅動的應用程序(其中必須捕獲二進制數據)時,開發者必須決定是把二進制數據存儲在文件系統中或是直接存儲在數據庫內。兩種方案都存在各自的優點與缺點-正如在本文中所討論的。如果你選擇把二進制數據保存在數據庫中,那么,你需要多付出一些努力來實現插入、更新和檢索數據。在本文中,我們通過使用ADO.NET編碼和ASP.NET 2.0 SqlDataSource控件分析了如何直接把圖像文件上傳到一個數據庫。

作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗