更新及刪除現有的二進位資料 (C#)

作者:Scott Mitchell

下載 PDF

在先前的教學課程中,我們瞭解 GridView 控件如何讓您輕鬆地編輯和刪除文字數據。 在本教學課程中,我們將瞭解如何讓 GridView 控件能夠編輯和刪除二進位數據,不論該二進位數據是儲存在資料庫中還是儲存在文件系統中。

簡介

在過去三個教學課程中,我們已新增相當多的功能來處理二進位數據。 我們一開始會將數據 BrochurePath 行新增至數據表, Categories 並據以更新架構。 我們也新增了數據存取層和商業規則層方法,以使用 [類別] 數據表現有的 Picture 數據行,其中保存圖像檔的二進位內容。 我們已建置網頁,以在 GridView 中呈現折頁簿的二進位數據,其中包含元素中顯示的 <img> 類別圖片,並已新增 DetailsView,讓使用者新增類別並上傳其折頁簿和圖片數據。

所有仍要實作的都是編輯和刪除現有類別的能力,我們將在本教學課程中使用 GridView 的內建編輯和刪除功能來完成。 編輯類別時,用戶可以選擇性地上傳新圖片,或讓類別繼續使用現有的圖片。 針對摺頁冊,他們可以選擇使用現有的摺頁冊、上傳新的摺頁冊,或指出該類別不再有與其相關聯的摺頁冊。 讓我們開始吧!

步驟 1:更新數據存取層

DAL 具有自動產生的、 和方法,但這些方法是根據不包含數據Picture行的主要查詢來產生CategoriesTableAdapterDeleteUpdateInsert 因此, InsertUpdate 方法不包含參數來指定類別圖片的二進位數據。 如同我們在上一個 教學課程中所做的一樣,我們需要建立新的 TableAdapter 方法,以在指定二進位數據時更新 Categories 數據表。

開啟 [具類型的數據集],然後從 Designer,以滑鼠右鍵按兩下 CategoriesTableAdapter s 標頭,然後從操作功能表中選擇 [新增查詢] 以啟動 [TableAdapter 查詢組態精靈]。 此精靈一開始會詢問 TableAdapter 查詢應該如何存取資料庫。 選擇 [使用 SQL 語句],然後按 [下一步]。 下一個步驟會提示產生查詢類型。 因為我們要建立查詢以將新記錄新增至 Categories 數據表,請選擇 [更新],然後按 [下一步]。

選取UPDATE選項

圖 1:選取 [更新選項] (按下即可檢視完整大小的映像)

我們現在需要指定 UPDATE SQL 語句。 精靈會自動建議 UPDATE 對應至 TableAdapter 主要查詢的語句, (更新 CategoryNameDescriptionBrochurePath 值) 的語句。 變更 語句, Picture 讓數據行與 @Picture 參數一起包含,如下所示:

UPDATE [Categories] SET 
    [CategoryName] = @CategoryName, 
    [Description] = @Description, 
    [BrochurePath] = @BrochurePath ,
    [Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))

精靈的最後一個畫面會要求我們命名新的 TableAdapter 方法。 輸入 UpdateWithPicture 並按兩下 [完成]。

將 New TableAdapter 方法命名為 UpdateWithPicture

圖 2:將新的 TableAdapter 方法 UpdateWithPicture 命名 (按兩下以檢視大小完整的影像)

步驟 2:新增商業規則層方法

除了更新 DAL 之外,我們需要更新 BLL,以包含更新和刪除類別的方法。 這些是從呈現層叫用的方法。

若要刪除類別,我們可以使用 CategoriesTableAdapter 自動產生的 Delete 方法。 將下列方法新增至 CategoriesBLL 類別:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteCategory(int categoryID)
{
    int rowsAffected = Adapter.Delete(categoryID);
    // Return true if precisely one row was deleted, otherwise false
    return rowsAffected == 1;
}

在本教學課程中,讓我們建立兩個更新類別的方法- 一個預期二進位圖片數據,並 UpdateWithPicture 叫用我們剛新增至 CategoriesTableAdapter 的方法,另一個方法只 CategoryName接受 、 DescriptionBrochurePath 值,並使用 CategoriesTableAdapter 類別的自動產生 Update 語句。 使用兩種方法背後的理由是在某些情況下,使用者可能會想要更新類別圖片及其其他字段,在此情況下,用戶必須上傳新圖片。 上傳的圖片二進位數據接著可以在語句中使用 UPDATE 。 在其他情況下,使用者可能只有興趣更新名稱與描述。 但是, UPDATE 如果語句也預期數據行的 Picture 二進位數據,我們也需要提供該資訊。 這需要額外的資料庫行程,才能傳回正在編輯之記錄的圖片數據。 因此,我們想要兩 UPDATE 種方法。 商業規則層會根據是否在更新類別時提供圖片數據來判斷要使用哪一個。

