導言:
在前面2節教程,我們探討了如何使用FileUpload控件從客戶端向服務器上傳文件,以及如何在數據Web控件里顯示二進制數據。
在本節,我們將創建一個web頁面以添加新的種類。除了為類的name和description屬性添加TextBoxes控件外,我們還要在頁面上添加2個FileUpload控件——一個用來上傳新類的圖片,另一個用來上傳類的小說明冊子。上傳的圖片將直接存儲在新記錄的Picture列。與此相反,小冊子將存儲在~/Brochures 文件夾,同時將文件路徑存儲在新記錄的BrochurePath列。
在創建頁面之前,我們需要更新體系結構。由于CategoriesTableAdapter的主查詢并不返回Picture列,因此自動生產的Insert方法只包含了CategoryName, Description和BrochurePath列。我們需要在TableAdapter里創建新的方法以包括Categories的4個列。同時業務邏輯層的的CategoriesBLL類也需要更新。
第1步:在CategoriesTableAdapter添加一個InsertWithPicture方法
在前面的教程《創建一個數據訪問層》里我們創建了CategoriesTableAdapter,并設置其自動生成了基于主查詢的INSERT, UPDATE和DELETE命令。此外,我們設置該TableAdapter啟用DB Direct方法,它將創建Insert, Update和Delete方法。這些方法執行自動生成的INSERT, UPDATE和DELETE命令,自然而然的,其接受的輸入參數基于主查詢所返回的那些列。在教程《使用FileUpload上傳文件》里,我們擴展了 CategoriesTableAdapter的主查詢以包含BrochurePath列。
因為CategoriesTableAdapter的主查詢并為引用Picture,在添加新記錄或更新記錄時不能涉及Picture值。為了獲取Picture信息,我們要么在TableAdapter里創建一個新方法以插入Picture的二進制數據;要么定制自動生成的INSERT命令。但定制自動生成的INSERT命令有一個風險,即定制的INSERT命令有可能被向導覆蓋。比如,假設我們定制INSERT命令使用Picture列,更新TableAdapter的Insert方法,使之多包含一個對應picture二進制數據的參數。然后在業務邏輯層創建一個方法使用該 DAL方法,再在表現層調用該業務邏輯層方法。現在一切工作正常,但當下一次在TableAdapter設置向導里設置TableAdapter完成后,我們定制的INSERT命令馬上就會被向導重寫,回歸到定制前的狀態。其結果是我們的代碼將無法編譯!
注意:如果使用存儲過程而不用SQL語句的話,就不存在這個問題。在以后的教程里,我們將探討在數據訪問層用存儲過程替代SQL語句。
為避免這個頭痛的問題,我們為TableAdapter添加新的方法,而不定制自動生成的SQL命令。我們為添加的方法命名為InsertWithPicture,它接受 CategoryName, Description, BrochurePath和Picture值;執行INSERT命令將上述值添加進一條記錄。
在CategoriesTableAdapter的頂部點右鍵,選擇“添加查詢”。進入TableAdapter 查詢設置向導,首先詢問我們TableAdapter查詢如何訪問數據庫,選擇“使用SQL語句”,點Next,因為我們要為表Categories添加新記錄,選“INSERT”,點Next。
圖1:選“INSERT”選項
現在,我們需要指定INSERT SQL語句。向導自動地生成一個基于主查詢的INSERT語句。此時,它只插入CategoryName, Description和BrochurePath值。對其更新,包括Picture列和參數@Picture ,如下:
1
2
3
4
|
INSERT INTO [Categories] ([CategoryName], [Description], [BrochurePath], [Picture]) VALUES (@CategoryName, @Description, @BrochurePath, @Picture) |
最后,向導要我們為方法命名,取名為InsertWithPicture,點Finish。
圖2:為新方法命名為InsertWithPicture
第2步:更新業務邏輯層
由于一般來說表現層將引用業務邏輯層,而不是繞過它直接引用數據訪問層,我們需要創建一個業務邏輯層方法,以調用剛才創建的數據訪問層方法(InsertWithPicture),本節,我們在CategoriesBLL里創建一個名為InsertWithPicture方法,它接受3個字符串和一個byte數組,字符串參數對應name, description和brochure文件地址;byte數組對應于圖片的二進制內容。就像下面的代碼所顯示的那樣,BLL方法調用相應DAL方法:
1
2
3
4
5
6
7
|
[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Insert, false )] public void InsertWithPicture( string categoryName, string description, string brochurePath, byte [] picture) { Adapter.InsertWithPicture(categoryName, description, brochurePath, picture); } |
注意:在為BLL添加InsertWithPicture方法前,確保已經保存了數據集(Typed DataSet ),因為CategoriesTableAdapter類的代碼是基于Typed DataSet自動生成的。如果最開始沒有把對Typed DataSet所進行的修改保存的話,Adapter屬性將不認同InsertWithPicture方法。
第3步:列出現有的種類及其二進制數據
本教程我們將創建一個頁面,允許用戶添加新的類,包含其圖片和說明小冊子。在上一節,我們用一個包含TemplateField和ImageField的GridView控件來展示每個類的名稱、描述,并包含一個下載說明小冊子的鏈接。在本教程,我們實現相同的功能,創建一個頁面,即展示現有的類,還可以添加新的類。
打開BinaryData文件夾的DisplayOrDownload.aspx頁面,切換到源模式,復制GridView和ObjectDataSource控件的聲明代碼,粘貼在UploadInDetailsView.aspx頁面的<asp:Content>元素里。同時不要忘記將后臺代碼類的GenerateBrochureLink方法拷貝到UploadInDetailsView.aspx的后臺代碼類里。
圖3:將DisplayOrDownload.aspx頁面的聲明代碼拷貝到頁面UploadInDetailsView.aspx
完成以后,在瀏覽器里查看該頁面,確保一切正常。GridView控件里列出了8個類,每個類包含一張圖片以及一個下載說明小冊子的鏈接。
圖4:你應該看到每個類及其相應二進制數據
第4步:設置CategoriesDataSource以支持添加功能
那個ID為Categories的GridView控件所使用的名為CategoriesDataSource的 ObjectDataSource控件目前還不支持添加數據。為實現該功能,我們要設置該控件的Insert方法引用類CategoriesBLL的某個方法。具體的講,我們要用到在第2步里添加的InsertWithPicture方法。
在ObjectDataSource控件的智能標簽里,點“設置數據源”。照原樣一直點到“Define Data Methods”界面。再點INSERT標簽,從下拉列表里選方法“InsertWithPicture”,點Finish完成設置。
圖5:設置ObjectDataSource控件使用InsertWithPicture方法
注意:當完成設置后,Visual Studio會問你是否“刷新Fields and Keys”,選擇No,因為如果選Yes的話,將重新構造data Web controls fields,那樣將重寫所有我們已經定制好的列(field)。
完成設置后,ObjectDataSource控件將會為InsertMethod屬性賦值,同時包含一個<InsertParameters>,如下面的聲明代碼所示:
1
2
3
4
5
6
7
8
9
10
|
<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步:創建一個插入界面
在教程16《概述插入、更新和刪除數據》里我們談到,當DetailsView控件的數據源控件支持添加功能時,便可以啟用DetailsView內置的添加界面。讓我們在頁面上添加一個DetailsView控件,置于GridView控件之上,并處于添加模式。當在DetailsView控件里添加一個新種類時,其下的GridView控件將自動發生刷新,并將剛添加的類顯示出來。
從工具箱拖一個DetailsView控件到頁面,置于GridView之上,設其ID為NewCategory,清空其Height和Width屬性。 其智能標簽里,設置它綁定到名為CategoriesDataSource的數據源,并啟用“插入”功能。
圖6:將DetailsView控件綁定到CategoriesDataSource,并啟用插入功能。
為使DetailsView呈現為插入界面,設其DefaultMode屬性為Insert
我們注意到,盡管DetailsView控件有5個BoundFields——CategoryID, CategoryName, Description, NumberOfProducts和BrochurePath,但插入界面并不包含CategoryID,因為CategoryID列的InsertVisible屬性為false。為什么會顯示這4個列呢?因為ObjectDataSource調用的GetCategories()方法返回的就是這些列。當添加新類時,我們不希望用戶為NumberOfProducts列指定值,此外,我們還希望讓用戶為新類上傳圖片和相關的PDF小冊子。
在DetailsView里將NumberOfProducts列完成刪除,再分別CategoryName列和BrochurePath列的HeaderText屬性設置為“Category”和“Brochure”。將BrochurePath 轉換為TemplateField,再添加一個TemplateField,設其HeaderText屬性為“Picture”,把它放置在BrochurePath列和CommandField列之間。
圖7:將DetailsView控件綁定到CategoriesDataSource,并啟用插入功能(注:圖片說明有誤)
當你在“編輯列”對話框里將BrochurePath BoundField 轉換為一個TemplateField后,該TemplateField將包含3個模板:ItemTemplate,EditItemTemplate和InsertItemTemplate,由于我們只需要InsertItemTemplate模板,將另外2個模板刪除。如此,你的DetailsView控件的聲明代碼看起來應該像下面的這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<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> |
為Brochure和Picture Fields添加FileUpload控件
當前,BrochurePath TemplateField的InsertItemTemplate模板包含一個TextBox,而Picture TemplateField并不包含任何的模板,我們為這2個TemplateField的InsertItemTemplate模板模板添加FileUpload控件。
從DetailsView控件的智能標簽選擇“編輯模板”,從下拉列表選擇BrochurePath TemplateField的InsertItemTemplate模板,將模板里的TextBox刪除,從工具箱拖一個FileUpload控件到頁面,設其ID為BrochureUpload。類似的,為Picture TemplateField的InsertItemTemplate模板添加一個ID為PictureUpload的FileUpload控件。
圖8:在InsertItemTemplate模板里添加一個FileUpload控件
完成添加后,這2個TemplateField的聲明代碼應該和下面的差不多:
1
2
3
4
5
6
7
8
9
10
|
<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類型;對圖片,我們需要用戶上傳一個image文件。那是不是image文件必須是某個特定的類型呢,比如GIF或JPG?考慮到其它不同類型的文件,我們需要擴展表Categories的列以包含這些類型的文件,同時我們可以在頁面DisplayCategoryPicture.aspx里通過Response.ContentType將這些文件發送到客戶端。由于表Categories現在并沒有這樣的列,我們只有限制用戶上傳指定為某種類型的image文件。表Categories里現有的images為位圖,不過使用JPG類型或許更恰當。
當用戶上傳的文件類型不正確時,我們將取消插入操作,并顯示一個提示信息。在DetailsView控件下添加一個Label Web控件,設ID為UploadWarning,清除Text屬性,設CssClass屬性為“Warning”, 再將Visible和EnableViewState屬性都設為false。Warning CSS定義在Styles.css里,作用是將文字顯示為粗斜體,紅色大號字。
注意:最理想的情況是將CategoryName和Description BoundFields都轉換為TemplateFields,達到定制插入界面的目的。比如,對Description插入界面來說,使用一個允許分行的文本框或許更好;對CategoryName插入界面,因為CategoryName不允許為NULL值,我們應該添加一個RequiredFieldValidator控件,以確保輸入類的名稱。這些步驟都留給讀者做練習,更深入的探討請參考前面的教程之20《定制數據修改界面》
第6步:將上傳的小冊子保存在服務器的文件系統
但用戶鍵入相關的類別信息,點Insert按鈕后,發生頁面回傳,接著發生一連串的插入流程。首先,DetailsView控件的ItemInserting event事件發生;接著,調用ObjectDataSource控件的Insert()方法,它將導致Categories表添加新記錄;最后,發生DetailsView控件的ItemInserted event事件。
在調用ObjectDataSource控件的Insert()方法以前,我們必須確保用戶已經上傳了恰當的文件并保存在服務器的文件系統。為此,我們為DetailsView控件的ItemInserting事件創建一個事件處理器,添加如下的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Reference the FileUpload control FileUpload BrochureUpload = (FileUpload)NewCategory.FindControl( "BrochureUpload" ); 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 ; e.Cancel = true ; return ; } } |
代碼首先引用DetailsView控件模板里名為BrochureUpload的FileUpload控件,如果已經上傳了文件,就檢查FileUpload控件的extension是否為“.PDF”, 如果不是則取消插入操作并退出。
注意:通過檢查文件的擴展名(extension)來確保用戶上傳的為PDF文件的做法并不是萬全之策。比如,可能用戶的確上傳的是PDF文件,只不過其擴展名為.Brochure;或者用戶提供的并不是PDF文件,卻使用.pdf的擴展名。保險的做法是通過編程對文件內容做最后一次檢查。如此一來,雖然徹底,但稍嫌過頭(overkill)。在絕大多數情況下,檢查文件擴展名就已經足夠了。
就像在教程《使用FileUpload上傳文件》里討論的那樣,將文件保存在文件系統里時要特別小心,以免覆蓋別人上傳的文件。本節,我們嘗試對上傳文件使用一個已經使用的名字,在名字末尾添加一個數字,以示區別。舉例,如果在文件夾~/Brochures里存在一個名為Meats.pdf的文件,上傳文件時我們取名為Meats-1.pdf,如果文件夾里恰好也存在一個Meats-1.pdf文件,我們就取名為Meats-2.pdf,以此類推,直到文件名唯一為止。
下面的代碼使用File.Exists(path)方法來判斷是否已經存在同名文件,如果存在,就重新命名,直到名字唯一為止:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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++; } |
一旦找到唯一的文件名后,立即將文件保存在文件系統,同時更新ObjectDataSource控件的InsertParameter參數brochurePath的值,以便將文件名寫入數據庫。就像在教程《使用FileUpload上傳文件》里看到的一樣,可以使用FileUpload控件的SaveAs(path)方法來保存文件。使用e.Values集合來更新ObjectDataSource控件的參數brochurePath。
1
2
3
|
// Save the file to disk and set the value of the brochurePath parameter BrochureUpload.SaveAs(Server.MapPath(brochurePath)); e.Values[ "brochurePath" ] = brochurePath; |
第7步:將上傳的圖片保存到數據庫
為了把上傳的圖片保存在新添加的記錄里,我們需要在DetailsView控件的ItemInserting事件里,用上傳的數據對ObjectDataSource控件的picture參數賦值。然而,在此之前,我們需要確保上傳的文件為JPG而不是其它的什么格式。就象在第6步中探討的一樣,我們用文件的擴展名來檢查其類型。
雖然Categories表允許Picture列為NULL值,但所有的種類都應該有一張圖片。在本頁面,我們強制用戶添加記錄時提供圖片。下面的代碼確保已經上傳圖片,且為恰當的類型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// Reference the FileUpload controls FileUpload PictureUpload = (FileUpload)NewCategory.FindControl( "PictureUpload" ); if (PictureUpload.HasFile) { // 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 ; e.Cancel = true ; return ; } } else { // No picture uploaded! UploadWarning.Text = "You must provide a picture for the new category." ; UploadWarning.Visible = true ; e.Cancel = true ; return ; } |
這些代碼應放在第6步中的代碼前面,如果上傳的文件有問題,事件處理器在文件保存到文件系統前就結束了。
假設上傳的文件沒有問題,然后我們用下面的代碼將上傳文件的數據分配給參數picture:
1
2
|
// Set the value of the picture parameter e.Values[ "picture" ] = PictureUpload.FileBytes; |
完整的ItemInserting事件處理器
下面是ItemInserting事件處理器的完整代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
protected void NewCategory_ItemInserting( object sender, DetailsViewInsertEventArgs e) { // Reference the FileUpload controls FileUpload PictureUpload = (FileUpload)NewCategory.FindControl( "PictureUpload" ); if (PictureUpload.HasFile) { // 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 ; e.Cancel = true ; return ; } } else { // No picture uploaded! UploadWarning.Text = "You must provide a picture for the new category." ; UploadWarning.Visible = true ; e.Cancel = true ; return ; } // Set the value of the picture parameter e.Values[ "picture" ] = PictureUpload.FileBytes; // Reference the FileUpload controls FileUpload BrochureUpload = (FileUpload)NewCategory.FindControl( "BrochureUpload" ); 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 ; e.Cancel = true ; return ; } 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)); e.Values[ "brochurePath" ] = brochurePath; } } |
第8步:更新DisplayCategoryPicture.aspx頁面
讓我們花幾分鐘測試我們在上幾步創建的插入界面和ItemInserting事件處理器。在瀏覽器查看UploadInDetailsView.aspx頁面 ,嘗試添加一個類,忽略picture或指定一個非JPG的圖片或非PDF的小冊子。以上任何一種情況下,都會顯示一個錯誤信息,并取消插入操作。
圖9:當上傳的文件不對時將顯示一個警告信息
確認頁面要求上傳一張圖片,且不接受非PDF或非JPG文件。添加一個包含JPG格式圖片的新類別,將Brochure列置空,點擊Insert按鈕后,頁面回傳,將為Categories表添加一個新記錄,同時上傳的圖片數據直接存儲進數據庫。GridView控件更新后,將新添加的類顯示出來。但是,就像圖10所示的那樣,類的圖片沒有正確的顯示出來。
圖10:新類的圖片沒有顯示出來
圖片沒有顯示出來的原因是因為用來返回特定類的圖片的頁面DisplayCategoryPicture.aspx被設置為處理帶OLE報頭的位圖。當Picture列的數據被返回到客戶端前已經把那78字節的報頭剝離掉。而且上傳的JPG文件并沒有OLE報頭,因此,必需的字節已經從圖片的二進制數據移除了。
由于現在表Categories里既有JPG文件又有帶OLE報頭的位圖,我們需要對頁面DisplayCategoryPicture.aspx做調整,使它對原來的8個類剝離OLE報頭,而不對新添加的類進行剝離。在后面的教程,我們探討如何更新現有記錄的image文件,并將所有以前的類的圖片調整為JPG格式。現在,我們在頁面DisplayCategoryPicture.aspx 里用下面的代碼將原來的8個類的OLE報頭剝離。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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]; if (categoryID <= 8) { // For older categories, we must strip the OLE header... images are bitmaps // 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 int OleHeaderLength = 78; int strippedImageLength = category.Picture.Length - OleHeaderLength; byte [] strippedImageData = new byte [strippedImageLength]; 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); } } |
做了上述修改后,JPG圖片現在可以正確的在GridView控件顯示出來了。
圖11:新添加的類的JPG圖片可以正確顯示了
第9步:出現異常時刪除Brochure文件
將上傳文件保存在文件系統還面臨一個問題,即無法將數據與存儲模式關聯起來。當刪除一條記錄時,存儲在文件系統的相應文件也應該刪除;類似地,添加記錄時,亦然。假定這些情況:當一個用戶添加一個新的種類時,他指定了一張圖片和一份說明小冊子。點擊Insert按鈕后,引發頁面回傳,發生DetailsView控件的ItemInserting事件,將文件保存到服務器文件系統;接下來,ObjectDataSource控件的Insert()方法調用CategoriesBLL類的InsertWithPicture方法,它又調用CategoriesTableAdapter的InsertWithPicture方法。
如果數據庫剛好處于離線狀態,或者INSERT SQL語句有錯誤,那又會怎么樣呢?毫無疑問添加記錄會失敗。最終結果是,向數據庫添加記錄失敗了,但卻成功地向服務器文件系統上傳了文件。當插入過程拋出異常時,應該將該文件刪除。
在教程18《在ASP.NET頁面中處理BLL/DAL層的異常》里,我們提到體系構架的不同層都可能拋出異常。在表現層,我們可以通過DetailsView控件的ItemInserted事件判斷是否發生了異常,同時提供ObjectDataSource控件的InsertParameters參數值。因此,我們為ItemInserted事件創建一個事件處理器,檢查是否拋出異常,如果是則刪除the ObjectDataSource控件的brochurePath參數指定的文件。
1
2
3
4
5
6
7
8
9
10
11
|
protected void NewCategory_ItemInserted ( object sender, DetailsViewInsertedEventArgs e) { if (e.Exception != null ) { // Need to delete brochure file, if it exists if (e.Values[ "brochurePath" ] != null ) System.IO.File.Delete(Server.MapPath( e.Values[ "brochurePath" ].ToString())); } } |
總結
我們要經過幾個步驟來創建一個基于web的添加界面,該界面允許添加記錄包含二進制數據。如果選擇直接存儲在數據庫,我們將對體系結構做一些調整,為了實現插入二進制數據,需要添加相應的方法;調整完體系結構下一步就需要創建一個添加界面,可以使用DetailsView控件,并定制其包含FileUpload控件。上傳的文件可以存儲在服務器的文件系統,或者在DetailsView控件的ItemInserting事件處理器里對一個數據源參數(data source parameter)賦值。
將數據保存在文件系統還需要注意選用一個命名體系,以避免一個用戶上傳的文件將另一個用戶上傳的文件覆蓋掉。另外,當向數據庫插入數據失敗時,必須將上傳的文件刪除掉。
現在我們可以向系統添加新的種類并附帶其圖片和說明小冊子。在下一章我們探討如何更新現有的類,以及當刪除一個類時如何正確的移除對應的二進制數據。
祝編程快樂!
作者簡介
本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用 微軟Web技術。希望對大家的學習ASP.NET有所幫助。