共用方式為


新增記錄時包含檔案上傳選項 (VB)

演講者:Scott Mitchell

下載 PDF

本教學課程示範如何建立一個允許使用者輸入文字資料和上傳二進位檔案的 Web 介面。 為了說明可用於儲存二進位資料的選項,一個檔案將儲存在資料庫中,而另一個檔案將儲存在檔案系統中。

簡介

在前兩個教學課程中,我們探索了儲存與應用程式資料模型相關的二進位資料的技術,了解如何使用 FileUpload 控制項將檔案從用戶端傳送到 Web 伺服器,並了解如何在資料 Web 控制項中。 不過,我們還沒有討論如何將上傳的資料與資料模型關聯起來。

在本教學課程中,我們將建立一個網頁來新增類別。 除了用於類別名稱和描述的文字輸入框之外,此頁面還需要包含兩個 FileUpload 控制項,一個用於新類別的圖片,另一個用於手冊。 上傳的圖片將直接儲存在新記錄的 Picture 列中,而手冊將儲存在 ~/Brochures 資料夾中,並且檔案的路徑將儲存在新記錄的 BrochurePath 列中。

在建立這個新網頁之前,我們需要先更新架構。 CategoriesTableAdapter 的主要查詢不檢索 Picture 列。 因此,自動產生的 Insert 方法僅具有 CategoryNameDescriptionBrochurePath 欄位的輸入。 因此,我們需要在 TableAdapter 中建立一個額外的方法來提示輸入所有四個 Categories 欄位。 商務邏輯層中的 CategoriesBLL 類別也需要更新。

步驟 1:新增 InsertWithPicture 方法到 CategoriesTableAdapter

我們之前在「建立資料存取層」教學課程中建立 CategoriesTableAdapter 時,將其設定為根據主要查詢自動產生 INSERTUPDATEDELETE 陳述式。 此外,我們指示 TableAdapter 採用 DB Direct 方法,該方法建立了方法 InsertUpdateDelete。 這些方法執行自動產生的 INSERTUPDATEDELETE 陳述式,並因此接受基於主要查詢傳回的資料列的輸入參數。 在「上傳檔案」教學課程中,我們增強了 CategoriesTableAdapter 的主要查詢以使用 BrochurePath 列。

由於 CategoriesTableAdapter 的主要查詢不參考 Picture 列,因此我們既不能新增記錄,也不能使用 Picture 列的值更新現有記錄。 為了擷取此訊息,我們可以在 TableAdapter 中建立一個新方法,專門用於插入包含二進位資料的記錄,或者我們可以自訂自動產生的 INSERT 陳述式。 自訂自動產生的 INSERT 陳述式的問題在於,該精靈有可能覆寫我們的自訂內容。 例如,假設我們自訂了 INSERT 陳述式以加入使用 Picture 列。 這將更新 TableAdapter 的 Insert 方法,以包含類別圖片的二進位資料的附加輸入參數。 然後,我們可以在商務邏輯層中建立一個方法來使用此 DAL 方法,並透過表示層呼叫此 BLL 方法,一切都會順利進行。 也就是說,直到我們下次透過 TableAdapter 設定精靈來設定 TableAdapter。 一旦精靈完成,我們自訂的 INSERT 陳述式將會被覆寫,Insert 方法將恢復到其舊形式,並且我們的程式碼將不再編譯!

注意

當使用預存程序而不是即席 SQL 陳述式時,這種煩惱就不存在了。 未來的教學課程將探討在資料存取層中使用預存程序來取代即席 SQL 陳述式。

為了避免這種潛在的麻煩,我們不要自訂自動產生的 SQL 陳述式,而是為 TableAdapter 建立一個新方法。 該方法名為 InsertWithPicture,將接受 CategoryNameDescriptionBrochurePathPicture 列的值,並執行一條將所有四個值儲存在新記錄中的 INSERT 陳述式。