為了方便這樣做,請將兩個方法新增至 CategoriesBLL 類別,這兩個方法都命名為 UpdateCategory。 第一個應該接受三string個、一個byte數位和 做int為其輸入參數;第二個int,只接受三string個 和 。 輸入 string 參數適用於類別名稱、描述和折頁簿檔案路徑、 byte 陣列適用於類別圖片的二進位內容,以及 int 用來 CategoryID 識別要更新之記錄的 。 請注意,如果傳入 byte 的陣列為 null,則第一個多載會叫用第二個多載:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateCategory(string categoryName, string description, 
    string brochurePath, byte[] picture, int categoryID)
{
    // If no picture is specified, use other overload
    if (picture == null)
        return UpdateCategory(categoryName, description, brochurePath, categoryID);
    // Update picture, as well
    int rowsAffected = Adapter.UpdateWithPicture
        (categoryName, description, brochurePath, picture, categoryID);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateCategory(string categoryName, string description, 
    string brochurePath, int categoryID)
{
    int rowsAffected = Adapter.Update
        (categoryName, description, brochurePath, categoryID);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}

步驟 3:透過插入和檢視功能複製

上述教學課程 中,我們建立了名為 UploadInDetailsView.aspx 的頁面,列出 GridView 中的所有類別,並提供 DetailsView 將新類別新增至系統。 在本教學課程中,我們將擴充 GridView 以包含編輯和刪除支援。 而不是繼續從 UploadInDetailsView.aspx工作,讓我們改為將本教學課程變更 UpdatingAndDeleting.aspx 放在相同資料夾 ~/BinaryData的頁面中。 將宣告式標記與程式代碼從 UploadInDetailsView.aspx 複製並貼到 UpdatingAndDeleting.aspx

從開啟 UploadInDetailsView.aspx 頁面開始。 複製 專案內 <asp:Content> 的所有宣告式語法,如圖 3 所示。 接下來,開啟 UpdatingAndDeleting.aspx 並貼上其 <asp:Content> 元素中的這個標記。 同樣地,將程式代碼從頁面的程式 UploadInDetailsView.aspx 代碼後置類別 UpdatingAndDeleting.aspx複製到 。

從 UploadInDetailsView.aspx複製宣告式標記

圖 3:從 UploadInDetailsView.aspx (按下複製宣告式標記 ,以檢視完整大小的影像)

複製宣告式標記和程式代碼之後,請造訪 UpdatingAndDeleting.aspx。 您應該會看到相同的輸出,並擁有與上一個教學課程頁面相同的用戶體驗 UploadInDetailsView.aspx

步驟 4:將刪除支援新增至 ObjectDataSource 和 GridView

如我們在 插入、更新和刪除資料 的概觀中所討論,GridView 提供內建的刪除功能,如果網格線的基礎數據源支援刪除,則可以在複選框的刻度啟用這些功能。 目前 GridView 系結至 () CategoriesDataSource 不支援刪除的 ObjectDataSource。

若要解決此問題,請按兩下 ObjectDataSource 智慧標記中的 [設定資料源] 選項,以啟動精靈。 第一個畫面顯示 ObjectDataSource 已設定為使用 CategoriesBLL 類別。 點擊 [下一步]。 目前,只會指定 ObjectDataSource s InsertMethodSelectMethod 屬性。 不過,精靈會分別使用 和 DeleteCategory 方法,自動填入 UPDATE 和 DELETE UpdateCategory 索引卷標的下拉式清單。 這是因為在 類別中, CategoriesBLL 我們使用標示這些方法 DataObjectMethodAttribute 做為更新和刪除的預設方法。

現在,將 [更新] 索引標籤的下拉式清單設定為 ([無]) ,但將 [DELETE] 索引卷標的下拉式清單設定為 DeleteCategory。 我們將在步驟 6 中返回此精靈,以新增更新支援。

將 ObjectDataSource 設定為使用 DeleteCategory 方法

圖 4:將 ObjectDataSource 設定為使用 DeleteCategory 方法 (按兩下即可檢視完整大小的影像)

注意

完成精靈時,Visual Studio 可能會詢問您是否要重新整理字段和密鑰,這會重新產生數據 Web 控件欄位。 選擇 [否],因為選擇 [是] 會覆寫您可能所做的任何字段自定義。

ObjectDataSource 現在會包含其 DeleteMethod 屬性的值以及 DeleteParameter。 回想一下,使用精靈指定方法時,Visual Studio 會將 ObjectDataSource s OldValuesParameterFormatString 屬性設定為 original_{0},這會導致更新和刪除方法調用發生問題。 因此,請完全清除此屬性,或將其重設為預設值 。 {0} 如果您需要重新整理此 ObjectDataSource 屬性上的記憶體,請參閱 插入、更新和刪除數據 教學課程的概觀。

完成精靈並修正 OldValuesParameterFormatString之後,ObjectDataSource 的宣告式標記看起來應該如下所示:

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture" 
    DeleteMethod="DeleteCategory">
    <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>
    <DeleteParameters>
        <asp:Parameter Name="categoryID" Type="Int32" />
    </DeleteParameters>
</asp:ObjectDataSource>

設定 ObjectDataSource 之後,請核取 GridView 智慧標記中的 [啟用刪除] 複選框,將刪除功能新增至 GridView。 這會將 CommandField 新增至 GridView,其 ShowDeleteButton 屬性設定為 true

啟用在 GridView 中刪除的支援

圖 5:在 GridView 中啟用刪除的支援 (按單擊即可檢視大小完整的映射)

請花點時間測試刪除功能。 數據表與CategoryIDCategories數據表CategoryID之間有外鍵,因此當您嘗試刪除前八個類別的任何Products一個時,將會收到外鍵條件約束違規例外狀況。 若要測試這項功能,請新增類別,並提供折頁簿和圖片。 圖 6 所示的測試類別包含名為 的測試 Test.pdf 折頁簿檔案和測試圖片。 圖 7 顯示新增測試類別之後的 GridView。

使用摺頁冊和影像新增測試類別

圖 6:新增含有折頁冊和影像的測試類別 (按兩下即可檢視大小完整的影像)

插入測試類別之後,它會顯示在 GridView 中

圖 7:插入測試類別之後,它會顯示在 GridView (按兩下即可檢視大小完整的影像)

在 Visual Studio 中,重新整理 方案總管。 您現在應該會在資料夾中看到新的檔案 ~/BrochuresTest.pdf (請參閱圖 8) 。

接下來,按兩下 [測試類別] 資料列中的 [刪除] 連結,導致頁面回傳,以及 CategoriesBLL 引發類別的 DeleteCategory 方法。 這會叫用 DAL s Delete 方法,導致適當的 DELETE 語句傳送至資料庫。 然後,數據會重新系結至 GridView,且標記會傳回至不再存在測試類別的用戶端。

雖然刪除工作流程已成功從 Categories 數據表移除 [測試類別] 記錄,但並未從網頁伺服器的文件系統中移除其折頁簿檔案。 重新整理 方案總管,您會看到Test.pdf仍在~/Brochures資料夾中。

Test.pdf 檔案未從網頁伺服器檔案系統中刪除

圖 8:檔案 Test.pdf 未從網頁伺服器檔案系統中刪除

步驟 5:移除已刪除類別的摺頁冊檔案

儲存資料庫外部二進位數據的其中一個缺點是,刪除相關聯的資料庫記錄時,必須採取額外的步驟來清除這些檔案。 GridView 和 ObjectDataSource 提供在刪除命令執行之前和之後引發的事件。 我們實際上需要為前置和後置動作事件建立事件處理程式。 Categories刪除記錄之前,我們需要判斷其 PDF 檔案的路徑,但在刪除類別之前,我們不想刪除 PDF,以防有一些例外狀況且不會刪除類別。

GridView s RowDeleting 事件 會在叫用 ObjectDataSource 的 delete 命令之前引發,而其 RowDeleted 事件 會在之後引發。 使用下列程式代碼建立這兩個事件的事件處理程式:

// A page variable to "remember" the deleted category's BrochurePath value 
string deletedCategorysPdfPath = null;
protected void Categories_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    // Determine the PDF path for the category being deleted...
    int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = 
        categoryAPI.GetCategoryByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    if (category.IsBrochurePathNull())
        deletedCategorysPdfPath = null;
    else
        deletedCategorysPdfPath = category.BrochurePath;
}
protected void Categories_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    // Delete the brochure file if there were no problems deleting the record
    if (e.Exception == null)
    {
        // Is there a file to delete?
        if (deletedCategorysPdfPath != null)
        {
            System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
        }
    }
}

