《解剖PetShop》连串之六

六 PetShop之表示层设计

表示层(Presentation
Layer)的筹划可以给系统客户最直白的感受和最十足的信念。正如人与人的交接相识一样,初次汇合的感觉总是永难忘怀的。一件交付给客户利用的产品,若是在用户界面(User
Interface,UI)上不够吸引人的性状,界面不和谐,操作不够敬服,即便那件产品质量尤其不错,架构设计合理,业务逻辑都满足了客户的需求,却照旧难以讨得客户的欢心。俗语云:“佛要金装,人要时装”,尤其是对于Web应用程序而言,Web网页就好比人的行装,代表着全套连串的地位与脸面,是揽客“顾客”的最大卖点。

“献丑不如藏拙”,作为艺术细胞缺乏的自个儿,并不打算在用户界面的美术设计上舍近求远,是以本书略过不提。本章所关切的表示层设计,如故以架构设计的角度,讲演在表示层设计中对格局的采纳,ASP.NET控件的统筹与利用,同时还包罗了对ASP.NET
2.0新特征的牵线。

6.1  MVC模式

表示层设计中最主要的方式是MVC(Model-View-Controller,即模型-视图-控制器)情势。MVC格局最早是由SmallTalk语言研讨团指出的,被广泛应用在用户交互应用程序中。Controller按照用户请求(Response)修改Model的质量,此时伊夫nt(事件)被触发,所有器重于Model的View对象会自动更新,并根据Model对象暴发一个响应(Response)信息,重回给Controller。马丁Fowler在《集团应用架构方式》一书中,显示了MVC方式应用的全经过,如图6-1所示: 

XML 1

图6-1 典型的MVC模式

设若将MVC格局拆解为八个独立的片段:Model、View、Controller,我们得以经过GOF设计方式来兑现和管理它们中间的关系。在系统架构设计中,业务逻辑层的天地对象以及数据访问层的数据值对象都属于MVC方式的Model对象。假若要治本Model与View之间的关联,可以应用Observer情势,View作为观望者,一旦Model的属性值发生变化,就会通报View基于Model的值举办立异。而Controller作为控制用户请求/响应的对象,则足以行使Mediator情势,专门负责请求/响应职分之间的调试。而对于View本身,在面向组件设计思想的根基上,大家经常将它安顿为组件只怕控件,那一个零部件或然控件依照自身特色的不等,共同构成一连串似于递归组合的对象协会,因此大家得以选拔Composite情势来布署View对象。

不过在.NET平台下,大家并不要求本身去落实MVC格局。对于View对象而言,ASP.NET已经提供了常用的Web控件,大家也得以透过一连System.Web.UI.UserControl,自定义用户控件,并采取ASPX页面组合Web控件来兑现视图。ASP.NET定义了System.Web.UI.Page类,它相当于MVC形式的Controller对象,可以处理用户的呼吁。由于应用了codebehind技术,使得用户界面的来得与UI完结逻辑完全分开,也即是说,View对象与Controller对象变成相对独立的两有些,从而便利代码的重用性。比较ASP而言,那种编程方式更契合开发人士的编程习惯,同时方便开发人士与UI设计人员的分工与合作。至于Model对象,则为业务逻辑层的园地对象。别的,.NET平台经过ADO.NET提供了DataSet对象,便于与Web控件的数据源绑定。

6.2  Page Controller方式的利用

综观PetShop的表示层设计,丰盛利用了ASP.NET的技艺特色,通过Web页面与用户控件控制和展现视图,并使用codebehind技术将事情逻辑层的圈子对象插手到表示层落成逻辑中,一个独立的Page
Controller形式绘身绘色。

Page Controller情势是Martin福勒在《公司应用架构形式》中最器重的表示层形式之一。在.NET平台下,Page
Controller格局的已毕至极简单,以Products.aspx页面为例。首先在aspx页面中,举办如下的安装:

XML 2<%@ Page AutoEventWireup=”true” Language=”C#” MasterPageFile=”~/MasterPage.master” Title=”Products” Inherits=”PetShop.Web.Products” CodeFile=”~/Products.aspx.cs” %>

Aspx页面继承自System.Web.UI.Page类。Page类对象通过持续System.Web.UI.Control类,从而具有了Web控件的脾性,同时它还实现了IHttpHandler接口。作为ASP.NET处理HTTP
Web请求的接口,提供了之类的定义:

XML 3[AspNetHostingPermission(SecurityAction.InheritanceDemand, 
XML 4Level=AspNetHostingPermissionLevel.Minimal), 
XML 5AspNetHostingPermission(SecurityAction.LinkDemand, 
XML 6Level=AspNetHostingPermissionLevel.Minimal)]
XML 7public interface IHttpHandler
XML 8XML 9XML 10{
XML 11      void ProcessRequest(HttpContext context);
XML 12XML 13      bool IsReusable XML 14{ get; }
XML 15}
XML 16

Page类达成了ProcessRequest()方法,通过它可以设置Page对象的Request和Response属性,从而达成对用户请求/相应的决定。然后Page类通过从Control类继承来的Load事件,将View与Model建立关联,如Products.aspx.cs所示:

XML 17public partial class Products : System.Web.UI.Page 
XML 18XML 19XML 20{
XML 21    protected void Page_Load(object sender, EventArgs e) 
XML 22XML 23    XML 24{
XML 25        //get page header and title
XML 26        Page.Title = WebUtility.GetCategoryName(Request.QueryString[“categoryId”]);
XML 27    }
XML 28}
XML 29

事件机制恰好是observer格局的落实,当ASPX页面的Load事件被点燃后,系统通过WebUtility类(在第28章中有对WebUtility类的详尽介绍)的GetCategoryName()方法,拿到Category值,并将其出示在页面的Title上。Page对象作为Controller,就好似一个调停者,用于协调View与Model之间的关联。

出于ASPX页面中仍可以包罗Web控件,这个控件对象同样是当做View对象,通过Page类型对象达成对它们的决定。例如在CheckOut.aspx页面中,当用户爆发CheckOut的呼吁后,作为System.Web.UI.WebControls.Winzard控件类型的wzdCheckOut,会在任何向导进程截止时,触发FinishButtonClick事件,并在该事件中调用领域对象Order的Insert()方法,如下所示:

XML 30public partial class CheckOut : System.Web.UI.PageXML 31
XML 32
XML 33XML 34    protected void wzdCheckOut_FinishButtonClick(object sender, WizardNavigationEventArgs e) XML 35{
XML 36XML 37        if (Profile.ShoppingCart.CartItems.Count > 0) XML 38{
XML 39XML 40            if (Profile.ShoppingCart.Count > 0) XML 41{
XML 42
XML 43                // display ordered items
XML 44                CartListOrdered.Bind(Profile.ShoppingCart.CartItems);
XML 45
XML 46                // display total and credit card information
XML 47                ltlTotalComplete.Text = ltlTotal.Text;
XML 48                ltlCreditCardComplete.Text = ltlCreditCard.Text;
XML 49
XML 50                // create order
XML 51                OrderInfo order = new OrderInfo(int.MinValue, DateTime.Now, User.Identity.Name, GetCreditCardInfo(), billingForm.Address, shippingForm.Address, Profile.ShoppingCart.Total, Profile.ShoppingCart.GetOrderLineItems(), null);
XML 52
XML 53                // insert
XML 54                Order newOrder = new Order();
XML 55                newOrder.Insert(order);
XML 56
XML 57                // destroy cart
XML 58                Profile.ShoppingCart.Clear();
XML 59                Profile.Save();
XML 60            }
XML 61        }
XML 62XML 63        else XML 64{
XML 65            lblMsg.Text = “<p><br>Can not process the order. Your cart is empty.</p><p class=SignUpLabel><a class=linkNewUser href=Default.aspx>Continue shopping</a></p>”;
XML 66            wzdCheckOut.Visible = false;
XML 67        }
XML 68    }
XML 69
XML 70

在地方的一段代码中,万分独立地发挥了Model与View之间的关系。它经过得到控件的属性值,作为参数值传递给数据值对象OrderInfo,从而选拔页面上发出的订单新闻成立订单对象,然后再调用领域对象Order的Inser()方法将OrderInfo对象插入到数码表中。其它,它还对世界对象ShoppingCart的数目项作出判断,如若其值等于0,就在页面中突显UI提醒音讯。此时,View的情节决定了Model的值,而Model值反过来又决定了View的显示内容。

6.3  ASP.NET控件

ASP.NET控件是View对象最重大的组成部分,它充分利用了面向对象的安插性思想,通过包装与持续创设一个个控件对象,使得用户在付出Web页面时,可以重用这一个控件,甚至自定义自个儿的控件。在第8章中,我曾经介绍了.NET
Framework中控件的统筹思想,通过引入一种“复合方式”的Composite情势完毕了控件树。在ASP.NET控件中,System.Web.UI.Control就是那棵控件树的根,它定义了装有ASP.NET控件共有的性质、方法和事件,并负责管理和操纵控件的漫天实施生命周期。

Control基类并从未包罗UI的特定作用,假设急需提供与UI相关的艺术属性,就须求从System.Web.UI.WebControls.WebControl类派生。该类实际上也是Control类的子类,但它附加了诸如ForeColor、BackColor、Font等天性。

除去,还有一个首要的类是System.Web.UI.UserControl,即用户控件类,它同样是Control类的子类。我们得以自定义一些用户控件派生自UserControl,在Visual
Studio的Design环境下,大家可以透过拖动控件的不二法门将六序列型的控件组合成一个自定义用户控件,也得以在codebehind格局下,为自定义用户控件类添加新的性子和格局。

全方位ASP.NET控件类的层次结构如图6-2所示: 

XML 71

图6-2 ASP.NET控件类的层次结构

ASP.NET控件的推行生命周期如表6-1所示:

阶段

控件需要执行的操作
要重写的方法或事件
初始化 初始化在传入 Web 请求生命周期内所需的设置。 Init 事件(OnInit 方法)
加载视图状态 在此阶段结束时,就会自动填充控件的 ViewState 属性,控件可以重写 LoadViewState 方法的默认实现,以自定义状态还原。 LoadViewState 方法
处理回发数据 处理传入窗体数据,并相应地更新属性。
注意:只有处理回发数据的控件参与此阶段。
LoadPostData 方法(如果已实现 IPostBackDataHandler)
加载 执行所有请求共有的操作,如设置数据库查询。此时,树中的服务器控件已创建并初始化、状态已还原并且窗体控件反映了客户端的数据。 Load 事件(OnLoad 方法)
发送回发更改通知 引发更改事件以响应当前和以前回发之间的状态更改。
注意:只有引发回发更改事件的控件参与此阶段。
RaisePostDataChangedEvent 方法(如果已实现 IPostBackDataHandler)
处理回发事件 处理引起回发的客户端事件,并在服务器上引发相应的事件。
注意:只有处理回发事件的控件参与此阶段。
RaisePostBackEvent 方法(如果已实现 IPostBackEventHandler)
预呈现 在呈现输出之前执行任何更新。可以保存在预呈现阶段对控件状态所做的更改,而在呈现阶段所对的更改则会丢失。 PreRender 事件(OnPreRender 方法)
保存状态 在此阶段后,自动将控件的 ViewState 属性保持到字符串对象中。此字符串对象被发送到客户端并作为隐藏变量发送回来。为了提高效率,控件可以重写 SaveViewState 方法以修改 ViewState 属性。 SaveViewState 方法
呈现 生成呈现给客户端的输出。 Render 方法
处置 执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如数据库链接。 Dispose 方法
卸载 执行销毁控件前的所有最终清理操作。控件作者通常在 Dispose 中执行清除,而不处理此事件。 UnLoad 事件(On UnLoad 方法)

表6-1 ASP.NET控件的施行生命周期

在那边,控件设计使用了Template
Method形式,Control基类提供了绝大部分protected虚方法,留待其子类改写其方法。以PetShop
4.0为例,就定义了三个ASP.NET控件,它们都属于System.Web.UI.WebControls.WebControl的子类。其中,CustomList控件派生自System.Web.UI.WebControls.DataList,CustomGrid控件则派生自System.Web.UI.WebControls.Repeater。