開啟類型化資料集,然後從設計工具中以滑鼠以滑鼠右鍵按一下 CategoriesTableAdapter 的標題,然後從操作功能表中選擇「新增查詢」。 這將啟動 TableAdapter 查詢設定精靈,該精靈首先詢問我們 TableAdapter 查詢應如何存取資料庫。 選擇使用 SQL 陳述式並按一下下一步。 下一步將提示輸入要產生的查詢類型。 由於我們正在建立查詢以向 Categories 表中新增記錄,因此選擇 INSERT 並按一下 Next。

選擇 INSERT 選項

圖 1:選擇 INSERT 選項 (點擊查看完整圖片)

我們現在需要指定 INSERT SQL 陳述式。 精靈會自動建議與 TableAdapter 的主要查詢相對應的 INSERT 陳述式。 在本例中,是插入 CategoryNameDescriptionBrochurePath 值的 INSERT 陳述式。 更新陳述式,以便將 Picture 列與 @Picture 參數一起包含在內,如下所示:

INSERT INTO [Categories] 
    ([CategoryName], [Description], [BrochurePath], [Picture]) 
VALUES 
    (@CategoryName, @Description, @BrochurePath, @Picture)

精靈的最後一個畫面要求我們命名新的 TableAdapter 方法。 輸入 InsertWithPicture 並點擊「完成」。

將新的 TableAdapter 方法命名為 InsertWithPicture

圖 2:將新的 TableAdapter 方法命名為 InsertWithPicture (點擊查看完整圖片)

步驟 2:更新商務邏輯層

由於表示層應該只與商務邏輯層互動,而不是繞過它直接進入資料存取層,因此我們需要建立一個 BLL 方法來呼叫我們剛剛建立的 DAL 方法 (InsertWithPicture)。 對於本教學課程,在名為 InsertWithPictureCategoriesBLL 類別中建立一個方法,該方法接受三個 StringByte 陣列作為輸入。 String 輸入參數為類別名稱、描述和手冊檔案路徑,而 Byte 陣列為類別圖片的二進位內容。 如下程式碼所示,此 BLL 方法會叫用對應的 DAL 方法:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Insert, False)> _
Public Sub InsertWithPicture(categoryName As String, description As String, _
    brochurePath As String, picture() As Byte)
    
    Adapter.InsertWithPicture(categoryName, description, brochurePath, picture)
End Sub

注意

確保在將 InsertWithPicture 方法新增至 BLL 之前已儲存類型化資料集。 由於 CategoriesTableAdapter 類別程式碼是根據類型化資料集自動產生的,因此如果您不先將變更儲存到類型化資料集,則 Adapter 屬性將不會知道 InsertWithPicture 方法。

步驟 3:列出現有類別及其二進位資料

在本教學課程中,我們將建立一個頁面,允許終端使用者為系統新增類別,並為新類別提供圖片和手冊。 在前面的教學課程中,我們使用 TemplateField 和 ImageField 的 GridView 來顯示每個類別的名稱、描述、圖片以及下載其手冊的連結。 讓我們在本教學課程中複製該功能,建立一個頁面,該頁面既列出所有現有類別,又允許建立新類別。

首先從 BinaryData 資料夾開啟 DisplayOrDownload.aspx 頁面。 前往「來源」檢視並複製 GridView 和 ObjectDataSource 的宣告式語法,將其貼上到 UploadInDetailsView.aspx 中的 <asp:Content> 元素內。 另外,別忘了將 GenerateBrochureLink 方法從 DisplayOrDownload.aspx 的程式碼後置類別複製到 UploadInDetailsView.aspx

將宣告語法從 DisplayOrDownload.aspx 複製並貼上到 UploadInDetailsView.aspx

圖 3:將宣告語法從 DisplayOrDownload.aspx 複製並貼上到 UploadInDetailsView.aspx (點擊查看完整圖片)