RowDeleting在事件處理程式中,CategoryID從 GridView s DataKeys 集合擷取要刪除之數據列的 ,可透過e.Keys集合在此事件處理程式中存取。 接下來, CategoriesBLL 會叫用 類別 s GetCategoryByCategoryID(categoryID) ,以傳回所刪除記錄的相關信息。 如果傳 CategoriesDataRow 回的物件具有非NULL``BrochurePath 值,則會儲存在頁面變數 deletedCategorysPdfPath 中,以便刪除事件處理程式中的 RowDeleted 檔案。

注意

BrochurePath我們可以將 新增BrochurePath至 GridView s DataKeyNames 屬性,並透過e.Keys集合存取記錄值,而不是擷取要在事件處理程式中刪除RowDeleting之記錄的詳細Categories數據。 這麼做會稍微增加 GridView 的檢視狀態大小,但會減少所需的程式代碼數量,並儲存資料庫車程。

叫用 ObjectDataSource 基礎刪除命令之後,會引發 GridView 的 RowDeleted 事件處理程式。 如果刪除資料時沒有例外狀況,而且 有的值 deletedCategorysPdfPath,則會從文件系統中刪除 PDF。 請注意,不需要這個額外的程式代碼,即可清除與其圖片相關聯的類別二進位數據。 這是因為圖片數據會直接儲存在資料庫中,因此刪除 Categories 數據列也會刪除該類別的圖片數據。