由于那多个控件都改变了其父类控件的显现格局,故而,大家得以经过重写父类的Render虚方法,已毕控件的自定义。例如CustomGrid控件:

XML 72public class CustomGrid : Repeater…
XML 73//Static constants
XML 74    protected const string HTML1 = “<table cellpadding=0 
XML 75cellspacing=0><tr><td colspan=2>”;
XML 76    protected const string HTML2 = “</td></tr><tr><td class=paging align=left>”;
XML 77    protected const string HTML3 = “</td><td align=right class=paging>”;
XML 78    protected const string HTML4 = “</td></tr></table>”;
XML 79    private static readonly Regex RX = new Regex(@”^&page=\d+”, 
XML 80RegexOptions.Compiled);
XML 81    private const string LINK_PREV = “<a href=?page={0}>< Previous</a>”;
XML 82    private const string LINK_MORE = “<a href=?page={0}>More ></a>”;
XML 83private const string KEY_PAGE = “page”;
XML 84    private const string COMMA = “?”;
XML 85    private const string AMP = “&”;
XML 86
XML 87XML 88override protected void Render(HtmlTextWriter writer) XML 89{
XML 90
XML 91        //Check there is some data attached
XML 92XML 93        if (ItemCount == 0) XML 94{
XML 95            writer.Write(emptyText);
XML 96            return;
XML 97        }
XML 98        //Mask the query
XML 99        string query = Context.Request.Url.Query.Replace(COMMA, AMP);
XML 100        query = RX.Replace(query, string.Empty);
XML 101        // Write out the first part of the control, the table header
XML 102        writer.Write(HTML1);
XML 103        // Call the inherited method
XML 104        base.Render(writer);
XML 105        // Write out a table row closure
XML 106        writer.Write(HTML2);
XML 107        //Determin whether next and previous buttons are required
XML 108        //Previous button?
XML 109        if (currentPageIndex > 0)
XML 110            writer.Write(string.Format(LINK_PREV, (currentPageIndex – 1) + query));
XML 111        //Close the table data tag
XML 112        writer.Write(HTML3);
XML 113
XML 114        //Next button?
XML 115        if (currentPageIndex < PageCount)
XML 116            writer.Write(string.Format(LINK_MORE, (currentPageIndex + 1) + query));
XML 117
XML 118        //Close the table
XML 119        writer.Write(HTML4);
XML 120    }

鉴于CustomGrid继承自Repeater控件,由此它同时还延续了Repeater的DataSource属性,那是一个虚属性,它暗中认同的set访问器属性如下:

XML 121public virtual object DataSource
XML 122XML 123XML 124{
XML 125XML 126      get  XML 127{… }
XML 128      set
XML 129XML 130      XML 131{
XML 132            if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))
XML 133XML 134            XML 135{
XML 136XML 137                  throw new ArgumentException(SR.GetString(“Invalid_DataSource_Type”, new object[] XML 138{ this.ID }));
XML 139            }
XML 140            this.dataSource = value;
XML 141            this.OnDataPropertyChanged();
XML 142      }
XML 143}

对于CustomGrid而言,DataSource属性有着差别的安装行为,因此在定义CustomGrid控件的时候,需求改写DataSource虚属性,如下所示:

XML 144private IList dataSource;
XML 145private int itemCount;
XML 146
XML 147XML 148override public object DataSource XML 149{
XML 150XML 151    set XML 152{
XML 153    //This try catch block is to avoid issues with the VS.NET designer
XML 154        //The designer will try and bind a datasource which does not derive from ILIST
XML 155XML 156        try XML 157{
XML 158            dataSource = (IList)value;
XML 159            ItemCount = dataSource.Count;
XML 160        }
XML 161XML 162        catch XML 163{
XML 164            dataSource = null;
XML 165            ItemCount = 0;
XML 166        }
XML 167    }
XML 168}

当设置的value对象值不为IList类型时,set访问器就将捕获非常,然后将dataSource字段设置为null。

由于我们改写了DataSource属性,因此改写Repeater类的OnDataBinding()方法也就势在必行。别的,CustomGrid还提供了分页的效率,大家也亟需贯彻分页的有关操作。与DataSource属性差距,Repeater类的OnDataBinding()方法其实是继承和改写了Control基类的OnDataBinding()虚方法,而大家又在此基础上改写了Repeater类的OnDataBinding()方法:

XML 169XML 170override protected void OnDataBinding(EventArgs e) XML 171{
XML 172
XML 173    //Work out which items we want to render to the page
XML 174    int start = CurrentPageIndex * pageSize;
XML 175    int size = Math.Min(pageSize, ItemCount – start);
XML 176
XML 177    IList page = new ArrayList();
XML 178    //Add the relevant items from the datasource
XML 179    for (int i = 0; i < size; i++)
XML 180        page.Add(dataSource[start + i]);
XML 181
XML 182    //set the base objects datasource
XML 183    base.DataSource = page;
XML 184    base.OnDataBinding(e);
XML 185}

其余,CustomGrid控件类还扩充了多如牛毛属于本身的性质和章程,例如PageSize、PageCount属性以及SetPage()方法等。正是因为ASP.NET控件引入了Composite形式与Template
Method格局,当大家在自定义控件时,就足以因而持续与改写的法子来完毕控件的设计。自定义ASP.NET控件一方面可以根据系统的须求完结特定的法力,也可以最大限度地促成目的的选拔,既可以减掉编码量,同时也有益于今后对先后的扩大与修改。
在PetShop
4.0中,除了自定义了上述WebControl控件的子控件外,最重大的仍旧使用了用户控件。在Controls文件夹下,一共定义了11个用户控件,内容包罗客户地址音信、信用卡音信、购物车音信、期望列表(Wish
List)音讯以及导航新闻、搜索结果音讯等。它们约等于是局地结缘控件,除了饱含了子控件的法子和天性外,也定义了有的必备的UI完毕逻辑。以ShoppingCartControl用户控件为例,它会在该控件被彰显(Render)以前,做一些数目准备干活,获取购物车数量,并作为数据源绑定到其下的Repeater控件:

XML 186public partial class ShoppingCartControl : System.Web.UI.UserControlXML 187
XML 188       
XML 189XML 190    protected void Page_PreRender(object sender, EventArgs e) XML 191{
XML 192XML 193        if (!IsPostBack) XML 194{
XML 195            BindCart();                
XML 196        }
XML 197    }
XML 198XML 199    private void BindCart() XML 200{
XML 201
XML 202        ICollection<CartItemInfo> cart = Profile.ShoppingCart.CartItems;
XML 203XML 204        if (cart.Count > 0) XML 205{
XML 206            repShoppingCart.DataSource = cart;
XML 207            repShoppingCart.DataBind();
XML 208            PrintTotal();
XML 209            plhTotal.Visible = true;
XML 210        }
XML 211XML 212        else XML 213{
XML 214            repShoppingCart.Visible = false;
XML 215            plhTotal.Visible = false;
XML 216            lblMsg.Text = “Your cart is empty.”;
XML 217        }
XML 218    }

在ShoppingCart页面下,大家得以投入该用户控件,如下所示:

XML 219<PetShopControl:shoppingcartcontrol id=”ShoppingCartControl1″ runat=”server”></PetShopControl:shoppingcartcontrol>

出于ShoppingCartControl用户控件已经达成了用于显示购物车多少的逻辑,那么在ShoppingCart.aspx.cs中,就可以不要承担这么些逻辑,在丰裕完毕目标重用的进程中,同时又达到了义务分开的目标。用户控件的设计者与页面设计者能够互不烦扰,分头完结本人的安顿性。特别是对此页面设计者而言,他得以是单纯的UI设计人士角色,仅需求关爱用户界面是不是雅观与温馨,对于表示层中对天地对象的调用与操作就足以不用理会,整个页面的代码也显得结构清晰、逻辑清楚,无疑也“干净”了不少。

6.4  ASP.NET 2.0新特性

鉴于PetShop 4.0是基于.NET Framework
2.0平台开发的电子商务系统,由此它在表示层也引入了累累ASP.NET
2.0的新特色,例如MemberShip、Profile、Master
Page、登录控件等风味。接下来,我将结合PetShop
4.0的规划分别介绍它们的兑现。

6.4.1  Profile特性

Profile提供的功用是本着用户的特性化服务。在ASP.NET
1.x本龙时,大家得以应用Session、Cookie等格局来存储用户的事态音讯。不过Session对象是享有生存期的,一旦生存期停止,该对象保留的值就会失效。库克ie将用户信息保存在客户端,它装有一定的安全隐患,一些关键的新闻不恐怕储存在Cookie中。一旦客户端禁止选择Cookie,则该功效就将失去利用的出力。

Profile的面世缓解了上述的困扰,它可以将用户的个人化音讯保存在指定的数据库中。ASP.NET
2.0的Profile作用暗中认可帮助Access数据库和SQL
Server数据库,纵然需求协理任何数据库,可以编制相关的ProfileProvider类。Profile对象是强类型的,大家可以为用户音讯建立属性,以PetShop
4.0为例,它创建了ShoppingCart、WishList和AccountInfo属性。

出于Profile功用必要拜访数据库,由此在数据访问层(DAL)定义了和Product等数据表相似的模块结构。首先定义了一个IProfileDAL接口模块,包括了接口IPetShopProfileProvider:

XML 220public interface IPetShopProfileProvider 
XML 221XML 222XML 223
XML 224 AddressInfo GetAccountInfo(string userName, string appName);   
XML 225 void SetAccountInfo(int uniqueID, AddressInfo addressInfo);
XML 226 IList<CartItemInfo> GetCartItems(string userName, string appName, 
XML 227bool isShoppingCart);
XML 228 void SetCartItems(int uniqueID, ICollection<CartItemInfo> cartItems, 
XML 229bool isShoppingCart);
XML 230 void UpdateActivityDates(string userName, bool activityOnly, string appName);
XML 231 int GetUniqueID(string userName, bool isAuthenticated, bool ignoreAuthenticationType,
XML 232 string appName);
XML 233 int CreateProfileForUser(string userName, bool isAuthenticated, string appName);
XML 234 IList<string> GetInactiveProfiles(int authenticationOption, 
XML 235DateTime userInactiveSinceDate, string appName);
XML 236 bool DeleteProfile(string userName, string appName);   
XML 237 IList<CustomProfileInfo> GetProfileInfo(int authenticationOption, 
XML 238string usernameToMatch, DateTime userInactiveSinceDate, string appName, 
XML 239out int totalRecords);
XML 240}

因为PetShop 4.0版本分别扶助SQL
Server和Oracle数据库,由此它分别定义了三个不等的PetShopProfileProvider类,已毕IPetShopProfileProvider接口,并雄居两个例外的模块SQLProfileDAL和OracleProfileDAL中。具体的兑现请参见PetShop
4.0的源代码。
相同的,PetShop
4.0为Profile引入了工厂格局,定义了模块ProfileDALFActory,工厂类DataAccess的概念如下:

XML 241XML 242public sealed class DataAccess XML 243{
XML 244
XML 245    private static readonly string profilePath = ConfigurationManager.AppSettings[“ProfileDAL”];
XML 246XML 247    public static PetShop.IProfileDAL.IPetShopProfileProvider CreatePetShopProfileProvider() XML 248{
XML 249 string className = profilePath + “.PetShopProfileProvider”;
XML 250 return (PetShop.IProfileDAL.IPetShopProfileProvider)Assembly.Load(profilePath).CreateInstance(className);
XML 251    }
XML 252}

