PetShop之表示层设计

《解剖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所示: 

图片 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方式是MartinFowler在《公司应用架构形式》中最重点的表示层方式之一。在.NET平台下,Page
Controller方式的兑现万分简单,以Products.aspx页面为例。首先在aspx页面中,进行如下的装置:

图片 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请求的接口,提供了之类的概念:

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

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

图片 17public partial class Products : System.Web.UI.Page 
图片 18图片 19图片 20{
图片 21    protected void Page_Load(object sender, EventArgs e) 
图片 22图片 23    图片 24{
图片 25        //get page header and title
图片 26        Page.Title = WebUtility.GetCategoryName(Request.QueryString[“categoryId”]);
图片 27    }
图片 28}
图片 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()方法,如下所示:

图片 30public partial class CheckOut : System.Web.UI.Page图片 31
图片 32
图片 33图片 34    protected void wzdCheckOut_FinishButtonClick(object sender, WizardNavigationEventArgs e) 图片 35{
图片 36图片 37        if (Profile.ShoppingCart.CartItems.Count > 0) 图片 38{
图片 39图片 40            if (Profile.ShoppingCart.Count > 0) 图片 41{
图片 42
图片 43                // display ordered items
图片 44                CartListOrdered.Bind(Profile.ShoppingCart.CartItems);
图片 45
图片 46                // display total and credit card information
图片 47                ltlTotalComplete.Text = ltlTotal.Text;
图片 48                ltlCreditCardComplete.Text = ltlCreditCard.Text;
图片 49
图片 50                // create order
图片 51                OrderInfo order = new OrderInfo(int.MinValue, DateTime.Now, User.Identity.Name, GetCreditCardInfo(), billingForm.Address, shippingForm.Address, Profile.ShoppingCart.Total, Profile.ShoppingCart.GetOrderLineItems(), null);
图片 52
图片 53                // insert
图片 54                Order newOrder = new Order();
图片 55                newOrder.Insert(order);
图片 56
图片 57                // destroy cart
图片 58                Profile.ShoppingCart.Clear();
图片 59                Profile.Save();
图片 60            }
图片 61        }
图片 62图片 63        else 图片 64{
图片 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>”;
图片 66            wzdCheckOut.Visible = false;
图片 67        }
图片 68    }
图片 69
图片 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所示: 

图片 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控件:

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

鉴于CustomGrid继承自Repeater控件,因此它同时还再三再四了Repeater的DataSource属性,那是一个虚属性,它默认的set访问器属性如下:

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

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

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

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

由于大家改写了DataSource属性,因此改写Repeater类的OnDataBinding()方法也就势在必行。其余,CustomGrid还提供了分页的法力,大家也需求贯彻分页的连锁操作。与DataSource属性不一致,Repeater类的OnDataBinding()方法其实是持续和改写了Control基类的OnDataBinding()虚方法,而我辈又在此基础上改写了Repeater类的OnDataBinding()方法:

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

其余,CustomGrid控件类还扩展了好多属于自己的性质和章程,例如PageSize、PageCount属性以及SetPage()方法等。正是因为ASP.NET控件引入了Composite情势与Template
Method格局,当大家在自定义控件时,就足以经过持续与改写的不二法门来完结控件的宏图。自定义ASP.NET控件一方面可以依照系统的急需完毕特定的功能,也可以最大限度地贯彻目标的选拔,既可以削减编码量,同时也有利于以后对程序的扩大与修改。
在PetShop
4.0中,除了自定义了上述WebControl控件的子控件外,最重点的要么利用了用户控件。在Controls文件夹下,一共定义了11个用户控件,内容包罗客户地址音讯、信用卡音信、购物车新闻、期望列表(Wish
List)新闻以及导航音讯、搜索结果信息等。它们相当于是有些组成控件,除了含有了子控件的措施和总体性外,也定义了部分必不可少的UI完结逻辑。以ShoppingCartControl用户控件为例,它会在该控件被显示(Render)在此以前,做一些数据准备工作,获取购物车数量,并作为数据源绑定到其下的Repeater控件:

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

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

图片 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对象是所有生存期的,一旦生存期甘休,该对象保留的值就会失灵。Cookie将用户信息保存在客户端,它有着自然的安全隐患,一些至关首要的音讯无法积存在Cookie中。一旦客户端禁止行使Cookie,则该效率就将失去利用的功力。

Profile的出现缓解了上述的苦闷,它可以将用户的个人化音讯保存在指定的数据库中。ASP.NET
2.0的Profile作用默许协理Access数据库和SQL
Server数据库,假若必要帮忙其他数据库,可以编制相关的ProfileProvider类。Profile对象是强类型的,大家可以为用户新闻建立属性,以PetShop
4.0为例,它确立了ShoppingCart、WishList和AccountInfo属性。

由于Profile功能要求拜访数据库,因此在数量访问层(DAL)定义了和Product等数据表相似的模块结构。首先定义了一个IProfileDAL接口模块,蕴涵了接口IPetShopProfileProvider:

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

因为PetShop 4.0本子分别支持SQL
Server和Oracle数据库,因此它分别定义了多个例外的PetShopProfileProvider类,完结IPetShopProfileProvider接口,并置身五个分裂的模块SQLProfileDAL和OracleProfileDAL中。具体的贯彻请参见PetShop
4.0的源代码。
同一的,PetShop
4.0为Profile引入了工厂格局,定义了模块ProfileDALFActory,工厂类DataAccess的概念如下:

图片 241图片 242public sealed class DataAccess 图片 243{
图片 244
图片 245    private static readonly string profilePath = ConfigurationManager.AppSettings[“ProfileDAL”];
图片 246图片 247    public static PetShop.IProfileDAL.IPetShopProfileProvider CreatePetShopProfileProvider() 图片 248{
图片 249 string className = profilePath + “.PetShopProfileProvider”;
图片 250 return (PetShop.IProfileDAL.IPetShopProfileProvider)Assembly.Load(profilePath).CreateInstance(className);
图片 251    }
图片 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配置文件中安顿如下的配置节:

图片 253<profile automaticSaveEnabled=”false” defaultProvider=”ShoppingCartProvider”>
图片 254 <providers>
图片 255  <add name=”ShoppingCartProvider” connectionStringName=”SQLProfileConnString” type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet Shop 4.0″/>
图片 256  <add name=”WishListProvider” connectionStringName=”SQLProfileConnString” type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet Shop 4.0″/>
图片 257  <add name=”AccountInfoProvider” connectionStringName=”SQLProfileConnString” type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet Shop 4.0″/>
图片 258 </providers>
图片 259 <properties>
图片 260  <add name=”ShoppingCart” type=”PetShop.BLL.Cart” allowAnonymous=”true” provider=”ShoppingCartProvider”/>
图片 261  <add name=”WishList” type=”PetShop.BLL.Cart” allowAnonymous=”true” provider=”WishListProvider”/>
图片 262  <add name=”AccountInfo” type=”PetShop.Model.AddressInfo” allowAnonymous=”false” provider=”AccountInfoProvider”/>
图片 263 </properties>
图片 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属性:

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

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

代码中,aspnet_Membership_CreateUser为aspnet_regsql工具为membership创制的存储进程,它的功力就是开创一个用户。

OracleMembershipProvider类中对CreateUser()方法的定义如下:

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

代码中,InsertUser()方法就是负责用户的开创,而在前头则要求看清创造的用户是不是曾经存在。InsertUser()方法的定义如下:

图片 441图片 442private static bool InsertUser(OracleTransaction transaction, int userId, string email, string password, int passFormat, string passSalt, string passQuestion, string passAnswer, bool isApproved, DateTime dt) 图片 443{
图片 444
图片 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)”;
图片 446图片 447 OracleParameter[] insertParms = 图片 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) };
图片 449 insertParms[0].Value = userId;
图片 450 insertParms[1].Value = email;
图片 451 insertParms[2].Value = password;
图片 452 insertParms[3].Value = passFormat;
图片 453 insertParms[4].Value = passSalt;
图片 454 insertParms[5].Value = passQuestion;
图片 455 insertParms[6].Value = passAnswer;
图片 456 insertParms[7].Value = OracleHelper.OraBit(isApproved);
图片 457 insertParms[8].Value = dt;
图片 458 insertParms[9].Value = dt;
图片 459 insertParms[10].Value = dt;
图片 460
图片 461 if(OracleHelper.ExecuteNonQuery(transaction, CommandType.Text, insert, insertParms) != 1)
图片 462  return false;
图片 463 else
图片 464  return true;
图片 465}

在为Membership建立了Provider类后,还须求在配置文件中布署相关的配置节,例如SqlMembershipProvider的配备:

图片 466<membership defaultProvider=”SQLMembershipProvider”>
图片 467 <providers>
图片 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”/>
图片 469 </providers>
图片 470</membership>

对于OracleMembershipProvider而言,配置大概相像:

图片 471<membership defaultProvider=”OracleMembershipProvider”>
图片 472 <providers>
图片 473  <clear/>
图片 474  <add name=”OracleMembershipProvider” 
图片 475   type=”PetShop.Membership.OracleMembershipProvider” 
图片 476   connectionStringName=”OraMembershipConnString” 
图片 477   enablePasswordRetrieval=”false” 
图片 478   enablePasswordReset=”false” 
图片 479   requiresUniqueEmail=”false” 
图片 480   requiresQuestionAndAnswer=”false” 
图片 481   minRequiredPasswordLength=”7″ 
图片 482   minRequiredNonalphanumericCharacters=”1″ 
图片 483   applicationName=”.NET Pet Shop 4.0″ 
图片 484   hashAlgorithmType=”SHA1″ 
图片 485   passwordFormat=”Hashed”/>
图片 486 </providers>
图片 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等类其余控件,用法如下所示:

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

又例如NewUser.aspx中对CreateUserWizard控件的采取:

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

动用了登录控件后,大家毋需编写与用户登录相关的代码,登录控件已经为大家做到了相关的效果,那就大大地简化了这几个系统的安排与贯彻。

6.4.4  Master Page特性

Master Page相当于是一切Web站点的集合模板,建立的Master
Page文件扩充名为.master。它可以包含静态文本、html元素和服务器控件。Master
Page由特其余@Master指令识别,如:

图片 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所示: 

图片 498

图6-3 PetShop 4.0的Master Page

@Master指令的概念如下:

图片 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中:

图片 500public partial class MasterPage : System.Web.UI.MasterPage {
图片 501
图片 502    private const string HEADER_PREFIX = “.NET Pet Shop :: {0}”;
图片 503
图片 504    protected void Page_PreRender(object sender, EventArgs e) { 
图片 505        ltlHeader.Text = Page.Header.Title;
图片 506        Page.Header.Title = string.Format(HEADER_PREFIX, Page.Header.Title);          
图片 507    }
图片 508    protected void btnSearch_Click(object sender, EventArgs e) {
图片 509        WebUtility.SearchRedirect(txtSearch.Text);    
图片 510    }
图片 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指令的定义如下:

图片 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()方法,如下所示:

图片 513void Page_PreInit(Object sender, EventArgs e)
图片 514图片 515图片 516{
图片 517    this.MasterPageFile = “~/NewMaster.master”;
图片 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,可以在表示层设计上带来更加多的喜怒哀乐。

相关文章