新增兩個事件處理程序之後,請再次執行此測試案例。 刪除類別時,也會刪除其相關聯的 PDF。

更新現有記錄的相關二進位數據可提供一些有趣的挑戰。 本教學課程的其餘部分會深入探討如何將更新功能新增至折頁簿和圖片。 步驟 6 會探索在步驟 7 查看更新圖片時更新折頁簿信息的技術。

步驟 6:更新類別折頁冊

插入、更新和刪除數據 概觀教學課程所述,GridView 提供內建的數據列層級編輯支援,如果已適當設定基礎數據源,則可以依複選框的刻度實作。 目前, CategoriesDataSource ObjectDataSource 尚未設定為包含更新支援,因此讓我們在 中新增。

從 ObjectDataSource 精靈按兩下 [設定數據源] 連結,然後繼續進行第二個步驟。 DataObjectMethodAttribute由於 中使用的 CategoriesBLL,UPDATE 下拉式清單應該會自動填入UpdateCategory多載,該多載會接受所有數據行的四個輸入參數 (,但 Picture) 。 變更此選項,使其使用具有五個參數的多載。

將 ObjectDataSource 設定為使用 UpdateCategory 方法,其中包含圖片的參數

圖 9:將 ObjectDataSource 設定為使用 UpdateCategory 包含參數的方法 Picture (按兩下即可檢視大小完整的影像)

ObjectDataSource 現在會包含其 UpdateMethod 屬性的值,以及對應的 UpdateParameter 。 如步驟 4 所述,Visual Studio 會在使用 [設定數據源精靈] 時,將 ObjectDataSource s OldValuesParameterFormatString 屬性設定為 original_{0} 。 這會導致更新和刪除方法調用發生問題。 因此,請完全清除此屬性,或將其重設為預設值 。 {0}

完成精靈並修正 OldValuesParameterFormatString之後,ObjectDataSource 的宣告式標記看起來應該如下所示:

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture" 
    DeleteMethod="DeleteCategory" UpdateMethod="UpdateCategory">
    <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>
    <DeleteParameters>
        <asp:Parameter Name="categoryID" Type="Int32" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="categoryName" Type="String" />
        <asp:Parameter Name="description" Type="String" />
        <asp:Parameter Name="brochurePath" Type="String" />
        <asp:Parameter Name="picture" Type="Object" />
        <asp:Parameter Name="categoryID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

若要開啟 GridView 的內建編輯功能,請檢查 GridView 智慧標記中的 [啟用編輯] 選項。 這會將 CommandField s ShowEditButton 屬性設定為 true,導致新增 [編輯] 按鈕 (和 [更新] 和 [取消] 按鈕,以供編輯) 的數據列。

設定 GridView 以支援編輯

圖 10:將 GridView 設定為支援編輯 (按兩下即可檢視大小完整的影像)

瀏覽瀏覽器的頁面,然後按兩下其中一列的 [編輯] 按鈕。 CategoryNameDescription BoundFields 會轉譯為文字框。 BrochurePath TemplateField 缺少 EditItemTemplate,因此它會繼續顯示其ItemTemplate折頁簿的連結。 ImageField 會 Picture 轉譯為 TextBox Text ,其屬性會指派 ImageField s DataImageUrlField 值的值,在此案例中為 CategoryID

GridView 缺少 WorkbookPath 的編輯介面

圖 11:GridView 缺少編輯介面, BrochurePath (按兩下即可檢視完整大小的影像)

BrochurePath自訂編輯介面