將宣告式語法和 GenerateBrochureLink 方法複製到 UploadInDetailsView.aspx 頁面後,透過瀏覽器查看頁面以確保所有內容都正確複製。 您應該會看到一個 GridView 列出了八個類別,其中包括下載手冊的連結以及類別的圖片。

現在您應該會看到每個類別及其二進位資料

圖 4:現在您應該會看到每個類別及其二進位資料 (點擊查看完整圖片)

步驟 4:設定 CategoriesDataSource 以支援插入

Categories GridView所使用的 CategoriesDataSource ObjectDataSource 目前不提供插入資料的功能。 為了支援透過此資料來源控制項插入,我們需要將其 Insert 方法對應到其基礎物件 CategoriesBLL 中的方法。 特別是,我們希望將其對應到我們在步驟 2 InsertWithPicture 中新增回來的 CategoriesBLL 方法。

首先點選 ObjectDataSource 智慧標籤中的「設定資料來源」連結。 第一個畫面顯示資料來源設定為使用的物件 CategoriesBLL。 保留此設定不變,然後按一下「下一步」前進到「定義資料方法」畫面。 移至 INSERT 標籤,並從下拉式清單中選擇 InsertWithPicture 方法。 按一下 [完成] 以完成程序。

設定 ObjectDataSource 以使用 InsertWithPicture 方法

圖 5:設定 ObjectDataSource 以使用 InsertWithPicture 方法 (點擊查看完整圖片)

注意

完成精靈後,Visual Studio 可能會詢問您是否要重新整理欄位和索引鍵,這將重新產生資料 Web 控制項欄位。 選擇「否」,因為選擇「是」將覆寫您所做的欄位自訂。

完成精靈後,ObjectDataSource 現在將包含其 InsertMethod 屬性以及四個類別列的 InsertParameters 值,如以下宣告式標記所示:

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture">
    <InsertParameters>
        <asp:Parameter Name="categoryName" Type="String" />
        <asp:Parameter Name="description" Type="String" />
        <asp:Parameter Name="brochurePath" Type="String" />
        <asp:Parameter Name="picture" Type="Object" />
    </InsertParameters>
</asp:ObjectDataSource>

步驟 5:建立插入介面

如同在「插入、更新和刪除資料概述」中一開始介紹的,DetailsView 控制項提供了一個內建插入介面,可在使用支援插入的資料來源控制項時使用該介面。 讓我們在該頁面的 GridView 上方新增一個 DetailsView 控制項,該控制項將永久呈現其插入介面,從而允許使用者快速新增新類別。 在 DetailsView 中新增類別後,其下方的 GridView 將自動重新整理並顯示新類別。

先將 DetailsView 從工具箱拖曳到 GridView 上方的設計工具上,將其 ID 屬性設為 NewCategory,並清除 HeightWidth 屬性值。 從 DetailsView s 智慧標籤中,將其繫結到現有 CategoriesDataSource,然後選取「啟用插入」核取方塊。

螢幕擷取畫面顯示開啟的 DetailsView,其中 CategoryID 屬性設定為 NewCategory,Height 和 Width 屬性值為空,並且「啟用插入」核取方塊處於選取狀態。

圖 6:將 DetailsView 繫結到 CategoriesDataSource,並啟用插入 (點擊查看完整圖片)

若要在其插入介面中永久呈現 DetailsView,請將其 DefaultMode 屬性設為 Insert

請注意,DetailsView 有 5 個 BoundField CategoryIDCategoryNameDescriptionNumberOfProductsBrochurePath,雖然 CategoryID BoundField 不會在插入介面中呈現,因為其 InsertVisible 屬性設定為 False。 這些 BoundFields 存在是因為它們是 GetCategories() 方法傳回的列,這是 ObjectDataSource 呼叫來檢索其資料的方法。 然而,對於插入,我們不想讓使用者指定 NumberOfProducts 的值。 此外,我們需要允許他們上傳新類別的圖片以及上傳手冊的 PDF。

