原文還有兩個圖 在對ASP.NET Web表單的編程模型有了基本的認識后,通過應用于現(xiàn)實的開發(fā)案例來提高對ASP.NET Web表單內(nèi)在運作機制的了解,以及由此帶來的對系統(tǒng)架構(gòu)的掌控是很有必要的。我們沒有為編程而編程的高貴姿態(tài),我們深深懂得能夠開發(fā)出高效,健壯,強大的應用程序始終是編程的終極。我們下面通過一個完整的BToC電子商務系統(tǒng)的開發(fā)流程來展示ASP.NET Web表單是怎樣具體搭建面向下一代網(wǎng)絡平臺的。 這是一個典型的基于B/S(瀏覽器/服務器) 三層架構(gòu)的食品,飲料電子商務零售系統(tǒng)——“玉米地零食店”。前端為產(chǎn)品瀏覽器,為消費者提供瀏覽/選購商品,下訂單購物等各個環(huán)節(jié)的功能;中間層為銷售商的稅率,優(yōu)惠等商務邏輯;后端為與整個零售系統(tǒng)相關的產(chǎn)品,顧客,訂單等數(shù)據(jù)庫。我們采用ASP.NET+IIS 5來構(gòu)建前端和中間層,SQL Server 2000來管理后端數(shù)據(jù)庫,整個系統(tǒng)運行于Windows XP。相關硬件配置只要滿足上述軟件的基本配置,系統(tǒng)性能便可保證。下面為該網(wǎng)上零售系統(tǒng)的前端界面圖示:
在編制Web 表單商業(yè)前端和中間層之前,我們有必要對后端數(shù)據(jù)庫做一個簡單的介紹。后端數(shù)據(jù)庫 CornfieldGrocer 由4個表組成:產(chǎn)品類別表Categories ,產(chǎn)品細節(jié)表 Details ,產(chǎn)品表 Products ,客戶信息表Customers。考慮到演示系統(tǒng)的的簡潔性,我們沒有添加相關的存儲過程,視圖,規(guī)則等,這些在實際的系統(tǒng)的開發(fā)中對提高系統(tǒng)的性能是很有必要,尤其是在大數(shù)據(jù)量的情況下。下面為4個表的字段的圖示介紹:
各個表的字段的表義已經(jīng)相當清楚,我們不在這里贅述。我們下面向大家展示一下整個電子商務零售系統(tǒng)——“玉米地零食店”的物理文件組成及其結(jié)構(gòu),下圖為示意圖:
所有的文件位于ASP.NET站點目錄CornfieldGrocer下,其中還有Web表單頁面用到的圖片子目錄Images下的文件就不再在這里列出了。 下面我們開始編寫前端和中間層代碼,為了更清楚地展示W(wǎng)eb Form ASP.NET的底層代碼構(gòu)造,我們采用記事本來完成整個代碼的編寫過程。需要說明的是在真正的工程項目開發(fā)實踐中,如能借助Visual Studio.Net等可視集成開發(fā)工具,開發(fā)效率會大大提高。但在ASP.NET代碼的底層機制沒有諳熟的情況下,筆者強烈建議初期的開發(fā)學習不妨放在Windows系統(tǒng)自帶的“記事本”這一簡單卻能夠把代碼暴露得相當清晰的工具里。 由于篇幅有限,我們不可能將所有的代碼都在這里展示給大家。如前所述,web.config為每個站點級的基于XML的配置文件,負責一些ASP.NET的安全認證,編碼選擇,診斷測試等ASP.NET的配置工作,為瀏覽器請求ASP.NET Web表單時通過 IIS處理后的第一站。下面為其內(nèi)容:
<configuration> <system.web> <globalization requestEncoding="UTF-8" responseEncoding="UTF-8" /> </system.web> </configuration>
容易看到這里的配置內(nèi)容相當簡單,僅指定請求/發(fā)送的編碼為“UTF-8”。我們對此不再贅述。 global.asax文件及其由后端代碼文件global.asax.cs編譯成的Bin\CornfieldGrocer.dll共同組成該網(wǎng)上零售系統(tǒng)的ASP.NET應用程序定義。我們先來看文件global.asax:
<%@ Application Inherits="CornfieldGrocer.Global" %>
該文件只有一行指示符,它表示ASP.NET應用程序的定義繼承自Global類,而Global類正是在global.asax.cs文件中定義:
using System; using System.Collections; using System.ComponentModel; using System.Web; using System.Web.SessionState;
namespace CornfieldGrocer { public class Global : System.Web.HttpApplication { protected void Session_Start(Object sender, EventArgs e) { if (Session["ShoppingCart"] == null) { Session["ShoppingCart"] = new CornfieldGrocer.OrderList(); } } } }
在Global類里,我們定義了區(qū)段(Session)意義下的購物卡(ShoppingCart)——這里采用了C#中的索引器。購物卡的類型為CornfieldGrocer命名空間中的OrderList類,在CornfieldGrocer.cs文件中有定義。我們當然也可以在global.asax文件中用腳本語言的形式將上面兩個文件的內(nèi)容合并起來,但那不是ASP.NET推薦的做法,因為腳本語言的第一次執(zhí)行還要進行動態(tài)編譯,這回損失一部分性能,而將CS文件提前編譯成dll文件則會降低這種代價——當然這里的編譯的意思還是指將CS的源代碼文件編譯成微軟中間語言的過程。其次,頁面與后端代碼分離的原則易于項目管理,是Visual Studio.NET推薦的工程性的做法。 文件Default.aspx為整個網(wǎng)上零售系統(tǒng)的前端頁面HTML代碼,Default.aspx.cs為其后端控制Web表單行為的CS代碼。由于篇幅關系我們這里不再贅述其HTML代碼,實際上從前面給出的前端界面圖示,我們可以基本了解Default.aspx的HTML代碼結(jié)構(gòu)。Style.css文件為Default.aspx文件的頁面樣式定義文件,定義一些頁面元素的顏色,格式,間距等修飾性的東西,我們也不再多言。下面只向大家展示Default.aspx的頁面指示符:
<%@ AutoEventWireup="false" Inherits="CornfieldGrocer.MainForm" %>
我們用“Inherits="CornfieldGrocer.MainForm"”來表示我們的頁面繼承自MainForm類,這樣我們就實現(xiàn)了對ASP.NET Web 表單行為的控制代碼與頁面顯示的HTML的分離。其中“AutoEventWireup="false"”表示頁面事件非自動使能——頁面事件非自動使能的意思是所有頁面事件必須經(jīng)過用戶明確的操作才能觸發(fā),由于該屬性缺省為“true”表示自動使能,但我們的商業(yè)邏輯要求非自動使能,故這里的語句很有必要,否則會引起系統(tǒng)處理的混亂。下面我們來看MainForm類:
using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls;
namespace CornfieldGrocer { public class MainForm: System.Web.UI.Page { protected System.Web.UI.WebControls.Label CurrentCategory; protected System.Web.UI.WebControls.Label Name; protected System.Web.UI.WebControls.Label SubTotal; protected System.Web.UI.WebControls.ImageButton Imagebutton1; protected System.Web.UI.WebControls.Label Description; protected System.Web.UI.WebControls.Label Company; protected System.Web.UI.WebControls.Repeater DetailsListing; protected System.Web.UI.WebControls.DataList ProductListing; protected System.Web.UI.WebControls.DataList ShoppingCartList; protected System.Web.UI.HtmlControls.HtmlSelect CategoryList; protected System.Web.UI.WebControls.Button btnSelect; protected System.Web.UI.WebControls.Label Tax; protected System.Web.UI.WebControls.Label Total; protected System.Web.UI.WebControls.ImageButton Imagebutton4; protected System.Web.UI.WebControls.ImageButton Imagebutton5; protected System.Web.UI.WebControls.ImageButton Imagebutton6; protected System.Web.UI.HtmlControls.HtmlInputText Qty; protected System.Web.UI.HtmlControls.HtmlGenericControl CheckoutPanel; protected System.Web.UI.HtmlControls.HtmlImage SelectedProdPicture;
public MainForm() { Page.Init += new System.EventHandler(Page_Init); } private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { ProductListing.SelectedIndex = 0;
UpdateProducts(); UpdateShoppingCart(); } } private void Page_Init(object sender, EventArgs e) { InitializeComponent(); } private void InitializeComponent() { this.btnSelect.Click += new System.EventHandler(this.CategoryList_Select); this.ProductListing.SelectedIndexChanged+= new System.EventHandler(this.ProductListing_Select); this.Imagebutton1.Click+= new ImageClickEventHandler(this.AddBtn_Click); this.Imagebutton4.Click+= new ImageClickEventHandler(this.Recalculate_Click); this.Imagebutton6.Click+= new ImageClickEventHandler(this.ClearCart_Click); this.Load += new System.EventHandler(this.Page_Load); } private void CategoryList_Select(Object sender, EventArgs e) { CurrentCategory.Text = CategoryList.Items[CategoryList.SelectedIndex].Text; UpdateProducts(); } private void ProductListing_Select(Object sender, EventArgs e) { UpdateProducts(); } private void AddBtn_Click(Object sender, ImageClickEventArgs e) { int productID = Int32.Parse (ProductListing.DataKeys[ProductListing.SelectedIndex].ToString());
InventoryDB market = new InventoryDB(); DataRow product = market.GetProduct(productID);
CornfieldGrocer.OrderList shoppingCart = ((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
shoppingCart.Add(new CornfieldGrocer.OrderItem(productID, (String) product["ProductName"], Double.Parse(product["UnitPrice"].ToString()), 1));
UpdateShoppingCart(); } private void Recalculate_Click(Object sender, ImageClickEventArgs e) { CornfieldGrocer.OrderList shoppingCart = ((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
for (int i=0; i<ShoppingCartList.Items.Count; i++) > { HtmlInputText qty = (HtmlInputText) ShoppingCartList.Items[i].FindControl("Qty"); try { shoppingCart[(String) ShoppingCartList.DataKeys][i]].Quantity = Int32.Parse(qty.Value); } catch (Exception) { } } UpdateShoppingCart(); } private void ClearCart_Click(Object sender, ImageClickEventArgs e) { CornfieldGrocer.OrderList shoppingCart = ((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
shoppingCart.ClearCart(); UpdateShoppingCart(); } void UpdateProducts() { InventoryDB market = new InventoryDB();
int categoryID = Int32.Parse (CategoryList.Items[CategoryList.SelectedIndex].Value); ProductListing.DataSource = market.GetProducts(categoryID).DefaultView; ProductListing.DataBind();
int productID = Int32.Parse (ProductListing.DataKeys[ProductListing.SelectedIndex].ToString()); DataRow product = market.GetProduct(productID);
Name.Text = product["ProductName"].ToString(); SelectedProdPicture.Src = product["ImagePath"].ToString(); Description.Text = product["ProductDescription"].ToString(); Company.Text = product["Manufacturer"].ToString();
DetailsListing.DataSource = market.GetProductCalories(productID).DefaultView; DetailsListing.DataBind(); } void UpdateShoppingCart() { CornfieldGrocer.OrderList shoppingCart = ((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
SubTotal.Text = String.Format("{0:C}", shoppingCart.SubTotal); Tax.Text = String.Format("{0:C}", shoppingCart.Tax); Total.Text = String.Format("{0:C}", shoppingCart.Total);
ShoppingCartList.DataSource=shoppingCart.Values; ShoppingCartList.DataBind(); } } } MainForm類中共有11個方法,19個保護域。其中的19個保護域和前面給出的前端界面圖示的頁面元素相對應,這里不再贅述。11個方法中MainForm()為構(gòu)建器,其添加了頁面初始化事件Page_Init(),這是ASP.NET Web表單最先處理的事件,一般進行一些基礎的初始化操作。我們可以看到在Page_Init()中進行了初始化組件InitializeComponent()的操作。Page_Load()事件出現(xiàn)在用戶發(fā)出請求后,頁面裝載的時候,在這里一般可做一些商業(yè)邏輯初始化方面的操作,比如數(shù)據(jù)庫的連接,購物卡的初始化等。我們這里進行了產(chǎn)品展示UpdateProducts()和購物卡的初始化UpdateShoppingCart()的操作。 其他四個方法分別為產(chǎn)品類別的選擇ProductListing_Select(),購買產(chǎn)品的添加AddBtn_Click(),購物卡的重新計算Recalculate_Click(),購物卡的清除ClearCart_Click()都是通過對ASP.NET控件的操作來觸發(fā)相應的事件完成商業(yè)邏輯。上面的代碼已經(jīng)展示的相當清楚,我們不再贅述。 最后我們要向大家說明的是中間層商務邏輯的組件。它由三個類構(gòu)成:庫存數(shù)據(jù)類InventoryDB,訂單項目類OrderItem和訂單列表類OrderList。它們共同在文件CornfieldGrocer.cs文件中定義。自解釋的編程方式已經(jīng)它們的結(jié)構(gòu)展示的相當清除,我們下面只給出該文件的CS源代碼:
using System; using System.Data; using System.Data.SqlClient; using System.Collections;
namespace CornfieldGrocer { public class InventoryDB { public DataTable GetProducts(int categoryID) { SqlConnection sqlConnect= new SqlConnection ("server=(local);database=CornfieldGrocer;Trusted_Connection=yes"); SqlDataAdapter sqlAdapter1 = new SqlDataAdapter ("Select * from Products where categoryid="+categoryID,sqlConnect); DataSet products = new DataSet(); sqlAdapter1.Fill(products, "products"); return products.Tables[0]; } public DataRow GetProduct(int productID) { SqlConnection sqlConnect= new SqlConnection ("server=(local);database=CornfieldGrocer;Trusted_Connection=yes"); SqlDataAdapter sqlAdapter1 = new SqlDataAdapter ("Select * from Products where productID=" + productID, sqlConnect); DataSet product = new DataSet(); sqlAdapter1.Fill(product, "product"); return product.Tables[0].Rows[0]; } public DataTable GetProductCalories(int productID) { SqlConnection sqlConnect = new SqlConnection ("server=(local);database=CornfieldGrocer;Trusted_Connection=yes"); SqlDataAdapter sqlAdapter1 = new SqlDataAdapter ("Select * from Details where productID="+productID,sqlConnect); DataSet details = new DataSet(); sqlAdapter1.Fill(details, "details"); return details.Tables[0]; } }
public class OrderItem { public int productID; public int quantity; public String name; public double price; public OrderItem(int productID, String name, double price, int quantity) { this.productID = productID; this.quantity = quantity; this.name = name; this.price = price; } public int ProductID { get { return ProductID; } } public int Quantity { get { return quantity; } set { quantity=value; } } public String Name { get { return name; } } public double Price { get { return price; } } public double Total { get { return quantity * price; } } }
public class OrderList { private Hashtable orders = new Hashtable(); private double taxRate = 0.08; public double SubTotal { get { if (orders.Count == 0) return 0.0; double subTotal = 0; IEnumerator items = orders.Values.GetEnumerator(); while(items.MoveNext()) { subTotal += ((OrderItem) items.Current).Price * ((OrderItem) items.Current).Quantity; } return subTotal; } } public double TaxRate { get { return taxRate; } set { taxRate = value; } } public double Tax { get { return SubTotal * taxRate; } } public double Total { get { return SubTotal * (1 + taxRate); } } public ICollection Values { get { return orders.Values; } } public OrderItem this[String name] { get { return (OrderItem) orders[name]; } } public void Add(OrderItem value) { if (orders[value.Name] == null) { orders.Add(value.Name, value); } else { OrderItem oI = (OrderItem)orders[value.Name]; oI.Quantity = oI.Quantity + 1; } } public void ClearCart() { orders.Clear(); } } }
需要說明的是我們將三個文件CornfieldGrocer.cs,Default.aspx.cs,Global.asax.cs用編譯命令“csc /t:library /out:CornfieldGrocer.dll cornfieldgrocer.cs default.aspx.cs global.asax.cs”將它們?nèi)糠庋b在CornfieldGrocer命名空間里,雖然這并不是必須的。上面的編譯器輸出CornfieldGrocer.dll文件,我們配置該網(wǎng)上零售站點時只需將該文件拷貝到站點根目錄中的Bin目錄下即可。 到此為止,我們已經(jīng)完整的向大家展示了利用ASP.NET Web表單建立一個小型的網(wǎng)上交易系統(tǒng)的編碼,配置等工作。當然作為演示案例,它還沒有真正系統(tǒng)的完善的性能,安全,界面等各個方面的優(yōu)化考慮和設計。但它向我們展示的ASP.NET Web表單模型卻非常典型且底層,大家不防在此基礎上通過不斷的修改和擴充來開發(fā)適合自己的交易系統(tǒng)。比如對于Default.aspx文件中AutoEventWireup="false"如果設置為“true”或去掉這個語句,在運行頁面時會出現(xiàn)什么情況?通過這些練習便會不斷的加深我們對ASP.NET底層的認識,最后達到游刃有余的把握。實際上技術的學習,尤其是編程,除了一定的興趣和悟性外,大量代碼實例的鍛煉也是很有必要的,這本身就是筆者成長的一個過程,也是本文中筆者竭力要給大家展示的。
|