我們需要建立TemplateField的 BrochurePath 編輯介面,其中一個可讓使用者:

  • 依原樣保留類別的摺頁冊
  • 上傳新的摺頁冊來更新類別的摺頁冊,或
  • 在類別不再有相關聯的摺頁冊) 的情況下,請完全移除類別的折頁 (。

我們也需要更新 Picture ImageField 的編輯介面,但我們將在步驟 7 中取得。

從 GridView 的智慧標記中,按兩下 [編輯範本] 連結,然後從下拉式清單中選取 BrochurePath TemplateField s EditItemTemplate 。 將 RadioButtonList Web 控制項新增至此範本,並將其 ID 屬性 BrochureOptions 設定為 ,並將其 AutoPostBack 屬性設定為 true。 從 屬性視窗 中,按兩下屬性中的Items省略號,這會顯示ListItem集合 編輯器。 分別新增下列三個選項,其中 Value s 1、2 和 3:

  • 使用目前的折頁簿
  • 拿掉目前的折頁簿
  • 上傳新的摺頁冊

將第一個 ListItemSelected 屬性設定為 true

將 Three ListItems 新增至 RadioButtonList

圖 12:將三個 ListItem 新增至 RadioButtonList

在 RadioButtonList 底下,新增名為的 BrochureUploadFileUpload 控件。 將屬性 Visible 設定為 false

將 RadioButtonList 和 FileUpload 控件新增至 EditItemTemplate

圖 13:將 RadioButtonList 和 FileUpload 控件新增至 EditItemTemplate (按兩下即可檢視大小完整的影像)

此 RadioButtonList 提供使用者的三個選項。 概念是只有在選取 [上傳新摺頁冊] 的最後一個選項時,才會顯示 FileUpload 控件。 若要達成此目的,請建立 RadioButtonList s SelectedIndexChanged 事件的事件處理程式,並新增下列程式代碼:

protected void BrochureOptions_SelectedIndexChanged(object sender, EventArgs e)
{
    // Get a reference to the RadioButtonList and its Parent
    RadioButtonList BrochureOptions = (RadioButtonList)sender;
    Control parent = BrochureOptions.Parent;
    // Now use FindControl("controlID") to get a reference of the 
    // FileUpload control
    FileUpload BrochureUpload = 
        (FileUpload)parent.FindControl("BrochureUpload");
    // Only show BrochureUpload if SelectedValue = "3"
    BrochureUpload.Visible = (BrochureOptions.SelectedValue == "3");
}

由於 RadioButtonList 和 FileUpload 控件位於範本內,因此我們必須撰寫一些程式碼,以程式設計方式存取這些控件。 SelectedIndexChanged事件處理程式會在輸入參數中傳遞 RadioButtonList 的sender參考。 若要取得 FileUpload 控件,我們需要取得 RadioButtonList 的父控件,並使用 FindControl("controlID") 方法。 一旦我們同時參考 RadioButtonList 和 FileUpload 控件之後,只有在 RadioButtonList 等於 SelectedValue 3 時,FileUpload 控件的 Visible 屬性才會設定true為 ,這是 Value Upload 新折頁冊 ListItem的 。

有了此程式代碼,請花點時間測試編輯介面。 按兩下資料列的 [編輯] 按鈕。 一開始,應該選取 [使用目前的折頁簿] 選項。 變更選取的索引會導致回傳。 如果選取第三個選項,則會顯示 FileUpload 控件,否則會隱藏它。 圖 14 顯示第一次按兩下 [編輯] 按鈕時的編輯介面;圖 15 顯示選取 [上傳新折頁冊] 選項之後的介面。

一開始,已選取 [使用目前的折頁簿選項]

圖 14:一開始,[使用目前的折頁簿選項] 為 [選取] (按兩下即可檢視全尺寸影像)

選擇 [上傳新折頁冊] 選項會顯示 FileUpload 控件

圖 15:選擇 [上傳新折頁冊選項] 顯示 [檔案][載入控件] (按兩下即可檢視大小完整的影像)

儲存折頁簿檔案並更新數據BrochurePath

按兩下 GridView s Update 按鈕時,就會引發其 RowUpdating 事件。 會叫用 ObjectDataSource s 更新命令,然後引發 GridView 事件 RowUpdated 。 如同刪除工作流程,我們需要為這兩個事件建立事件處理程式。 在事件處理程式中 RowUpdating ,我們需要根據 SelectedValue RadioButtonList 的 BrochureOptions 判斷要採取的動作:

  • SelectedValue如果 為 1,我們想要繼續使用相同的BrochurePath設定。 因此,我們需要將 ObjectDataSource s brochurePath 參數設定為要更新之記錄的現有 BrochurePath 值。 您可以使用 來設定 e.NewValues["brochurePath"] = valueObjectDataSource s brochurePath 參數。
  • SelectedValue如果為 2,則我們想要將記錄的值BrochurePath設定為 NULL。 這可以藉由將 ObjectDataSource s brochurePath 參數設定為 Nothing來完成,這會導致在 語句中使用UPDATE資料庫NULL。 如果有移除的現有折頁冊檔案,我們需要刪除現有的檔案。 不過,只有在更新完成但不引發例外狀況時,我們才想要這麼做。
  • SelectedValue如果 為 3,則我們想要確保使用者已上傳 PDF 檔案,然後將它儲存至檔案系統,並更新記錄的數據BrochurePath行值。 此外,如果有正在取代的現有折頁冊檔案,我們需要刪除先前的檔案。 不過,只有在更新完成但不引發例外狀況時,我們才想要這麼做。

當 RadioButtonList s SelectedValue 為 3 時,完成的步驟幾乎與 DetailsView s ItemInserting 事件處理程式所使用的步驟完全相同。 當從我們在 一個教學課程中新增的 DetailsView 控件新增類別記錄時,就會執行這個事件處理程式。 因此,我們將此功能重構成不同的方法。 具體來說,我已將通用功能移出兩種方法:

  • ProcessBrochureUpload(FileUpload, out bool) 接受做為輸入 FileUpload 控制項實例和輸出布爾值,指定刪除或編輯作業是否應該繼續,或因某些驗證錯誤而取消。 這個方法會傳回已儲存盤案的路徑,如果沒有 null 儲存盤案,則傳回路徑。
  • DeleteRememberedBrochurePath如果 deletedCategorysPdfPath 不是 null,則會刪除頁面變數deletedCategorysPdfPath中路徑所指定的檔案。

這兩種方法的程序代碼如下。 請注意上一個教學課程中與 DetailsView ItemInserting 事件處理程式之間的ProcessBrochureUpload相似度。 在本教學課程中,我已更新DetailsView的事件處理程式,以使用這些新方法。 下載與此教學課程相關聯的程序代碼,以查看 DetailsView 事件處理程式的修改。

private string ProcessBrochureUpload
    (FileUpload BrochureUpload, out bool CancelOperation)
{
    CancelOperation = false;    // by default, do not cancel operation
    if (BrochureUpload.HasFile)
    {
        // Make sure that a PDF has been uploaded
        if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), 
            ".pdf", true) != 0)
        {
            UploadWarning.Text = 
                "Only PDF documents may be used for a category's brochure.";
            UploadWarning.Visible = true;
            CancelOperation = true;
            return null;
        }
        const string BrochureDirectory = "~/Brochures/";
        string brochurePath = BrochureDirectory + BrochureUpload.FileName;
        string fileNameWithoutExtension = 
            System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
        int iteration = 1;
        while (System.IO.File.Exists(Server.MapPath(brochurePath)))
        {
            brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension, 
                "-", iteration, ".pdf");
            iteration++;
        }
        // Save the file to disk and set the value of the brochurePath parameter
        BrochureUpload.SaveAs(Server.MapPath(brochurePath));
        return brochurePath;
    }
    else
    {
        // No file uploaded
        return null;
    }
}
private void DeleteRememberedBrochurePath()
{
    // Is there a file to delete?
    if (deletedCategorysPdfPath != null)
    {
        System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
    }
}

