插入、更新和刪除數據的概觀 (VB)
在本教學課程中,我們將瞭解如何將 ObjectDataSource 的 Insert()、Update() 和 Delete() 方法對應至 BLL 類別的方法,以及如何設定 GridView、DetailsView 和 FormView 控件以提供數據修改功能。
簡介
在過去數個教學課程中,我們已檢查如何使用 GridView、DetailsView 和 FormView 控件,在 ASP.NET 頁面中顯示數據。 這些控制項只會使用提供給它們的數據。 通常,這些控件會透過使用數據源控件來存取數據,例如 ObjectDataSource。 我們已瞭解 ObjectDataSource 如何作為 ASP.NET 頁面與基礎數據之間的 Proxy。 當 GridView 需要顯示數據時,它會叫用其 ObjectDataSource 的 Select()
方法,進而從我們的商業規則層 (BLL) 叫用方法,該層會呼叫適當數據存取層的 TableAdapter 中的方法,進而將查詢傳送 SELECT
至 Northwind 資料庫。
回想一下,當我們在第一個教學課程中於 DAL 中建立 TableAdapters 時,Visual Studio 會自動新增方法,以便從基礎資料庫數據表插入、更新和刪除數據。 此外,在建立商業規則層中,我們設計了 BLL 中呼叫到這些數據修改 DAL 方法的方法。
除了其 Select()
方法之外,ObjectDataSource 也有 Insert()
、 Update()
和 Delete()
方法。 Select()
如同方法,這三種方法可以對應至基礎物件中的方法。 設定為插入、更新或刪除數據時,GridView、DetailsView 和 FormView 控制項會提供使用者介面來修改基礎數據。 這個使用者介面會呼叫 Insert()
ObjectDataSource 的、 Update()
和 Delete()
方法,然後叫用基礎對象的相關聯方法(請參閱圖 1)。
圖 1:ObjectDataSource 的 Insert()
、 Update()
和 Delete()
方法做為 BLL 的 Proxy(按兩下以檢視完整大小的影像)
在本教學課程中,我們將瞭解如何將 ObjectDataSource 的 Insert()
、 Update()
和 Delete()
方法對應至 BLL 中類別的方法,以及如何設定 GridView、DetailsView 和 FormView 控件以提供資料修改功能。
步驟 1:建立插入、更新和刪除教學課程網頁
在我們開始探索如何插入、更新和刪除數據之前,讓我們先花點時間在網站專案中建立 ASP.NET 頁面,我們將在本教學課程和接下來幾個頁面。 首先新增一個名為 EditInsertDelete
的新資料夾。 接下來,將以下 ASP.NET 頁面新增至該資料夾,確保將每個頁面與 Site.master
母版頁相關聯:
Default.aspx
Basics.aspx
DataModificationEvents.aspx
ErrorHandling.aspx
UIValidation.aspx
CustomizedUI.aspx
OptimisticConcurrency.aspx
ConfirmationOnDelete.aspx
UserLevelAccess.aspx
圖 2:新增數據修改相關教學課程 ASP.NET 頁面
與其他資料夾一樣,EditInsertDelete
資料夾將在 Default.aspx
其部分列出教學課程。 回想一下,SectionLevelTutorialListing.ascx
使用者控制項提供了此功能。 因此,將這個使用者控制件Default.aspx
從 方案總管 拖曳至頁面的設計檢視,將它新增至 。
圖 3:將使用者控制項新增 SectionLevelTutorialListing.ascx
至 Default.aspx
(按兩下以檢視完整大小的影像)
最後,將頁面新增為檔案的專案 Web.sitemap
。 具體來說,在自定義格式 <siteMapNode>
設定之後新增下列標記:
<siteMapNode title="Editing, Inserting, and Deleting" url="~/EditInsertDelete/Default.aspx" description="Samples of Reports that Provide Editing, Inserting, and Deleting Capabilities"> <siteMapNode url="~/EditInsertDelete/Basics.aspx" title="Basics" description="Examines the basics of data modification with the GridView, DetailsView, and FormView controls." /> <siteMapNode url="~/EditInsertDelete/DataModificationEvents.aspx" title="Data Modification Events" description="Explores the events raised by the ObjectDataSource pertinent to data modification." /> <siteMapNode url="~/EditInsertDelete/ErrorHandling.aspx" title="Error Handling" description="Learn how to gracefully handle exceptions raised during the data modification workflow." /> <siteMapNode url="~/EditInsertDelete/UIValidation.aspx" title="Adding Data Entry Validation" description="Help prevent data entry errors by providing validation." /> <siteMapNode url="~/EditInsertDelete/CustomizedUI.aspx" title="Customize the User Interface" description="Customize the editing and inserting user interfaces." /> <siteMapNode url="~/EditInsertDelete/OptimisticConcurrency.aspx" title="Optimistic Concurrency" description="Learn how to help prevent simultaneous users from overwritting one another s changes." /> <siteMapNode url="~/EditInsertDelete/ConfirmationOnDelete.aspx" title="Confirm On Delete" description="Prompt a user for confirmation when deleting a record." /> <siteMapNode url="~/EditInsertDelete/UserLevelAccess.aspx" title="Limit Capabilities Based on User" description="Learn how to limit the data modification functionality based on the user role or permissions." /> </siteMapNode>
更新 Web.sitemap
後,請花點時間透過瀏覽器檢視教學課程網站。 現在,左側功能表會包含可編輯、插入和刪除教學課程的項目。
圖 4:網站地圖現在包含編輯、插入和刪除教學課程的專案
步驟 2:新增和設定 ObjectDataSource 控件
由於 GridView、DetailsView 和 FormView 在數據修改功能和版面配置上各有不同,因此讓我們個別檢查每一個。 不過,讓我們建立單一 ObjectDataSource,讓這三個控件範例都可以共用的單一 ObjectDataSource,而不是讓每個控件使用自己的 ObjectDataSource。
Basics.aspx
開啟頁面,將 ObjectDataSource 從 [工具箱] 拖曳至設計工具,然後按兩下其智慧標記中的 [設定數據源] 連結。 ProductsBLL
因為是唯一提供編輯、插入和刪除方法的 BLL 類別,請將 ObjectDataSource 設定為使用這個類別。
圖 5:將 ObjectDataSource 設定為使用 ProductsBLL
類別 (按一下即可檢視完整大小的影像)
在下一個畫面中,我們可以選取適當的索引標籤,並從下拉式清單中選擇方法,以指定ProductsBLL
類別的對應至 ObjectDataSource 的Select()
、 Insert()
Update()
和 Delete()
方法。 圖 6 現在看起來應該很熟悉,它會將 ObjectDataSource 的 Select()
方法對應至 ProductsBLL
類別的 GetProducts()
方法。 從 Insert()
頂端清單中選取適當的索引標籤,即可設定、 Update()
和 Delete()
方法。
圖 6:讓 ObjectDataSource 傳回所有產品 (按兩下以檢視完整大小的影像)
圖 7、8 和 9 顯示 ObjectDataSource 的 UPDATE、INSERT 和 DELETE 索引卷標。 設定這些索引標籤,讓 Insert()
、 Update()
和 Delete()
方法分別叫 ProductsBLL
用 類別的 UpdateProduct
、 AddProduct
和 DeleteProduct
方法。
圖 7:將 ObjectDataSource 的 Update()
方法對應至 ProductBLL
類別的 UpdateProduct
方法(按兩下以檢視完整大小的影像)
圖 8:將 ObjectDataSource 的 Insert()
方法對應至ProductBLL
類別的 Add Product
方法(按兩下以檢視完整大小的影像)
圖 9:將 ObjectDataSource 的 Delete()
方法對應至 ProductBLL
類別的 DeleteProduct
方法(按兩下以檢視完整大小的影像)
您可能已經注意到 UPDATE、INSERT 和 DELETE 索引標籤中的下拉式清單已選取這些方法。 這要歸功於我們使用 DataObjectMethodAttribute
裝飾的 ProductsBLL
方法。 例如,DeleteProduct 方法具有下列簽章:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteProduct(ByVal productID As Integer) As Boolean End Function
屬性 DataObjectMethodAttribute
會指出每個方法的用途,無論是用於選取、插入、更新或刪除,以及其是否為預設值。 如果您在建立 BLL 類別時省略這些屬性,您必須從 UPDATE、INSERT 和 DELETE 索引標籤標手動選取方法。
確定適當的 ProductsBLL
方法對應至 ObjectDataSource 的 Insert()
、 Update()
和 Delete()
方法之後,請按兩下 [完成] 以完成精靈。
檢查 ObjectDataSource 的標記
透過其精靈設定 ObjectDataSource 之後,請移至 [來源] 檢視,以檢查產生的宣告式標記。 標記 <asp:ObjectDataSource>
會指定要叫用的基礎物件和方法。 此外,還有、 與會對應到類別、 與 DeleteProduct
方法的UpdateProduct
AddProduct
輸入參數ProductsBLL
:InsertParameters
UpdateParameters
DeleteParameters
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct" InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL" UpdateMethod="UpdateProduct"> <DeleteParameters> <asp:Parameter Name="productID" Type="Int32" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> <InsertParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> </InsertParameters> </asp:ObjectDataSource>
ObjectDataSource 包含其相關聯方法之每個輸入參數的參數,就像當 ObjectDataSource 設定為呼叫需要輸入參數的 select 方法時,會出現一份清單SelectParameter
。GetProductsByCategoryID(categoryID)
如我們很快就會看到,這些 DeleteParameters
、 UpdateParameters
和 InsertParameters
的值會在叫用 ObjectDataSource 的 Insert()
、 Update()
或 Delete()
方法之前,由 GridView、DetailsView 和 FormView 自動設定。 這些值也可以視需要以程式設計方式設定,因為我們將在未來的教學課程中討論。
使用精靈將 ObjectDataSource 設定為 的一個副作用是 Visual Studio 會將 OldValuesParameterFormatString 屬性 設定為 original_{0}
。 這個屬性值是用來包含所編輯數據的原始值,而且在兩個案例中很有用:
- 如果在編輯記錄時,用戶可以變更主鍵值。 在此情況下,必須同時提供新的主鍵值和原始主鍵值,以便找到具有原始主鍵值的記錄,並據以更新其值。
- 使用開放式並行存取時。 開放式並行存取是一種技術,可確保兩個同時使用者不會覆寫彼此的變更,而且是未來教學課程的主題。
屬性 OldValuesParameterFormatString
會指出基礎物件更新中輸入參數的名稱,並刪除原始值的方法。 當我們探索開放式並行存取時,我們將更詳細地討論此屬性及其用途。 不過,我現在會提出它,因為我們的 BLL 方法不會預期原始值,因此請務必移除這個屬性。 OldValuesParameterFormatString
當數據 Web 控制件嘗試叫用 ObjectDataSource 的 Update()
或 Delete()
方法時,將 屬性設定為預設值{0}
以外的任何項目都會造成錯誤,因為 ObjectDataSource 會嘗試同時傳入 UpdateParameters
或 DeleteParameters
指定的 和原始值參數。
如果目前還不清楚這點,別擔心,我們將在未來的教學課程中檢查此屬性及其公用程式。 目前,請務必完全從宣告式語法移除這個屬性宣告,或將值設定為預設值 ({0})。
注意
如果您只要從設計視圖中的 屬性視窗 清除OldValuesParameterFormatString
屬性值,屬性仍會存在於宣告式語法中,但會設定為空字串。 不幸的是,這仍然會導致上述相同的問題。 因此,請從宣告式語法移除 屬性,或從 屬性視窗 將值設定為預設值 {0}
。
步驟 3:新增數據 Web 控制項並設定數據修改
將 ObjectDataSource 新增至頁面並設定之後,我們即可將數據 Web 控件新增至頁面以顯示數據,並提供使用者修改數據的方法。 我們將分別查看 GridView、DetailsView 和 FormView,因為這些數據 Web 控件在數據修改功能和設定上有所不同。
如本文的其餘部分所示,透過 GridView、DetailsView 和 FormView 控件新增非常基本的編輯、插入和刪除支援,就像核取幾個複選框一樣簡單。 真實世界中有許多細微之處和邊緣案例,使得提供這類功能比點選更相關。 不過,本教學課程僅著重於證明簡化的數據修改功能。 未來的教學課程將探討在真實世界環境中無疑會出現的擔憂。
從 GridView 刪除數據
從 [工具箱] 將 GridView 拖曳至設計工具開始。 接下來,從 GridView 智慧標記的下拉式清單中選取 ObjectDataSource 至 GridView。 此時 GridView 的宣告式標記將是:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False" ReadOnly="True" SortExpression="ProductID" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True" SortExpression="SupplierName" /> </Columns> </asp:GridView>
透過其智慧標記將 GridView 系結至 ObjectDataSource 有兩個優點:
- ObjectDataSource 傳回的每個字段都會自動建立 BoundFields 和 CheckBoxFields。 此外,BoundField 和 CheckBoxField 的屬性會根據基礎欄位的元數據來設定。 例如,
ProductID
CategoryName
、 和SupplierName
欄位在 中ProductsDataTable
標示為唯讀,因此在編輯時不應該更新。 為了容納這一點,這些 BoundFields 的 ReadOnly 屬性 會設定為True
。 - DataKeyNames 屬性會指派給基礎物件的主鍵欄位。。 使用 GridView 來編輯或刪除資料時,這一點很重要,因為此屬性表示唯一識別每個記錄的欄位(或欄位集)。 如需屬性的詳細資訊
DataKeyNames
,請參閱 使用可選取的 Master GridView 搭配 DetailsView 教學課程的 Master/Detail。
雖然 GridView 可以透過 屬性視窗 或宣告式語法系結至 ObjectDataSource,但這樣做需要您手動新增適當的 BoundField 和DataKeyNames
標記。
GridView 控制項提供資料列層級編輯和刪除的內建支援。 設定 GridView 以支援刪除新增 [刪除] 按鈕的數據行。 當使用者按下特定資料列的 [刪除] 按鈕時,就會發生回傳,而 GridView 會執行下列步驟:
- 已指派 ObjectDataSource
DeleteParameters
的值 - 叫用 ObjectDataSource 的
Delete()
方法,刪除指定的記錄 - GridView 會叫用其
Select()
方法,將本身重新系結至 ObjectDataSource
指派給 DeleteParameters
的值是已按兩下 [刪除] 按鈕之數據列的 field(s) 值 DataKeyNames
。 因此,必須正確設定 GridView 的屬性 DataKeyNames
。 如果遺失, DeleteParameters
則會在步驟 1 中指派 的值 Nothing
,這反過來也不會造成步驟 2 中任何已刪除的記錄。
注意
集合 DataKeys
會儲存在 GridView 的控件狀態中,這表示 DataKeys
即使 GridView 的檢視狀態已停用,也會在回傳中記住這些值。 不過,對於支援編輯或刪除的 GridView,檢視狀態仍然啟用非常重要(預設行為)。 如果您將 GridView 的 EnableViewState
屬性設定為 false
,則編輯和刪除行為會讓單一使用者正常運作,但如果有並行使用者刪除數據,則這些並行使用者可能會不小心刪除或編輯他們不想要的記錄。
這個相同的警告也適用於 DetailsViews 和 FormViews。
若要將刪除功能新增至 GridView,只需移至其智慧標記,然後核取 [啟用刪除] 複選框。
圖 10:核取 [啟用刪除] 複選框
核取智慧標記中的 [啟用刪除] 複選框會將 CommandField 新增至 GridView。 CommandField 會轉譯 GridView 中的數據行,其中包含執行下列一或多項工作的按鈕:選取記錄、編輯記錄,以及刪除記錄。 我們先前已看到 CommandField 的運作方式,其中包含使用可選取的主方格檢視與詳細數據詳細數據檢視教學課程中的選取記錄。
CommandField 包含數個 ShowXButton
屬性,指出 CommandField 中顯示的按鈕系列。 藉由核取 [啟用刪除] 複選框,CommandField 的屬性ShowDeleteButton
True
已新增至 GridView 的 Columns 集合。
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowDeleteButton="True" /> ... BoundFields removed for brevity ... </Columns> </asp:GridView>
此時,相信或不相信,我們已完成將刪除支援新增至 GridView! 如圖 11 所示,透過瀏覽器瀏覽此頁面時,會出現 [刪除] 按鈕的數據行。
圖 11:CommandField 新增刪除按鈕的數據行(按兩下以檢視完整大小的影像)
如果您一直在自行建置本教學課程,請在測試此頁面時按兩下 [刪除] 按鈕會引發例外狀況。 繼續閱讀以了解引發這些例外狀況的原因,以及如何修正這些例外狀況。
注意
如果您遵循本教學課程隨附的下載,這些問題已經考慮過。 不過,我鼓勵您閱讀下列詳細數據,以協助找出可能發生的問題和適當的因應措施。
如果嘗試刪除產品時,您會收到例外狀況,其訊息類似於 “ObjectDataSource 'ObjectDataSource1' 找不到具有參數的非泛型方法 'DeleteProduct':p roductID,original_ProductID”,您可能會忘記從 ObjectDataSource 移除 OldValuesParameterFormatString
屬性。 指定 屬性時 OldValuesParameterFormatString
,ObjectDataSource 會嘗試傳入 productID
和 original_ProductID
輸入參數至 DeleteProduct
方法。 DeleteProduct
不過,只接受單一輸入參數,因此例外狀況。 拿掉 OldValuesParameterFormatString
屬性(或將它設定為 {0}
)會指示 ObjectDataSource 不要嘗試傳入原始輸入參數。
圖 12:確定 OldValuesParameterFormatString
已清除屬性(按兩下以檢視完整大小的影像)
即使您已移除 OldValuesParameterFormatString
屬性,當您嘗試刪除含有訊息的產品時,仍然會收到例外狀況:「DELETE 語句與 REFERENCE 條件約束 『FK_Order_Details_Products』 衝突。Northwind 資料庫包含 和 Products
數據表之間的Order Details
外鍵條件約束,這表示如果數據表中有Order Details
一或多個記錄,就無法從系統刪除產品。 由於 Northwind 資料庫中的每個產品至少有一筆記錄, Order Details
因此在先刪除產品的相關訂單詳細數據記錄之前,我們無法刪除任何產品。
圖 13:外鍵條件約束禁止刪除產品(按兩下以檢視完整大小的影像)
在我們的教學課程中,讓我們只刪除數據表中的所有記錄 Order Details
。 在真實世界中,我們需要:
- 有另一個畫面來管理訂單詳細數據資訊
DeleteProduct
擴增方法,以包含邏輯來刪除指定的產品訂單詳細數據- 修改 TableAdapter 所使用的 SQL 查詢,以包含刪除指定的產品訂單詳細數據
讓我們刪除數據表中的所有記錄 Order Details
,以規避外鍵條件約束。 移至 Visual Studio 中的 [伺服器總管],以滑鼠右鍵按鍵按兩下 NORTHWND.MDF
節點,然後選擇 [新增查詢]。 然後,在查詢視窗中執行下列 SQL 語句: DELETE FROM [Order Details]
圖 14:從 Order Details
資料表中移除所有記錄 (按兩下以檢視完整大小的影像)
清除 Order Details
資料表后,按兩下 [刪除] 按鈕將會刪除產品,而不會發生錯誤。 如果按下 [刪除] 按鈕不會刪除產品,請檢查以確定 GridView 的 DataKeyNames
屬性已設定為主鍵字段 (ProductID
)。
注意
按兩下 [刪除] 按鈕時,就會發生回傳並刪除記錄。 這很危險,因為很容易不小心按兩下錯誤資料列的 [刪除] 按鈕。 在未來的教學課程中,我們將瞭解如何在刪除記錄時新增客戶端確認。
使用 GridView 編輯數據
除了刪除之外,GridView 控件也提供內建的數據列層級編輯支援。 設定 GridView 以支援編輯新增 [編輯] 按鈕的數據行。 從用戶的觀點來看,按兩下資料列的 [編輯] 按鈕會使該數據列變成可編輯,將儲存格轉換成包含現有值的文字框,並將 [編輯] 按鈕取代為 [更新] 和 [取消] 按鈕。 進行所需的變更之後,終端使用者可以按兩下 [更新] 按鈕來認可變更,或按下 [取消] 按鈕來捨棄變更。 在任一情況下,按兩下 [更新] 或 [取消 GridView] 會回到其預先編輯狀態。
從頁面開發人員的觀點來看,當用戶按下特定數據列的 [編輯] 按鈕時,就會發生回傳,而 GridView 會執行下列步驟:
- GridView 的
EditItemIndex
屬性會指派給按兩下 [編輯] 按鈕之數據列的索引 - GridView 會叫用其
Select()
方法,將本身重新系結至 ObjectDataSource - 符合
EditItemIndex
的數據列索引會在「編輯模式」中轉譯。在此模式中,[編輯] 按鈕會由Update和 Cancel 按鈕和 BoundFields 取代,其ReadOnly
屬性為 False(預設值)會轉譯為 TextBox Web 控件,而Text
其屬性會指派給數據欄位的值。
此時,標記會傳回瀏覽器,讓使用者對數據列的數據進行任何變更。 當使用者按兩下 [更新] 按鈕時,就會發生回傳,而 GridView 會執行下列步驟:
- ObjectDataSource
UpdateParameters
的值會指派使用者輸入至 GridView 編輯介面的值 - 叫用 ObjectDataSource 的
Update()
方法,並更新指定的記錄 - GridView 會叫用其
Select()
方法,將本身重新系結至 ObjectDataSource
在步驟 1 中指派給 UpdateParameters
的主要索引鍵值來自 屬性中指定的 DataKeyNames
值,而非主鍵值則來自已編輯數據列之 TextBox Web 控制件中的文字。 與刪除一樣,GridView 的屬性 DataKeyNames
必須正確設定。 如果遺失, UpdateParameters
主鍵值將會在步驟 1 中指派 值 Nothing
,這反過來也不會在步驟 2 中產生任何更新的記錄。
只要核取 GridView 智慧標記中的 [啟用編輯] 複選框,即可啟用編輯功能。
圖 15:核取 [啟用編輯] 複選框
核取 [啟用編輯] 複選框將會新增 CommandField (如有需要),並將其屬性設定 ShowEditButton
為 True
。 如先前所見,CommandField 包含數個 ShowXButton
屬性,指出 CommandField 中顯示的按鈕系列。 核取 [啟用編輯] 複選框會將 ShowEditButton
屬性新增至現有的 CommandField:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" /> ... BoundFields removed for brevity ... </Columns> </asp:GridView>
這就是新增基本的編輯支援。 如圖 16 所示,編輯介面相當粗略每個 BoundField,其 ReadOnly
屬性設定 False
為 (預設值) 會轉譯為 TextBox。 這包括 和 SupplierID
之類的CategoryID
欄位,這些欄位是其他數據表的索引鍵。
圖 16:按兩下 [Chai s 編輯] 按鈕以編輯模式顯示資料列 (按一下以檢視完整大小的影像)
除了要求使用者直接編輯外鍵值之外,編輯介面的介面還缺乏下列方式:
- 如果使用者輸入
CategoryID
或SupplierID
不存在於資料庫中,UPDATE
則會違反外鍵條件約束,導致引發例外狀況。 - 編輯介面不包含任何驗證。 如果您未提供必要值(例如
ProductName
),或輸入預期數值的字串值(例如在文字框中輸入「太多!」UnitPrice
,則會擲回例外狀況。 未來的教學課程將檢查如何將驗證控件新增至編輯使用者介面。 - 目前,所有不是只讀的產品欄位都必須包含在 GridView 中。 如果我們要從 GridView 移除欄位,例如
UnitPrice
,更新數據時 GridView 不會設定UpdateParameters
UnitPrice
值,這會將資料庫記錄UnitPrice
的變更為NULL
值。 同樣地,如果從 GridView 移除所需的欄位,ProductName
更新將會失敗,且上述的相同「數據行 』ProductName' 不允許 Null」例外狀況。 - 編輯介面格式設定會留下許多需要。 以
UnitPrice
四個小數點顯示 。 在理想情況下,CategoryID
和SupplierID
值會包含DropDownLists,列出系統中的類別和供應商。
這些都是我們現在必須存在的缺點,但在未來的教學課程中將會加以解決。
使用 DetailsView 插入、編輯和刪除資料
如先前教學課程中所見,DetailsView 控件會一次顯示一筆記錄,例如 GridView,可讓您編輯和刪除目前顯示的記錄。 使用者從 DetailsView 編輯和刪除項目的體驗,以及來自 ASP.NET 端的工作流程都與 GridView 的相同。 DetailsView 與 GridView 的差異在於它也提供內建插入支援。
若要示範 GridView 的數據修改功能,請先將 DetailsView 新增至 Basics.aspx
現有 GridView 上方的頁面,然後透過 DetailsView 的智慧標記將它系結至現有的 ObjectDataSource。 接下來,清除 DetailsView 的 Height
和 Width
屬性,然後從智慧標記中檢查 [啟用分頁] 選項。 若要啟用編輯、插入和刪除支援,只要勾選智慧標記中的 [啟用編輯]、[啟用插入] 和 [啟用刪除] 複選框即可。
圖 17:設定 DetailsView 以支援編輯、插入和刪除
如同 GridView,新增編輯、插入或刪除支援會將 CommandField 新增至 DetailsView,如下列宣告式語法所示:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <Fields> <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False" ReadOnly="True" SortExpression="ProductID" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True" SortExpression="SupplierName" /> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" ShowInsertButton="True" /> </Fields> </asp:DetailsView>
請注意,針對 DetailsView,CommandField 預設會出現在 Columns 集合的結尾。 由於 DetailsView 的字段會轉譯為數據列,因此 CommandField 會顯示為具有 DetailsView 底部 [插入]、[編輯] 和 [刪除] 按鈕的數據列。
圖 18:設定 DetailsView 以支援編輯、插入和刪除 (按一下以檢視完整大小的影像)
按兩下 [刪除] 按鈕會啟動與 GridView 相同的事件序列:回傳;接著,DetailsView 會根據DataKeyNames
值填入其 ObjectDataSource;然後以呼叫其 ObjectDataSource DeleteParameters
的 Delete()
方法完成,這實際上會從資料庫中移除產品。 在 DetailsView 中編輯的運作方式也與 GridView 相同。
若要插入,使用者會看到 [新增] 按鈕,當按兩下時,會將DetailsView轉譯為「插入模式」。使用「插入模式」,[新增] 按鈕會由 [插入] 和 [取消] 按鈕取代,而且只會顯示屬性 InsertVisible
設定為 True
(預設值) 的 BoundFields。 識別為自動遞增欄位的數據欄位,例如 ProductID
,在透過智慧標記將 DetailsView 系結至數據源時,其 InsertVisible 屬性 會設定 False
為 。
透過智慧標記將數據源系結至 DetailsView 時,Visual Studio InsertVisible
會將 屬性設定為 False
僅針對自動遞增字段。 唯讀欄位,例如 CategoryName
和 SupplierName
,將會以「插入模式」使用者介面顯示,除非其 InsertVisible
屬性明確設定為 False
。 請花點時間將這兩個字段 InsertVisible
的屬性設定為 False
,無論是透過DetailsView的宣告式語法,還是透過智慧標記中的 [編輯字段] 連結。 圖 19 顯示按兩下[編輯字段] 連結, InsertVisible
將屬性設定為 False
。
圖 19:Northwind Traders Now Offers Acme Tea (按兩下以檢視全尺寸影像)
設定 InsertVisible
屬性之後,請在瀏覽器中檢視 Basics.aspx
頁面,然後按下 [新增] 按鈕。 圖 20 顯示將新的飲料 Acme Tea 新增至我們的產品線時,DetailsView。
圖 20:Northwind Traders Now Offers Acme Tea (按兩下以檢視全尺寸影像)
輸入 Acme Tea 的詳細資料並按下 [插入] 按鈕之後,就會發生回傳,並將新記錄新增至 Products
資料庫數據表。 由於此 DetailsView 會依資料庫數據表中存在的產品順序列出產品,因此我們必須分頁至最後一個產品,才能查看新產品。
圖 21:Acme Tea 的詳細數據(按兩下以檢視完整大小的影像)
注意
DetailsView 的 CurrentMode 屬性 表示要顯示的介面,而且可以是下列其中一個值: Edit
、 Insert
或 ReadOnly
。 DefaultMode 屬性會指出 DetailsView 在編輯或插入完成之後所傳回的模式,而且可用於顯示永久處於編輯或插入模式的 DetailsView。
DetailsView 的插入和編輯功能與 GridView 具有相同的限制:用戶必須透過文本框輸入現有 CategoryID
和 SupplierID
值;介面缺少任何驗證邏輯;不允許值或未在資料庫層級指定預設值的所有產品欄位 NULL
都必須包含在插入介面中, 等等。
我們將在未來的文章中檢查延伸和增強 GridView 編輯介面的技術,也可以套用至 DetailsView 控件的編輯和插入介面。
使用 FormView 進行更彈性的數據修改使用者介面
FormView 提供插入、編輯和刪除數據的內建支援,但因為它使用範本,而不是欄位,所以沒有地方可以新增 BoundFields 或 GridView 和 DetailsView 控件所使用的 CommandField 來提供數據修改介面。 相反地,新增專案或編輯現有專案以及 [新增]、[編輯]、[刪除]、[插入]、[更新] 和 [取消] 按鈕時收集使用者輸入的Web控件必須手動新增至適當的範本。 幸運的是,Visual Studio 會在透過智慧標記中的下拉式清單將 FormView 系結至數據源時,自動建立所需的介面。
為了說明這些技術,請先將 FormView 新增至 Basics.aspx
頁面,然後從 FormView 的智慧標記將它系結至已經建立的 ObjectDataSource。 這會為具有 TextBox Web 控制項的 FormView 產生EditItemTemplate
InsertItemTemplate
、 和 ItemTemplate
,以收集 [新增]、[編輯]、[刪除]、[插入]、[更新] 和 [取消] 按鈕的使用者輸入和按鈕 Web 控件。 此外,FormView 的 DataKeyNames
屬性會設定為 ObjectDataSource 所傳回之物件的主鍵欄位(ProductID
)。 最後,檢查 FormView 智慧標記中的 [啟用分頁] 選項。
下列顯示 FormView 系結至 ObjectDataSource 之後,FormView 的 ItemTemplate
宣告式標記。 根據預設,每個非布爾值產品欄位都會系結至 Text
標籤 Web 控件的 屬性,而每個布爾值欄位 (Discontinued
) 都會繫結至 Checked
已停用 CheckBox Web 控制件的屬性。 為了讓按兩下 [新增]、[編輯] 和 [刪除] 按鈕觸發特定 FormView 行為,必須 CommandName
分別將其值設定為 New
、 Edit
和 Delete
。
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ... </EditItemTemplate> <InsertItemTemplate> ... </InsertItemTemplate> <ItemTemplate> ProductID: <asp:Label ID="ProductIDLabel" runat="server" Text='<%# Eval("ProductID") %>'></asp:Label><br /> ProductName: <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Bind("ProductName") %>'> </asp:Label><br /> SupplierID: <asp:Label ID="SupplierIDLabel" runat="server" Text='<%# Bind("SupplierID") %>'> </asp:Label><br /> CategoryID: <asp:Label ID="CategoryIDLabel" runat="server" Text='<%# Bind("CategoryID") %>'> </asp:Label><br /> QuantityPerUnit: <asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Bind("QuantityPerUnit") %>'> </asp:Label><br /> UnitPrice: <asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Bind("UnitPrice") %>'></asp:Label><br /> UnitsInStock: <asp:Label ID="UnitsInStockLabel" runat="server" Text='<%# Bind("UnitsInStock") %>'> </asp:Label><br /> UnitsOnOrder: <asp:Label ID="UnitsOnOrderLabel" runat="server" Text='<%# Bind("UnitsOnOrder") %>'> </asp:Label><br /> ReorderLevel: <asp:Label ID="ReorderLevelLabel" runat="server" Text='<%# Bind("ReorderLevel") %>'> </asp:Label><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked='<%# Bind("Discontinued") %>' Enabled="false" /><br /> CategoryName: <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Bind("CategoryName") %>'> </asp:Label><br /> SupplierName: <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Bind("SupplierName") %>'> </asp:Label><br /> <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" CommandName="Edit" Text="Edit"> </asp:LinkButton> <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete"> </asp:LinkButton> <asp:LinkButton ID="NewButton" runat="server" CausesValidation="False" CommandName="New" Text="New"> </asp:LinkButton> </ItemTemplate> </asp:FormView>
圖 22 顯示透過瀏覽器檢視 FormView 的 ItemTemplate
。 每個產品欄位都會以底部的 [新增]、[編輯] 和 [刪除] 按鈕列出。
圖 22:D efaut FormView ItemTemplate
會列出每個產品欄位以及 [新增]、[編輯] 和 [刪除] 按鈕(按兩下以檢視完整大小的影像)
如同 GridView 和 DetailsView,按兩下 [刪除] 按鈕或任何 Button、LinkButton 或 ImageButton,其CommandName
屬性設定為 Delete 會導致回傳、根據 FormView DataKeyNames
的值填入 ObjectDataSourceDeleteParameters
,並叫用 ObjectDataSource 的 Delete()
方法。
當單擊 [編輯] 按鈕時,就會發生回傳,而數據會重新系結至 EditItemTemplate
,負責轉譯編輯介面。 這個介面包含用於編輯數據的 Web 控制項,以及 [更新] 和 [取消] 按鈕。 Visual Studio 所產生的預設值 EditItemTemplate
包含任何自動遞增欄位的 Label、ProductID
每個非布爾值欄位的 TextBox,以及每個布林值欄位的 CheckBox。 此行為與 GridView 和 DetailsView 控件中自動產生的 BoundFields 非常類似。
注意
FormView 自動產生 EditItemTemplate
的問題之一是,它會針對唯讀的字段轉譯 TextBox Web 控件,例如 CategoryName
和 SupplierName
。 我們很快就會看到如何說明這一點。
中的 EditItemTemplate
TextBox 控制件會使用雙向數據系結,將其 Text
屬性系結至其對應數據欄位的值。 雙向數據系結,以 <%# Bind("dataField") %>
表示,在將數據系結至範本時,以及在填入 ObjectDataSource 的參數以插入或編輯記錄時,執行數據系結。 也就是說,當使用者從 ItemTemplate
按兩下 [編輯] 按鈕時, Bind()
方法會傳回指定的數據域值。 當用戶進行變更並按兩下 [更新] 之後,會套用至 ObjectDataSource 的 UpdateParameters
對應至所指定Bind()
數據欄位的值。 或者,以<%# Eval("dataField") %>
表示的單向數據系結只會在將數據系結至範本時擷取數據域值,而不會在回傳時將使用者輸入的值傳回數據源的參數。
下列宣告式標記顯示 FormView 的 EditItemTemplate
。 請注意, Bind()
方法會用於這裏的數據系結語法,而且 Update 和 Cancel Button Web 控制件會據此設定其 CommandName
屬性。
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ProductID: <asp:Label ID="ProductIDLabel1" runat="server" Text="<%# Eval("ProductID") %>"></asp:Label><br /> ProductName: <asp:TextBox ID="ProductNameTextBox" runat="server" Text="<%# Bind("ProductName") %>"> </asp:TextBox><br /> SupplierID: <asp:TextBox ID="SupplierIDTextBox" runat="server" Text="<%# Bind("SupplierID") %>"> </asp:TextBox><br /> CategoryID: <asp:TextBox ID="CategoryIDTextBox" runat="server" Text="<%# Bind("CategoryID") %>"> </asp:TextBox><br /> QuantityPerUnit: <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text="<%# Bind("QuantityPerUnit") %>"> </asp:TextBox><br /> UnitPrice: <asp:TextBox ID="UnitPriceTextBox" runat="server" Text="<%# Bind("UnitPrice") %>"> </asp:TextBox><br /> UnitsInStock: <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text="<%# Bind("UnitsInStock") %>"> </asp:TextBox><br /> UnitsOnOrder: <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text="<%# Bind("UnitsOnOrder") %>"> </asp:TextBox><br /> ReorderLevel: <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text="<%# Bind("ReorderLevel") %>"> </asp:TextBox><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked="<%# Bind("Discontinued") %>" /><br /> CategoryName: <asp:TextBox ID="CategoryNameTextBox" runat="server" Text="<%# Bind("CategoryName") %>"> </asp:TextBox><br /> SupplierName: <asp:TextBox ID="SupplierNameTextBox" runat="server" Text="<%# Bind("SupplierName") %>"> </asp:TextBox><br /> <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update" Text="Update"> </asp:LinkButton> <asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel"> </asp:LinkButton> </EditItemTemplate> <InsertItemTemplate> ... </InsertItemTemplate> <ItemTemplate> ... </ItemTemplate> </asp:FormView>
EditItemTemplate
此時,如果我們嘗試使用它,就會擲回例外狀況。 問題是 和 CategoryName
SupplierName
欄位會在中 EditItemTemplate
轉譯為 TextBox Web 控制件。 我們要麼需要將這些 TextBox 變更為 [捲標],要麼將它們完全移除。 讓我們從完全 EditItemTemplate
刪除它們。
圖 23 顯示 Chai 按兩下 [編輯] 按鈕之後,瀏覽器中的 FormView。 請注意,SupplierName
中顯示的 ItemTemplate
和 CategoryName
字段已不存在,因為我們剛從 EditItemTemplate
中移除它們。 按兩下 [更新] 按鈕時,FormView 會繼續執行與 GridView 和 DetailsView 控制件相同的步驟序列。
圖 23:根據預設, EditItemTemplate
顯示每個可編輯的產品欄位為 TextBox 或 CheckBox (按兩下以檢視完整大小的影像)
按兩下 [插入] 按鈕時,FormView ItemTemplate
會隨之回傳。 不過,由於正在加入新記錄,因此不會繫結至 FormView。 介面 InsertItemTemplate
包含用於新增記錄以及 [插入] 和 [取消] 按鈕的 Web 控制件。 Visual Studio 所產生的預設值 InsertItemTemplate
包含每個非布爾值欄位的 TextBox,以及每個布林值欄位的 CheckBox,類似於自動產生的 EditItemTemplate
介面。 TextBox 控件的 Text
屬性會使用雙向數據系結,系結至其對應數據欄位的值。
下列宣告式標記顯示 FormView 的 InsertItemTemplate
。 請注意, Bind()
方法會用於這裏的數據系結語法,而且 [插入] 和 [取消] 按鈕 Web 控件會據此設定其 CommandName
屬性。
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ... </EditItemTemplate> <InsertItemTemplate> ProductName: <asp:TextBox ID="ProductNameTextBox" runat="server" Text="<%# Bind("ProductName") %>"> </asp:TextBox><br /> SupplierID: <asp:TextBox ID="SupplierIDTextBox" runat="server" Text="<%# Bind("SupplierID") %>"> </asp:TextBox><br /> CategoryID: <asp:TextBox ID="CategoryIDTextBox" runat="server" Text="<%# Bind("CategoryID") %>"> </asp:TextBox><br /> QuantityPerUnit: <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text="<%# Bind("QuantityPerUnit") %>"> </asp:TextBox><br /> UnitPrice: <asp:TextBox ID="UnitPriceTextBox" runat="server" Text="<%# Bind("UnitPrice") %>"> </asp:TextBox><br /> UnitsInStock: <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text="<%# Bind("UnitsInStock") %>"> </asp:TextBox><br /> UnitsOnOrder: <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text="<%# Bind("UnitsOnOrder") %>"> </asp:TextBox><br /> ReorderLevel: <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text="<%# Bind("ReorderLevel") %>"> </asp:TextBox><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked="<%# Bind("Discontinued") %>" /><br /> CategoryName: <asp:TextBox ID="CategoryNameTextBox" runat="server" Text="<%# Bind("CategoryName") %>"> </asp:TextBox><br /> SupplierName: <asp:TextBox ID="SupplierNameTextBox" runat="server" Text="<%# Bind("SupplierName") %>"> </asp:TextBox><br /> <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert" Text="Insert"> </asp:LinkButton> <asp:LinkButton ID="InsertCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel"> </asp:LinkButton> </InsertItemTemplate> <ItemTemplate> ... </ItemTemplate> </asp:FormView>
FormView 的自動產生 InsertItemTemplate
有一個微妙之處。 具體而言,即使針對唯讀的欄位,也會建立 TextBox Web 控制項,例如 CategoryName
和 SupplierName
。 和 一 EditItemTemplate
樣,我們需要從 InsertItemTemplate
中移除這些 TextBoxes。
圖 24 顯示新增產品 Acme Coffee 時瀏覽器中的 FormView。 請注意,SupplierName
中顯示的 ItemTemplate
和 CategoryName
欄位已不存在,因為我們剛移除它們。 按兩下 [插入] 按鈕時,FormView 會繼續執行與 DetailsView 控制件相同的步驟順序,並將新記錄新增至 Products
數據表。 圖 25 顯示插入 FormView 后 Acme Coffee 產品的詳細數據。
圖 24: InsertItemTemplate
指定 FormView 的插入介面 (按兩下以檢視完整大小的影像)
圖 25:New Product,Acme Coffee 的詳細數據會顯示在 FormView 中(單擊以檢視完整大小的影像)
透過將只讀、編輯和插入介面分隔成三個不同的範本,FormView 允許比 DetailsView 和 GridView 更精細地控制這些介面。
注意
如同 DetailsView,FormView 的 CurrentMode
屬性會指出要顯示的介面,而其 DefaultMode
屬性則表示 FormView 在編輯或插入完成之後所傳回的模式。
摘要
在本教學課程中,我們檢查了使用 GridView、DetailsView 和 FormView 插入、編輯和刪除數據的基本概念。 這三個控件都提供一些層級的內建數據修改功能,不需要在 ASP.NET 頁面中撰寫單行程式代碼,這要歸功於數據Web控件和 ObjectDataSource。 不過,簡單點和點選技術會呈現相當虛弱且天真的數據修改使用者介面。 若要提供驗證、插入程序設計值、妥善處理例外狀況、自定義使用者介面等等,我們必須依賴接下來幾個教學課程將討論的技術。
祝您程式設計愉快!
關於作者
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 找到) 與他聯繫。