從 DetailsView 中完全刪除 NumberOfProducts BoundField,然後將 CategoryNameBrochurePath BoundFields 的 HeaderText 屬性分別更新為 Category 和 Brochure。 接下來,將 BrochurePath BoundField 轉換為 TemplateField 並為圖片新增一個新的 TemplateField,並為這個新的 TemplateField 賦予 Picture 的 HeaderText 值。 移動 Picture TemplateField 使其位於 BrochurePath TemplateField 和 CommandField 之間。

顯示欄位視窗的螢幕擷取畫面,其中醒目顯示了 TemplateField、Picture 和 HeaderText。

圖 7:將 DetailsView 繫結到 CategoriesDataSource 並啟用插入

如果透過「編輯欄位」對話方塊將 BrochurePath BoundField 轉換為 TemplateField,則 TemplateField 會包含 ItemTemplateEditItemTemplateInsertItemTemplate。 然而,只需要 InsertItemTemplate,因此請隨意刪除其他兩個範本。 此時,您的 DetailsView 的宣告語法應如下所示:

<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False" 
    DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" 
    DefaultMode="Insert">
    <Fields>
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
            <InsertItemTemplate>
                <asp:TextBox ID="TextBox1" runat="server"
                    Text='<%# Bind("BrochurePath") %>'></asp:TextBox>
            </InsertItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Picture"></asp:TemplateField>
        <asp:CommandField ShowInsertButton="True" />
    </Fields>
</asp:DetailsView>

為手冊和圖片欄位新增 FileUpload 控制項

目前,BrochurePath TemplateField 的 InsertItemTemplate 包含一個 TextBox,而 Picture TemplateField 不包含任何範本。 我們需要更新這兩個 TemplateField 的 InsertItemTemplate 才能使用 FileUpload 控制項。

從 DetailsView s 智慧標籤中,選擇 Edit Templates 選項,然後從下拉式清單中選擇 BrochurePath TemplateField 的 InsertItemTemplate。 刪除 TextBox,然後將 FileUpload 控制項從工具箱拖曳到範本中。 將 FileUpload 控制項的 ID 設為 BrochureUpload。 同樣,將 FileUpload 控制項新增到 Picture TemplateField 的 InsertItemTemplate。 將此 FileUpload 控制項的 ID 設為 PictureUpload

將 FileUpload 控制項新增至 InsertItemTemplate

圖 8:將 FileUpload 控制項新增至 InsertItemTemplate (點擊查看完整圖片)

新增這些內容後,兩個 TemplateField 的宣告語法將是:

<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
    <InsertItemTemplate>
        <asp:FileUpload ID="BrochureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture">
    <InsertItemTemplate>
        <asp:FileUpload ID="PictureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>

當使用者新增新類別時,我們希望確保手冊和圖片的文件類型正確。 對於手冊,使用者必須提供 PDF。 對於圖片,我們需要使用者上傳影像文件,但是我們是否允許任何影像檔案或僅允許特定類型的影像檔案,例如 GIF 或 JPG? 為了允許不同的文件類型,我們需要擴展架 Categories 結構描述包含擷取文件類型的列,以便可以透過 DisplayCategoryPicture.aspx 中的 Response.ContentType 將該類型傳送到用戶端。 由於我們沒有這樣的列,因此謹慎的做法是限制使用者僅提供特定的影像檔案類型。 Categories 表的現有影像是點陣圖,但 JPG 是更適合透過 Web 提供影像的檔案格式。

如果使用者上傳了不正確的文件類型,我們需要取消插入並顯示一條指示問題的訊息。 在DetailsView 下新增Label Web 控制項。 將其 ID 屬性設為 UploadWarning,清除其 Text 屬性,將 CssClass 屬性設為「警告」,並將 VisibleEnableViewState 屬性設為 FalseWarning CSS 類別在 Styles.css 中定義,並以大號、紅色、斜體、粗體字體呈現文字。