GridView 的 RowUpdatingRowUpdated 事件處理程式會使用 ProcessBrochureUploadDeleteRememberedBrochurePath 方法,如下列程式代碼所示:

protected void Categories_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    // Reference the RadioButtonList
    RadioButtonList BrochureOptions = 
        (RadioButtonList)Categories.Rows[e.RowIndex].FindControl("BrochureOptions");
    // Get BrochurePath information about the record being updated
    int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = 
        categoryAPI.GetCategoryByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    if (BrochureOptions.SelectedValue == "1")
    {
        // Use current value for BrochurePath
        if (category.IsBrochurePathNull())
            e.NewValues["brochurePath"] = null;
        else
            e.NewValues["brochurePath"] = category.BrochurePath;
    }
    else if (BrochureOptions.SelectedValue == "2")
    {
        // Remove the current brochure (set it to NULL in the database)
        e.NewValues["brochurePath"] = null;
    }
    else if (BrochureOptions.SelectedValue == "3")
    {
        // Reference the BrochurePath FileUpload control
        FileUpload BrochureUpload = 
            (FileUpload)Categories.Rows[e.RowIndex].FindControl("BrochureUpload");
        // Process the BrochureUpload
        bool cancelOperation = false;
        e.NewValues["brochurePath"] = 
            ProcessBrochureUpload(BrochureUpload, out cancelOperation);
        e.Cancel = cancelOperation;
    }
    else
    {
        // Unknown value!
        throw new ApplicationException(
            string.Format("Invalid BrochureOptions value, {0}", 
                BrochureOptions.SelectedValue));
    }
    if (BrochureOptions.SelectedValue == "2" || 
        BrochureOptions.SelectedValue == "3")
    {
        // "Remember" that we need to delete the old PDF file
        if (category.IsBrochurePathNull())
            deletedCategorysPdfPath = null;
        else
            deletedCategorysPdfPath = category.BrochurePath;
    }
}
protected void Categories_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
    // If there were no problems and we updated the PDF file, 
    // then delete the existing one
    if (e.Exception == null)
    {
        DeleteRememberedBrochurePath();
    }
}

請注意事件處理程式如何使用 RowUpdating 一系列的條件語句,根據 BrochureOptions RadioButtonList 的 SelectedValue 屬性值來執行適當的動作。

有了此程式代碼,您可以編輯類別,並讓它使用其目前的折頁簿、不使用折頁簿,或上傳新的折頁簿。 請繼續操作,然後再試一次。在和 RowUpdated 事件處理程式中RowUpdating設定斷點,以瞭解工作流程。

步驟 7:上傳新圖片

Picture ImageField 的編輯介面會轉譯為填入其 DataImageUrlField 屬性中值的文字框。 在編輯工作流程期間,GridView 會將參數傳遞至 ObjectDataSource,其中參數名稱為 ImageField s DataImageUrlField 屬性的值,以及參數 s 值在編輯介面的文字框中輸入的值。 當映像儲存為文件系統上的檔案,且 DataImageUrlField 包含映像的完整 URL 時,此行為就適用。 在這種情況下,編輯介面會在文本框中顯示影像的 URL,使用者可以變更並儲存回資料庫。 授與此預設介面不允許用戶上傳新的影像,但會讓他們將影像的URL從目前的值變更為另一個。 不過,在本教學課程中,ImageField 的預設編輯介面就已足夠,因為 Picture 二進位數據會直接儲存在資料庫中,而 DataImageUrlField 屬性只 CategoryID保留 。