在事情逻辑层(BLL)中,单独定义了模块Profile,它添加了对BLL、IProfileDAL和ProfileDALFactory模块的次序集。在该模块中,定义了密封类PetShopProfileProvider,它两次三番自System.Web.Profile.ProfileProvider类,该类作为Profile的Provider基类,用于在自定义配置文件中已毕相关的配备文件服务。在PetShopProfileProvider类中,重写了父类ProfileProvider中的一些格局,例如Initialize()、GetPropertyValues()、SetPropertyValues()、DeleteProfiles()等方法。别的,还为ShoppingCart、WishList、AccountInfo属性提供了Get和Set方法。至于Provider的切实可行落到实处,则调用工厂类DataAccess创立的实际品种对象,如下所示:
private static readonly IPetShopProfileProvider dal =
DataAccess.CreatePetShopProfileProvider();

概念了PetShop.Profile.PetShopProfileProvider类后,才足以在web.config配置文件中配备如下的配置节:

XML 253<profile automaticSaveEnabled=”false” defaultProvider=”ShoppingCartProvider”>
XML 254 <providers>
XML 255  <add name=”ShoppingCartProvider” connectionStringName=”SQLProfileConnString” type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet Shop 4.0″/>
XML 256  <add name=”WishListProvider” connectionStringName=”SQLProfileConnString” type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet Shop 4.0″/>
XML 257  <add name=”AccountInfoProvider” connectionStringName=”SQLProfileConnString” type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet Shop 4.0″/>
XML 258 </providers>
XML 259 <properties>
XML 260  <add name=”ShoppingCart” type=”PetShop.BLL.Cart” allowAnonymous=”true” provider=”ShoppingCartProvider”/>
XML 261  <add name=”WishList” type=”PetShop.BLL.Cart” allowAnonymous=”true” provider=”WishListProvider”/>
XML 262  <add name=”AccountInfo” type=”PetShop.Model.AddressInfo” allowAnonymous=”false” provider=”AccountInfoProvider”/>
XML 263 </properties>
XML 264</profile>

在布局文件中,针对ShoppingCart、WishList和AccountInfo(它们的品类分别为PetShop.BLL.Cart、PetShop.BLL.Cart、PetShop.Model.AddressInfo)属性分别定义了ShoppingCartProvider、WishListProvider、AccountInfoProvider,它们的门类均为PetShop.Profile.PetShopProfileProvider类型。至于Profile的新闻到底是储存在何种类型的数据库中,则由以下的配置节决定:
<add key=”ProfileDAL” value=”PetShop.SQLProfileDAL”/>

而键值为ProfileDAL的值,正是Profile的厂子类PetShop.ProfileDALFactory.DataAccess在应用反射技术创设IPetShopProfileProvider类型对象时得到的。

在表示层中,可以动用页面的Profile属性访问用户的秉性化属性,例如在ShoppingCart页面的codebehind代码ShoppingCart.aspx.cs中,调用Profile的ShoppingCart属性:

XML 265XML 266public partial class ShoppingCart : System.Web.UI.Page XML 267{
XML 268
XML 269XML 270    protected void Page_PreInit(object sender, EventArgs e) XML 271{
XML 272XML 273        if (!IsPostBack) XML 274{
XML 275            string itemId = Request.QueryString[“addItem”];
XML 276XML 277            if (!string.IsNullOrEmpty(itemId)) XML 278{
XML 279                Profile.ShoppingCart.Add(itemId);
XML 280                Profile.Save();
XML 281                // Redirect to prevent duplictations in the cart if user hits “Refresh”
XML 282                Response.Redirect(“~/ShoppingCart.aspx”, true);
XML 283            }
XML 284        }
XML 285    }
XML 286}

在上述的代码中,Profile属性的值从何而来?实际上,在大家为web.config配置文件中对Profile举行配置后,启动Web应用程序,ASP.NET会依照该配置文件中的相关配置创设一个ProfileCommon类的实例。该类继承自System.Web.Profile.ProfileBase类。然后调用从父类继承来的GetPropertyValue和SetPropertyValue方法,检索和装置配置文件的属性值。然后,ASP.NET将创立好的ProfileCommon实例设置为页面的Profile属性值。因此,大家可以通过智能感知获取Profile的ShoppingCart属性,同时也足以应用ProfileCommon继承自ProfileBase类的Save()方法,根据属性值更新Profile的数据源。

6.4.2  Membership特性

PetShop
4.0并从未利用Membership的高级成效,而是直接让Membership天性和ASP.NET
2.0新增的记名控件举办绑定。由于.NET Framework 2.0一度定义了针对性SQL
Server的SqlMembershipProvider,由此对于PetShop
4.0而言,完结Membership比之达成Profile要不难,仅仅必要为Oracle数据库定义MembershipProvider即可。在PetShop.Membership模块中,定义了OracleMembershipProvider类,它再三再四自System.Web.Security.MembershipProvider抽象类。

OracleMembershipProvider类的贯彻所有极高的参考价值,假设大家须要定义自个儿的MembershipProvider类,可以参照该类的已毕。
实质上OracleMemberShip类的贯彻并不复杂,在此类中,重倘若本着用户及用户安全而完结相关的行为。由于在父类MembershipProvider中,已经定义了有关操作的虚方法,由此我们需求作的是重写这个虚方法。由于与Membership有关的消息都以储存在数据库中,由此OracleMembershipProvider与SqlMembershipProvider类的关键差异仍然在于对数据库的造访。对于SQL
Server而言,大家使用aspnet_regsql工具为Membership建立了有关的数据表以及存储进度。或者是因为文化产权的原因,Microsoft并没有为Oracle数据库提供类似的工具,由此必要大家友好去创立membership的数据表。别的,由于没有开创Oracle数据库的积存进程,因此OracleMembershipProvider类中的完毕是一向调用SQL语句。以CreateUser()方法为例,剔除那么些乌烟瘴气的参数判断与安全性判断,SqlMembershipProvider类的兑现如下:

XML 287public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
XML 288XML 289XML 290{
XML 291      MembershipUser user1;
XML 292      //后面的代码略;
XML 293      try
XML 294XML 295      XML 296{
XML 297            SqlConnectionHolder holder1 = null;
XML 298            try
XML 299XML 300            XML 301{
XML 302                  holder1 = SqlConnectionHelper.GetConnection(this._sqlConnectionString, true);
XML 303                  this.CheckSchemaVersion(holder1.Connection);
XML 304                  DateTime time1 = this.RoundToSeconds(DateTime.UtcNow);
XML 305                  SqlCommand command1 = new SqlCommand(“dbo.aspnet_Membership_CreateUser”, holder1.Connection);
XML 306                  command1.CommandTimeout = this.CommandTimeout;
XML 307                  command1.CommandType = CommandType.StoredProcedure;
XML 308                  command1.Parameters.Add(this.CreateInputParam(“@ApplicationName”, SqlDbType.NVarChar, this.ApplicationName));
XML 309                  command1.Parameters.Add(this.CreateInputParam(“@UserName”, SqlDbType.NVarChar, username));
XML 310                  command1.Parameters.Add(this.CreateInputParam(“@Password”, SqlDbType.NVarChar, text2));
XML 311                  command1.Parameters.Add(this.CreateInputParam(“@PasswordSalt”, SqlDbType.NVarChar, text1));
XML 312                  command1.Parameters.Add(this.CreateInputParam(“@Email”, SqlDbType.NVarChar, email));
XML 313                  command1.Parameters.Add(this.CreateInputParam(“@PasswordQuestion”, SqlDbType.NVarChar, passwordQuestion));
XML 314                  command1.Parameters.Add(this.CreateInputParam(“@PasswordAnswer”, SqlDbType.NVarChar, text3));
XML 315                  command1.Parameters.Add(this.CreateInputParam(“@IsApproved”, SqlDbType.Bit, isApproved));
XML 316                  command1.Parameters.Add(this.CreateInputParam(“@UniqueEmail”, SqlDbType.Int, this.RequiresUniqueEmail ? 1 : 0));
XML 317                  command1.Parameters.Add(this.CreateInputParam(“@PasswordFormat”, SqlDbType.Int, (int) this.PasswordFormat));
XML 318                  command1.Parameters.Add(this.CreateInputParam(“@CurrentTimeUtc”, SqlDbType.DateTime, time1));
XML 319                  SqlParameter parameter1 = this.CreateInputParam(“@UserId”, SqlDbType.UniqueIdentifier, providerUserKey);
XML 320                  parameter1.Direction = ParameterDirection.InputOutput;
XML 321                  command1.Parameters.Add(parameter1);
XML 322                  parameter1 = new SqlParameter(“@ReturnValue”, SqlDbType.Int);
XML 323                  parameter1.Direction = ParameterDirection.ReturnValue;
XML 324                  command1.Parameters.Add(parameter1);
XML 325                  command1.ExecuteNonQuery();
XML 326                  int num3 = (parameter1.Value != null) ? ((int) parameter1.Value) : -1;
XML 327                  if ((num3 < 0) || (num3 > 11))
XML 328XML 329                  XML 330{
XML 331                        num3 = 11;
XML 332                  }
XML 333                  status = (MembershipCreateStatus) num3;
XML 334                  if (num3 != 0)
XML 335XML 336                  XML 337{
XML 338                        return null;
XML 339                  }
XML 340                  providerUserKey = new Guid(command1.Parameters[“@UserId”].Value.ToString());
XML 341                  time1 = time1.ToLocalTime();
XML 342                  user1 = new MembershipUser(this.Name, username, providerUserKey, email, passwordQuestion, null, isApproved, false, time1, time1, time1, time1, new DateTime(0x6da, 1, 1));
XML 343            }
XML 344            finally
XML 345XML 346            XML 347{
XML 348                  if (holder1 != null)
XML 349XML 350                  XML 351{
XML 352                        holder1.Close();
XML 353                        holder1 = null;
XML 354                  }
XML 355            }
XML 356      }
XML 357      catch
XML 358XML 359      XML 360{
XML 361            throw;
XML 362      }
XML 363      return user1;
XML 364}

代码中,aspnet_Membership_CreateUser为aspnet_regsql工具为membership成立的蕴藏进程,它的功用就是创办一个用户。

OracleMembershipProvider类中对CreateUser()方法的概念如下:

XML 365XML 366public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object userId, out MembershipCreateStatus status) XML 367{
XML 368    //后边的代码略;
XML 369 //Create connection
XML 370 OracleConnection connection = new OracleConnection(OracleHelper.ConnectionStringMembership);
XML 371 connection.Open();
XML 372 OracleTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
XML 373XML 374 try XML 375{
XML 376  DateTime dt = DateTime.Now;
XML 377  bool isUserNew = true;
XML 378
XML 379  // Step 1: Check if the user exists in the Users table: create if not    
XML 380  int uid = GetUserID(transaction, applicationId, username, true, false, dt, out isUserNew);
XML 381XML 382  if(uid == 0) XML 383{ // User not created successfully!
XML 384   status = MembershipCreateStatus.ProviderError;
XML 385   return null;
XML 386  }
XML 387  // Step 2: Check if the user exists in the Membership table: Error if yes.
XML 388XML 389  if(IsUserInMembership(transaction, uid)) XML 390{
XML 391   status = MembershipCreateStatus.DuplicateUserName;
XML 392   return null;
XML 393  }
XML 394  // Step 3: Check if Email is duplicate
XML 395XML 396  if(IsEmailInMembership(transaction, email, applicationId)) XML 397{
XML 398   status = MembershipCreateStatus.DuplicateEmail;
XML 399   return null;
XML 400  }
XML 401  // Step 4: Create user in Membership table     
XML 402  int pFormat = (int)passwordFormat;
XML 403XML 404  if(!InsertUser(transaction, uid, email, pass, pFormat, salt, “”, “”, isApproved, dt)) XML 405{
XML 406   status = MembershipCreateStatus.ProviderError;
XML 407   return null;
XML 408  }
XML 409  // Step 5: Update activity date if user is not new
XML 410XML 411  if(!isUserNew) XML 412{
XML 413XML 414   if(!UpdateLastActivityDate(transaction, uid, dt)) XML 415{
XML 416    status = MembershipCreateStatus.ProviderError;
XML 417    return null;
XML 418   }
XML 419  }
XML 420  status = MembershipCreateStatus.Success;
XML 421  return new MembershipUser(this.Name, username, uid, email, passwordQuestion, null, isApproved, false, dt, dt, dt, dt, DateTime.MinValue);
XML 422 }
XML 423XML 424 catch(Exception) XML 425{
XML 426  if(status == MembershipCreateStatus.Success)
XML 427   status = MembershipCreateStatus.ProviderError;
XML 428  throw;
XML 429 }
XML 430XML 431 finally XML 432{
XML 433  if(status == MembershipCreateStatus.Success)
XML 434   transaction.Commit();
XML 435  else
XML 436   transaction.Rollback();
XML 437  connection.Close();
XML 438  connection.Dispose();
XML 439 }
XML 440}