注意

理想情況下,CategoryNameDescription BoundFields 將轉換為 TemplateFields,並自訂它們的插入介面。 例如,Description 的插入介面可能更適合使用多行文字輸入框。 由於 CategoryName 列不接受 NULL 值,因此應新增 RequiredFieldValidator 以確保使用者為新類別名稱提供值。 這些步驟留給讀者當作練習。 請參考「自訂資料修改介面」,以深入了解如何增強資料修改介面。

步驟 6:將上傳的手冊儲存到 Web 伺服器的檔案系統

當使用者輸入新類別的值並按一下「插入」按鈕時,將發生回傳並展開插入工作流程。 首先,將觸發 DetailsView 的 ItemInserting 事件。 接下來,呼叫 ObjectDataSource 的 Insert() 方法,這會導致將新記錄新增到 Categories 表中。 之後,將觸發 DetailsView 的 ItemInserted 事件

在叫用 ObjectDataSource 的 Insert() 方法之前,我們必須先確保使用者上傳了適當的文件類型,然後將手冊 PDF 儲存到 Web 伺服器的檔案系統中。 為 DetailsView 的 ItemInserting 事件建立一個事件處理常式,並新增以下程式碼:

' Reference the FileUpload controls
Dim BrochureUpload As FileUpload = _
    CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
If BrochureUpload.HasFile Then
    ' Make sure that a PDF has been uploaded
    If String.Compare(System.IO.Path.GetExtension _
        (BrochureUpload.FileName), ".pdf", True) <> 0 Then
        UploadWarning.Text = _
            "Only PDF documents may be used for a category's brochure."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
End If

事件處理常式會先參考 DetailsView 範本中的 BrochureUpload FileUpload 控制項。 然後,如果手冊已上傳,則檢查上傳檔案的副檔名。 如果副檔名不是 .PDF,則會顯示警告,取消插入,並且事件處理常式的執行結束。

注意

依賴上傳檔案的副檔名並不是確保上傳檔案是 PDF 文件的萬全之策。 使用者可以擁有副檔名為 .Brochure 的有效 PDF 文件,也可以使用非 PDF 文件並為其指定 .pdf 副檔名。 需要以程式設計方式檢查檔案的二進位內容,以便更確定地驗證檔案類型。 然而,這種徹底的方法往往是矯枉過正的。對於大多數情況,檢查擴展名就足夠了。

正如「上傳檔案」教學課程中所討論的,將文件儲存到文件系統時必須小心,以免一個使用者的上傳內容覆寫另一個使用者的上傳內容。 在本教學課程中,我們將嘗試使用與上傳檔案相同的名稱。 但是,如果 ~/Brochures 目錄中已存在具有相同文件名稱的文件,我們將在結尾附加一個數字,直到找到唯一的名稱。 例如,如果使用者上傳名為 Meats.pdf 的手冊文件,但 ~/Brochures 資料夾中已有名為 Meats.pdf 的文件,我們會將已儲存的檔案名稱變更為 Meats-1.pdf。 如果存在,我們將嘗試 Meats-2.pdf,依此類推,直到找到唯一的檔案名稱。

以下程式碼會使用 File.Exists(path) 方法來確定是否已存在具有指定檔案名稱的檔案。 如果是這樣,它將繼續嘗試手冊的新檔案名稱,直到沒有發現衝突。

Const BrochureDirectory As String = "~/Brochures/"
Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
Dim fileNameWithoutExtension As String = _
    System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
Dim iteration As Integer = 1
While System.IO.File.Exists(Server.MapPath(brochurePath))
    brochurePath = String.Concat(BrochureDirectory, _
        fileNameWithoutExtension, "-", iteration, ".pdf")
    iteration += 1
End While