若要進一步了解當使用者使用 ImageField 編輯數據列時,會發生什麼事,請考慮下列範例:用戶編輯具有 CategoryID 10 的數據列,導致 Picture ImageField 轉譯為值為 10 的文本框。 假設使用者將此文字框中的值變更為 50,然後按下 [更新] 按鈕。 發生回傳,GridView 一開始會建立名為 CategoryID 且值為 50 的參數。 不過,在 GridView 傳送此參數 (,以及 CategoryNameDescription 參數) 之前,它會在 DataKeys 集合的值中新增。 因此,它會以目前數據列的基礎CategoryID值 10 覆寫 CategoryID 參數。 簡單來說,ImageField 的編輯介面不會影響本教學課程的編輯工作流程,因為 ImageField s DataImageUrlFieldDataKey 屬性的名稱和網格線的值相同。

雖然 ImageField 可讓您輕鬆地根據資料庫數據顯示影像,但我們不想在編輯介面中提供文本框。 相反地,我們想要提供 FileUpload 控件,讓用戶可用來變更類別的圖片。 BrochurePath不同於值,針對這些教學課程,我們決定要求每個類別都必須有圖片。 因此,我們不需要讓使用者指出沒有相關聯的圖片,使用者可能會上傳新的圖片,或讓目前的圖片保持原狀。

若要自定義 ImageField 的編輯介面,我們需要將其轉換成 TemplateField。 從 GridView 的智慧標記中,按兩下 [編輯資料行] 連結,選取 ImageField,然後按兩下 [將此字位轉換成 TemplateField] 連結。

將 ImageField 轉換成 TemplateField

圖 16:將 ImageField 轉換成 TemplateField

以這種方式將 ImageField 轉換成 TemplateField 會產生具有兩個範本的 TemplateField。 如下列宣告式語法所示, ItemTemplate 包含 Image Web 控件,其 ImageUrl 屬性是以 ImageField 和 DataImageUrlFieldDataImageUrlFormatString 屬性為基礎的數據系結語法指派。 EditItemTemplate包含 TextBox,其Text屬性系結至 屬性所DataImageUrlField指定的值。

<asp:TemplateField>
    <EditItemTemplate>
        <asp:TextBox ID="TextBox1" runat="server" 
            Text='<%# Eval("CategoryID") %>'></asp:TextBox>
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Image ID="Image1" runat="server" 
            ImageUrl='<%# Eval("CategoryID", 
                "DisplayCategoryPicture.aspx?CategoryID={0}") %>' />
    </ItemTemplate>
</asp:TemplateField>

我們需要更新 EditItemTemplate 以使用 FileUpload 控制件。 從 GridView 的智慧標記按兩下 [編輯範本] 連結,然後從下拉式清單中選取 Picture TemplateField s EditItemTemplate 。 在範本中,您應該會看到 TextBox 移除此專案。 接下來,將 FileUpload 控件從 [工具箱] 拖曳至範本,並將其 ID 設定為 PictureUpload。 同時新增文字 若要變更類別的圖片,請指定新的圖片。 若要讓類別的圖片保持相同,請將字段保留空白給範本。

將 FileUpload 控件新增至 EditItemTemplate

圖 17:將 FileUpload 控件新增至 EditItemTemplate (按兩下以檢視大小完整的映射)

自訂編輯介面之後,請在瀏覽器中檢視您的進度。 以唯讀模式檢視數據列時,類別的影像會顯示為先前的影像,但按兩下 [編輯] 按鈕會將圖片列轉譯為具有 FileUpload 控件的文字。

編輯介面包含 FileUpload 控制件

圖 18:編輯介面包含 FileUpload 控件 (按兩下即可檢視完整大小的影像)

回想一下,ObjectDataSource 已設定為呼叫 CategoriesBLL 類別的 UpdateCategory 方法,以接受做為圖片的二進位數據做為 byte 數位的輸入。 不過,如果這個陣列具有 null 值,則會呼叫替代 UpdateCategory 多載,這會發出 UPDATE 不會修改 Picture 數據行的 SQL 語句,進而讓類別的目前圖片保持不變。 因此,在 GridView 事件處理程式 RowUpdating 中,我們需要以程式設計方式參考 PictureUpload FileUpload 控件,並判斷檔案是否已上傳。 如果未上傳, 則我們不想要 指定 參數的值 picture 。 另一方面,如果在 FileUpload 控制件中 PictureUpload 上傳檔案,我們想要確保它是 JPG 檔案。 如果是,我們可以透過 picture 參數將其二進位內容傳送至 ObjectDataSource。

如同步驟 6 中使用的程式代碼,此處所需的大部分程式代碼都已存在於 DetailsView 的 ItemInserting 事件處理程式中。 因此,我已將通用功能重構為新的方法 ValidPictureUpload,並更新 ItemInserting 事件處理程式以使用此方法。

將下列程式代碼新增至 GridView RowUpdating 事件處理程式的開頭。 請務必將此程式碼置於儲存折頁簿檔案的程式代碼之前,因為我們不想在上傳無效的圖片檔案時,將折頁簿儲存到網頁伺服器的文件系統。