代码中,InsertUser()方法就是肩负用户的始建,而在头里则必要看清创立的用户是否业已存在。InsertUser()方法的概念如下:

XML 441XML 442private static bool InsertUser(OracleTransaction transaction, int userId, string email, string password, int passFormat, string passSalt, string passQuestion, string passAnswer, bool isApproved, DateTime dt) XML 443{
XML 444
XML 445 string insert = “INSERT INTO MEMBERSHIP (USERID, EMAIL, PASSWORD, PASSWORDFORMAT, PASSWORDSALT, PASSWORDQUESTION, PASSWORDANSWER, ISAPPROVED, CREATEDDATE, LASTLOGINDATE, LASTPASSWORDCHANGEDDATE) VALUES (:UserID, :Email, :Pass, :PasswordFormat, :PasswordSalt, :PasswordQuestion, :PasswordAnswer, :IsApproved, :CDate, :LLDate, :LPCDate)”;
XML 446XML 447 OracleParameter[] insertParms = XML 448{ new OracleParameter(“:UserID”, OracleType.Number, 10), new OracleParameter(“:Email”, OracleType.VarChar, 128), new OracleParameter(“:Pass”, OracleType.VarChar, 128), new OracleParameter(“:PasswordFormat”, OracleType.Number, 10), new OracleParameter(“:PasswordSalt”, OracleType.VarChar, 128), new OracleParameter(“:PasswordQuestion”, OracleType.VarChar, 256), new OracleParameter(“:PasswordAnswer”, OracleType.VarChar, 128), new OracleParameter(“:IsApproved”, OracleType.VarChar, 1), new OracleParameter(“:CDate”, OracleType.DateTime), new OracleParameter(“:LLDate”, OracleType.DateTime), new OracleParameter(“:LPCDate”, OracleType.DateTime) };
XML 449 insertParms[0].Value = userId;
XML 450 insertParms[1].Value = email;
XML 451 insertParms[2].Value = password;
XML 452 insertParms[3].Value = passFormat;
XML 453 insertParms[4].Value = passSalt;
XML 454 insertParms[5].Value = passQuestion;
XML 455 insertParms[6].Value = passAnswer;
XML 456 insertParms[7].Value = OracleHelper.OraBit(isApproved);
XML 457 insertParms[8].Value = dt;
XML 458 insertParms[9].Value = dt;
XML 459 insertParms[10].Value = dt;
XML 460
XML 461 if(OracleHelper.ExecuteNonQuery(transaction, CommandType.Text, insert, insertParms) != 1)
XML 462  return false;
XML 463 else
XML 464  return true;
XML 465}

在为Membership建立了Provider类后,还索要在配备文件中布局相关的配置节,例如SqlMembershipProvider的计划:

XML 466<membership defaultProvider=”SQLMembershipProvider”>
XML 467 <providers>
XML 468  <add name=”SQLMembershipProvider” type=”System.Web.Security.SqlMembershipProvider” connectionStringName=”SQLMembershipConnString” applicationName=”.NET Pet Shop 4.0″ enablePasswordRetrieval=”false” enablePasswordReset=”true” requiresQuestionAndAnswer=”false” requiresUniqueEmail=”false” passwordFormat=”Hashed”/>
XML 469 </providers>
XML 470</membership>

对于OracleMembershipProvider而言,配置大约相似:

XML 471<membership defaultProvider=”OracleMembershipProvider”>
XML 472 <providers>
XML 473  <clear/>
XML 474  <add name=”OracleMembershipProvider” 
XML 475   type=”PetShop.Membership.OracleMembershipProvider” 
XML 476   connectionStringName=”OraMembershipConnString” 
XML 477   enablePasswordRetrieval=”false” 
XML 478   enablePasswordReset=”false” 
XML 479   requiresUniqueEmail=”false” 
XML 480   requiresQuestionAndAnswer=”false” 
XML 481   minRequiredPasswordLength=”7″ 
XML 482   minRequiredNonalphanumericCharacters=”1″ 
XML 483   applicationName=”.NET Pet Shop 4.0″ 
XML 484   hashAlgorithmType=”SHA1″ 
XML 485   passwordFormat=”Hashed”/>
XML 486 </providers>
XML 487</membership>

有关布署节属性的含义,可以参考MSDN等连锁文档。

6.4.3  ASP.NET登录控件

此间所谓的报到控件并不是指一个控件,而是ASP.NET
2.0新提供的一组用于缓解用户登录的控件。登录控件与Membership进行集成,连忙便捷地促成用户登录的拍卖。ASP.NET登录控件包罗Login控件、LoginView控件、LoginStatus控件、LoginName控件、PasswordRescovery控件、CreateUserWizard控件以及ChangePassword控件。
PetShop
4.0就像一本体现登录控件用法的八面见光教程。大家可以从诸如SignIn、NewUser等页面中,看到ASP.NET登录控件的应用办法。例如在SignIn.aspx中,用到了Login控件。在该控件中,可以包蕴TextBox、Button等类型的控件,用法如下所示:

XML 488<asp:Login ID=”Login” runat=”server” CreateUserUrl=”~/NewUser.aspx” SkinID=”Login” FailureText=”Login failed. Please try again.”>
XML 489</asp:Login>

又比如NewUser.aspx中对CreateUserWizard控件的采用:

XML 490<asp:CreateUserWizard ID=”CreateUserWizard” runat=”server” CreateUserButtonText=”Sign Up” InvalidPasswordErrorMessage=”Please enter a more secure password.” PasswordRegularExpressionErrorMessage=”Please enter a more secure password.” 
XML 491RequireEmail=”False” SkinID=”NewUser”>
XML 492<WizardSteps>
XML 493            <asp:CreateUserWizardStep ID=”CreateUserWizardStep1″ runat=”server”>
XML 494   </asp:CreateUserWizardStp>
XML 495 </WizardSteps>
XML 496</asp:CreateUserWizard>

利用了登录控件后,大家毋需编写与用户登录相关的代码,登录控件已经为我们成功了有关的意义,这就大大地简化了那个系统的安顿与完结。

6.4.4  Master Page特性

Master Page相当于是一切Web站点的统一模板,建立的Master
Page文件增加名为.master。它可以涵盖静态文本、html成分和服务器控件。Master
Page由新鲜的@Master指令识别,如:

XML 497<%@ Master Language=”C#” CodeFile=”MasterPage.master.cs” Inherits=”MasterPage” %>

利用Master
Page可以为网站建立一个集合的体裁,且可以采纳它便宜地创建一组控件和代码,然后将其选取于一组页。对于那一个体制与功效相似的页而言,利用Master
Page就足以集中处理为Master
Page,一旦举办改动,就可以在一个职位上开展创新。

在PetShop 4.0中,建立了名为MasterPage.master的Master
Page,它包涵了header、LoginView控件、导航菜单以及用于呈现内容的html成分,如图6-3所示: 

XML 498

图6-3 PetShop 4.0的Master Page

@Master指令的定义如下:

XML 499<%@ Master Language=”C#” AutoEventWireup=”true” CodeFile=”MasterPage.master.cs” Inherits=”PetShop.Web.MasterPage” %>

Master Page同样利用codebehind技术,以PetShop 4.0的Master
Page为例,codebehind的代码放在文件MasterPage.master.cs中:

XML 500public partial class MasterPage : System.Web.UI.MasterPage {
XML 501
XML 502    private const string HEADER_PREFIX = “.NET Pet Shop :: {0}”;
XML 503
XML 504    protected void Page_PreRender(object sender, EventArgs e) { 
XML 505        ltlHeader.Text = Page.Header.Title;
XML 506        Page.Header.Title = string.Format(HEADER_PREFIX, Page.Header.Title);          
XML 507    }
XML 508    protected void btnSearch_Click(object sender, EventArgs e) {
XML 509        WebUtility.SearchRedirect(txtSearch.Text);    
XML 510    }
XML 511}

留意Master
Page页面不再接续自System.Web.UI.Page,而是继续System.Web.UI.MasterPage类。与Page类继承TemplateControl类差别,它是UserControl类的子类。因而,能够接纳在Master
Page上的实用指令与UserControl的可用指令相同,例如Auto伊芙ntWireup、ClassName、CodeFile、EnableViewState、WarningLevel等。

每一种与Master
Page相关的内容页必须在@Page指令的MasterPageFile属性中引用相关的Master
Page。例如PetShop 4.0中的CheckOut内容页,其@Page指令的概念如下:

XML 512<%@ Page Language=”C#” MasterPageFile=”~/MasterPage.master” AutoEventWireup=”true” CodeFile=”CheckOut.aspx.cs” Inherits=”PetShop.Web.CheckOut” Title=”Check Out” %>

Master Page可以拓展嵌套,例如大家建立了父Master
Page页面Parent.master,那么在子Master
Page中,可以使用master属性指定其父MasterPage:
<%@ Master Language=”C#” master=”Parent.master”%>

而内容页则足以按照事态指向Parent.master大概Child.master页面。

就算说Master
Page大多数状态下是以宣称情势创建,但我们也得以成立一个类继承System.Web.UI.MasterPage,从而形成对Master
Page的编程式创立。但在选拔那种方法的还要,应该同时创设.master文件。其余对Master
Page的调用也得以运用编程的办法成功,例如动态地添加Master
Page,我们重写内容页的Page_PreInit()方法,如下所示:

XML 513void Page_PreInit(Object sender, EventArgs e)
XML 514XML 515XML 516{
XML 517    this.MasterPageFile = “~/NewMaster.master”;
XML 518}

之所以重写Page_PreInit()方法,是因为Master
Page会在内容页开始化阶段举办联合,也即是说是在PreInit阶段已毕Master
Page的分红。
ASP.NET
2.0引入的新个性,并不只限于上述介绍的始末。例如Theme、Wizard控件等新特色在PetShop
4.0中也获取了大量的应用。即便ASP.NET
2.0即时地吐故纳新,对表示层的布署有所改正,不过作为ASP.NET
2.0的内部一些,它们仅仅是对现有框架缺失的弥补与改革,属于“猛虎添翼”的层面,对于所有表示层设计技术而言,起到的牵动成效却不行简单。

截止AJAX(Asynchronous JavaScript and
XML)的面世,整个局面才大为改观。固然AJAX技术带有几分“旧瓶装新酒”的含意,可是它从降生之初,就持有了王者气象,大有席卷天下之势。各样援救AJAX技术的框架如不可胜道般纷纭吐出新芽,支撑起百花齐放的强盛,气势汹汹地创设出唯AJAX独尊的态势。近来,AJAX已经成为了Web应用的主流开发技术,许多业界大鳄都呲牙咧嘴初阶了对这一块新领地的抢滩登陆。例如IBM、Oracle、Yahoo等营业所都干扰启动了开源的AJAX项目。微软也不甘,及时地生产了ASP.NET
AJAX,那是一个基于ASP.NET的AJAX框架,它包含了ASP.NET
AJAX服务端组件和ASP.NET AJAX客户端组件,并集成在Visual
Studio中,为ASP.NET开发者提供了一个强劲的AJAX应用环境。

本人前日还不可以预见AJAX技术在以后的走向,可是单单从表示层设计的角度而言,AJAX技术一样带了一场全新的革命。大家还是能期待以往的PetShop
5.0,可以在表示层设计上带来越来越多的惊喜。

相关文章

网站地图xml地图