找到有效的檔案名稱後,需要將該檔案儲存到檔案系統,並且需要更新 ObjectDataSource 的 brochurePath``InsertParameter 值,以便將該檔案名稱寫入資料庫。 正如我們在「上傳檔案」教學課程中所看到的,可以使用 FileUpload 控制項的 SaveAs(path) 方法來儲存檔案。 若要更新 ObjectDataSource 的 brochurePath 參數,請使用 e.Values 集合。

' Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath))
e.Values("brochurePath") = brochurePath

步驟 7:將上傳的圖片儲存到資料庫

為了將上傳的圖片儲存在新的 Categories 記錄中,我們需要將上傳的二進位內容指派給 DetailsView 的 ItemInserting 事件中 ObjectDataSource 的 picture 參數。 然而,在進行此任務之前,我們需要先確保上傳的圖片是 JPG,而不是其他影像類型。 與步驟 6 一樣,讓我們使用上傳的圖片的檔案副檔名來確定其類型。

雖然 Categories 表允許 Picture 列的 NULL 值,但所有類別目前都有圖片。 讓我們強制使用者在透過此頁面新增類別時提供圖片。 以下程式碼檢查以確保圖片已上傳並且具有適當的副檔名。

' Reference the FileUpload controls
Dim PictureUpload As FileUpload = _
    CType(NewCategory.FindControl("PictureUpload"), FileUpload)
If PictureUpload.HasFile Then
    ' Make sure that a JPG has been uploaded
    If  String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
            ".jpg", True) <> 0 AndAlso _
        String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
            ".jpeg", True) <> 0 Then
        
        UploadWarning.Text = _
            "Only JPG documents may be used for a category's picture."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
Else
    ' No picture uploaded!
    UploadWarning.Text = _
        "You must provide a picture for the new category."
    UploadWarning.Visible = True
    e.Cancel = True
    Exit Sub
End If

此程式碼應放置在步驟 6 中的程式碼之前,以便如果圖片上傳出現問題,事件處理常式將在手冊檔案儲存到檔案系統之前終止。

假設已經上傳了合適的文件,則使用以下程式碼行將上傳的二進位內容指派給圖片參數的值:

' Set the value of the picture parameter
e.Values("picture") = PictureUpload.FileBytes

完成 ItemInserting 事件處理常式

為了完整起見,以下是 ItemInserting 事件處理常式的完整內容:

Protected Sub NewCategory_ItemInserting _
    (sender As Object, e As DetailsViewInsertEventArgs) _
    Handles NewCategory.ItemInserting
    
    ' Reference the FileUpload controls
    Dim PictureUpload As FileUpload = _
        CType(NewCategory.FindControl("PictureUpload"), FileUpload)
    If PictureUpload.HasFile Then
        ' Make sure that a JPG has been uploaded
        If  String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
                ".jpg", True) <> 0 AndAlso _
            String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
                ".jpeg", True) <> 0 Then
            
            UploadWarning.Text = _
                "Only JPG documents may be used for a category's picture."
            UploadWarning.Visible = True
            e.Cancel = True
            Exit Sub
        End If
    Else
        ' No picture uploaded!
        UploadWarning.Text = _
            "You must provide a picture for the new category."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
    ' Set the value of the picture parameter
    e.Values("picture") = PictureUpload.FileBytes
    ' Reference the FileUpload controls
    Dim BrochureUpload As FileUpload = _
        CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
    If BrochureUpload.HasFile Then
        ' Make sure that a PDF has been uploaded
        If String.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), _
            ".pdf", True) <> 0 Then
            
            UploadWarning.Text = _
                "Only PDF documents may be used for a category's brochure."
            UploadWarning.Visible = True
            e.Cancel = True
            Exit Sub
        End If
        Const BrochureDirectory As String = "~/Brochures/"
        Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
        Dim fileNameWithoutExtension As String = _
            System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
        Dim iteration As Integer = 1
        While System.IO.File.Exists(Server.MapPath(brochurePath))
            brochurePath = String.Concat(BrochureDirectory, _
                fileNameWithoutExtension, "-", iteration, ".pdf")
            iteration += 1
        End While
        ' Save the file to disk and set the value of the brochurePath parameter
        BrochureUpload.SaveAs(Server.MapPath(brochurePath))
        e.Values("brochurePath") = brochurePath
    End If