// Reference the PictureUpload FileUpload
FileUpload PictureUpload = 
    (FileUpload)Categories.Rows[e.RowIndex].FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
    // Make sure the picture upload is valid
    if (ValidPictureUpload(PictureUpload))
    {
        e.NewValues["picture"] = PictureUpload.FileBytes;
    }
    else
    {
        // Invalid file upload, cancel update and exit event handler
        e.Cancel = true;
        return;
    }
}

方法會 ValidPictureUpload(FileUpload) 採用 FileUpload 控件作為唯一的輸入參數,並檢查上傳的擴展名,以確保上傳的檔案是 JPG;只有在上傳圖片檔案時才會呼叫它。 如果未上傳任何檔案,則不會設定圖片參數,因此會使用預設值 null。 如果上傳並 ValidPictureUpload 傳回 true圖片,則 picture 參數會指派上傳影像的二進位數據;如果方法傳回 false,則會取消更新工作流程,並結束事件處理程式。

ValidPictureUpload(FileUpload)從 DetailsView ItemInserting 事件處理程式重構的方法程式代碼如下:

private bool ValidPictureUpload(FileUpload PictureUpload)
{
    // Make sure that a JPG has been uploaded
    if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), 
            ".jpg", true) != 0 &&
        string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), 
            ".jpeg", true) != 0)
    {
        UploadWarning.Text = 
            "Only JPG documents may be used for a category's picture.";
        UploadWarning.Visible = true;
        return false;
    }
    else
    {
        return true;
    }
}

步驟 8:以 JPG 取代原始類別圖片

回想一下,原始八個類別圖片是包裝在 OLE 標頭中的位圖檔案。 既然我們已新增編輯現有記錄圖片的功能,請花點時間將這些點陣圖取代為 JPG。 如果您想要繼續使用目前的類別圖片,您可以執行下列步驟,將它們轉換成 JPG:

  1. 將點陣圖影像儲存到硬碟。 UpdatingAndDeleting.aspx瀏覽瀏覽器中的頁面,並針對前八個類別的每個類別,以滑鼠右鍵按鍵按下影像並選擇儲存圖片。
  2. 在您選擇的影像編輯器中開啟影像。 例如,您可以使用 Microsoft 小畫家。
  3. 將點陣圖儲存為 JPG 影像。
  4. 使用 JPG 檔案,透過編輯介面更新類別圖片。

編輯類別並上傳 JPG 影像之後,影像將不會在瀏覽器中轉譯,因為 DisplayCategoryPicture.aspx 頁面會從前八個類別的圖片中移除前 78 個字節。 藉由移除執行 OLE 標頭移除的程式代碼來修正此問題。 執行此動作之後, DisplayCategoryPicture.aspx``Page_Load 事件處理程序應該只有下列程序代碼:

protected void Page_Load(object sender, EventArgs e)
{
    int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
    // Get information about the specified category
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = _
        categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    // 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);
}

注意

頁面 UpdatingAndDeleting.aspx 的插入和編輯介面可能會使用更多工作。 CategoryName DetailsView 和 GridView 中的 和 Description BoundFields 應該轉換成 TemplateFields。 由於 CategoryName 不允許 NULL 值,因此應該加入 RequiredFieldValidator。 Description而且 TextBox 應該轉換成多行 TextBox。 我留下這些完成的觸碰,做為您練習。

摘要

本教學課程會完成使用二進位數據的外觀。 在本教學課程和前三個教學課程中,我們已瞭解如何將二進位數據儲存在文件系統或直接儲存在資料庫中。 用戶藉由從硬碟選取檔案並將其上傳至網頁伺服器,以便將二進位數據儲存在文件系統上,或插入資料庫,以提供二進位數據給系統。 ASP.NET 2.0 包含 FileUpload 控件,讓提供這類介面就像拖放一樣簡單。 不過,如 上傳檔案 教學課程中所述,FileUpload 控件只適用於相對小型的檔案上傳,理想情況下不會超過 MB。 我們也探索如何將上傳的數據與基礎數據模型產生關聯,以及如何編輯和刪除現有記錄中的二進位數據。

我們的下一組教學課程會探索各種快取技術。 快取提供一種方法,藉由取得昂貴作業的結果,並將其儲存在可更快速存取的位置,以改善應用程式的整體效能。

快樂的程序設計!

關於作者

Scott Mitchell 是七份 ASP/ASP.NET 書籍的作者,以及 1998 年以來與 Microsoft Web 技術合作的 4GuysFromRolla.com 作者。 Scott 是獨立顧問、訓練員和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格來連線到 ,您可以在 找到http://ScottOnWriting.NET

特別感謝

本教學課程系列是由許多實用的檢閱者檢閱。 本教學課程的首席檢閱者是 Teresa Murphy。 有興趣檢閱即將推出的 MSDN 文章嗎? 如果是,請將一行 mitchell@4GuysFromRolla.com放在 。