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的属性,此时Event(事件)被硌,所有乘让Model的View对象见面自动更新,并根据Model对象来一个应(Response)信息,返回给Controller。Martin
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模式是Martin
Fowler在《企业应用架构模式》中尽要紧之代表层模式有。在.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的可用指令相同,例如AutoEventWireup、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,可以以象征层设计上带来双重多之大悲大喜。

相关文章