End Sub

步驟 8:修正 DisplayCategoryPicture.aspx 頁面

讓我們花點時間測試一下在最後幾個步驟中建立的插入介面和 ItemInserting 事件處理常式。 透過瀏覽器存取 UploadInDetailsView.aspx 頁面並嘗試新增類別,但省略圖片,或指定非 JPG 圖片或非 PDF 手冊。 在任何一種情況下,都會顯示錯誤訊息並取消插入工作流程。

如果上傳無效文件類型,則會顯示警告訊息

圖 9:如果上傳無效文件類型,則會顯示警告訊息 (點擊查看完整圖片)

一旦您確認頁面需要上傳圖片並且不接受非 PDF 或非 JPG 文件,請新增一個包含有效 JPG 圖片的新類別,並將「手冊」欄位留空。 點擊插入按鈕後,頁面將回傳,一筆新記錄將新增到 Categories 表中,上傳影像的二進位內容直接儲存在資料庫中。 GridView 已更新並顯示新新增的類別的行,但是,如圖 10 所示,新類別的圖片未正確呈現。

新類別圖片未顯示

圖10:新類別圖片未顯示 (點擊查看完整圖片)

未顯示新圖片的原因是因為傳回指定類別圖片的 DisplayCategoryPicture.aspx 頁面被設定為處理具有 OLE 標頭的點陣圖。 在將 Picture 列的二進位內容傳送回用戶端之前,會從該列的二進位內容中刪除此 78 位元組標頭。 但是我們剛剛為新類別上傳的 JPG 檔案沒有這個 OLE 標頭;因此,有效的、必要的位元組將從影像的二進位資料中刪除。

由於 Categories 表中現在既有帶有 OLE 標頭的位圖,也有 JPG,因此我們需要更新 DisplayCategoryPicture.aspx,以便它對原始八個類別執行 OLE 標頭剝離,並繞過對新類別記錄的剝離。 在下一個教學課程中,我們將研究如何更新現有記錄的影像,並且我們將更新所有舊的類別圖片,使它們成為 JPG。 不過,目前,請在 DisplayCategoryPicture.aspx 中使用以下程式碼,以只刪除原始八個類別的 OLE 標頭:

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
    Dim categoryID As Integer = Convert.ToInt32(Request.QueryString("CategoryID"))
    ' Get information about the specified category
    Dim categoryAPI As New CategoriesBLL()
    Dim categories As Northwind.CategoriesDataTable = _
        categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID)
    Dim category As Northwind.CategoriesRow = categories(0)
    If categoryID <= 8 Then
        ' Output HTTP headers providing information about the binary data
        Response.ContentType = "image/bmp"
        ' Output the binary data
        ' But first we need to strip out the OLE header
        Const OleHeaderLength As Integer = 78
        Dim strippedImageLength As Integer = _
            category.Picture.Length - OleHeaderLength
        Dim strippedImageData(strippedImageLength) As Byte
        Array.Copy(category.Picture, OleHeaderLength, _
            strippedImageData, 0, strippedImageLength)
        Response.BinaryWrite(strippedImageData)
    Else
        ' For new categories, images are JPGs...
        ' Output HTTP headers providing information about the binary data
        Response.ContentType = "image/jpeg"
        ' Output the binary data
        Response.BinaryWrite(category.Picture)
    End If
End Sub

透過此更改,JPG 影像現在可以在 GridView 中正確呈現。

新類別的 JPG 影像已正確轉譯

圖 11:新類別的 JPG 影像已正確轉譯 (點擊查看完整圖片)

步驟 9:在出現例外狀況時刪除手冊

在網頁伺服器的檔案系統上儲存二進位資料的一個挑戰是,它會導致資料模型與其二進位資料之間出現脫節。 因此,每當刪除一筆記錄時,檔案系統上對應的二進位資料也必須刪除。 這在插入時也可以發揮作用。 考慮以下情境:使用者新增一個新類別,指定有效的圖片和手冊。 點擊「插入」按鈕後,將發生回傳並觸發 DetailsView 的 ItemInserting 事件,將手冊儲存到 Web 伺服器的檔案系統中。 接著會叫用 ObjectDataSource 的 Insert() 方法,該方法會呼叫 CategoriesBLL 類別的 InsertWithPicture 方法,其會呼叫 CategoriesTableAdapterInsertWithPicture 方法。

現在,如果資料庫離線,或者 INSERT SQL 陳述式有錯誤,會發生什麼情況? 顯然,INSERT 將會失敗,因此不會將新的類別行新增到資料庫中。 但我們仍然在網頁伺服器的檔案系統上儲存了上傳的手冊檔案! 在插入工作流程中遇到例外狀況時需要刪除該檔案。

如同先前在「處理 ASP.NET 頁面中的 BLL 和 DAL 級例外狀況」教學課程中所討論的,當架構深處擲回例外狀況時,這些例外狀況會透過各層向上傳遞。 在表示層,我們可以從 DetailsView 的 ItemInserted 事件中確定是否發生了例外狀況。 此事件處理常式也提供 ObjectDataSource 的 InsertParameters 值。 因此,我們可以為 ItemInserted 事件建立事件處理常式,檢查是否有例外狀況,如果存在,則刪除由 ObjectDataSource 的 brochurePath 參數指定的檔案:

Protected Sub NewCategory_ItemInserted _
    (sender As Object, e As DetailsViewInsertedEventArgs) _
    Handles NewCategory.ItemInserted
    
    If e.Exception IsNot Nothing Then
        ' Need to delete brochure file, if it exists
        If e.Values("brochurePath") IsNot Nothing Then
            System.IO.File.Delete(Server.MapPath _
                (e.Values("brochurePath").ToString()))
        End If
    End If
End Sub

摘要

為了提供 Web 式介面來新增包含二進位資料的記錄,必須執行許多步驟。 如果二進位資料直接儲存到資料庫中,您很可能需要更新架構,新增特定方法來處理插入二進位資料的情況。 更新架構後,下一步就是建立插入介面,這可以使用已自訂為包含每個二進位資料欄位的 FileUpload 控制項的 DetailsView 來完成。 然後,上傳的資料可以儲存到 Web 伺服器的檔案系統,或指派給 DetailsView 的 ItemInserting 事件處理常式中的資料來源參數。

將二進位資料儲存到檔案系統比直接將資料儲存到資料庫需要更多的規劃。 必須選擇一種命名方案,以避免一個使用者的上傳覆寫另一個使用者的上傳。 此外,如果資料庫插入失敗,則必須採取額外的步驟來刪除上傳的檔案。

我們現在能夠透過手冊和圖片為系統新增新類別,但我們還沒有研究如何更新現有類別的二進位資料,或如何正確移除已刪除類別的二進位資料。 我們將在下一個教學課程中探討這兩個主題。

祝您程式設計愉快!

關於作者

Scott Mitchell,七本 ASP/ASP.NET 書籍的作者和 4GuysFromRolla.com 創始人,自 1998 年以來便開始使用 Microsoft Web 技術。 Scott 是獨立顧問、講師和作家。 他的新書是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 您可以透過 mitchell@4GuysFromRolla.com 或他的部落格 (可以在 http://ScottOnWriting.NET 找到) 與他聯繫。

特別感謝

本教學課程系列已經過許多熱心的檢閱者檢閱。 本教學課程的主要審閱者是 Dave Gardner、Teresa Murphy 和 Bernadette Leigh。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果有,請寄信到 mitchell@4GuysFromRolla.com。