物流管理运送行业冷藏车GPS车辆监控体系解决方案

 

冷藏车GPS解决方案概述:

Petshop5.0
详解之一(系统架构设计)
  

目前,随着人们物质生活水准的滋长,人们对冷链食品的花费需要也逐渐提升,消费品市场急忙扩大,初级农产品、各样水产品、速冻食品、包装熟食、奶产品、花卉等物品的仓储和配送过程上中急需冷藏或冻结,相关的冷链物流周转也进一步受到关注。冷链物流一般遵照3T规格,即产品最终质料取决于在冷链链中贮藏和流通时间(提姆e)、温度(Temperature)和制品耐藏性(Tolerance)。

前言:PetShop是一个范例,微软用它来体现.Net公司系统开发的力量。业界有许多.Net与J2EE之争,许多数据是从微软的PetShop和Sun的PetStore而来。这种抵触不可制止带有深切的买卖色彩,对于大家开发人士而言,没有必要过多关注。不过PetShop随着版本的不断更新,至现在基于.Net
2.0的PetShop5.0说尽,整个规划逐渐变得干练而雅致,却又很多得以借鉴之处。PetShop是一个小型的花色,系统架构与代码都相比简单,却也可见了不少颇有价值的宏图与支出理念。本体系试图对PetShop作一个全体的解剖,遵照的代码是PetShop5.0,可以从链接http://www.52rs.net/ArticleView.aspx?gID=b96c547b-e93b-47f4-b694-279f27566aff中获得。  

此时此刻,集团客户对冷链物流集团的预期更加高,对应的行当监管标准也越加多,全球化进程不断加速,导致冷链物流行业运营越来越复杂。冷链货运网络也急忙增添成为一个范围庞大的、设备专业性强、涉及行业常见、从业人士众多的家业。

一、PetShop的体系架构设计

眼下冷链货运行业的现状是普遍存在劳动密集、从业人士数量规模大、基本素质不高等特点,配送、营销等事情中车子的在途管理存在“盲区”和“黑箱”。现代冷链物流属于控温型物流,为了贯彻冷链物流的音信处理及时、配送流程优化,以及存取选拣自动化、物流管理智能化,冷链物流迫切需要音讯化技术作为帮忙手段。

在软件类别架构设计中,分层式结构是最常见,也是最重要的一种结构。微软引荐的分层式结构相似分为三层,从下至上各自为:数据访问层、业务逻辑层(又或变成世界层)、表示层,如图所示:

我国当前还缺乏使得的冷链物流的管住艺术,原有监测技术手段落后是最大的技艺瓶颈。方今我国利用的技术手段的重大症结是:人工测量和纸面记录;无统一数据系统协理;实时性差、监管脱节;取证困难、无法确定权利;不能展开预警、损失率大等。


图一:三层的分层式结构

亿程公司动用在多年自主开发GPS物流车辆调度管理信息类另外成功经验基础上,结合实际冷链物流公司的业务管理流程要求,全方位整合GPS工业级调度监控技术,设计出一整套符合冷链物流集团货运管理和完好服务音讯连串缓解方案。

数量访问层:有时候也称为是持久层,其效能重如若背负数据库的造访。简而言之教就是落实对数据表的Select,Insert,Update,Delete的操作。如果要参与ORM的元素,那么就会包括对象和数据表之间的mapping,以及对象实体的持久化。在PetShop的多少访问层中,并从未拔取ORM,从而造成了代码量的增多,可以当做是成套计划实现中的一大缺点。

此方案按照自动识别采集技能、GPS车辆跟踪技术,将货况音讯数据自动识读输入音讯平台统计机体系,在运输过程中选用GPS跟踪技术格局和伎俩,可以为铺面提供准确的多少搜集和跟踪反馈的管用化解手段,在物流集团的车子追踪,所运输物品追踪和供应链的地位识别与岗位一定等地点都可帮助集团尽管有效地解决最近冷链物流集团中货况运输管理存在的题目。

工作逻辑层:是一切类其它主导,它与这些连串的事情(领域)有关。以PetShop为例,业务逻辑层的相干计划,均和网上宠物店特有的逻辑相关,例如查询宠物,下订单,添加宠物到购物车等等。假设波及到数据库的走访,则调用数据访问层。

专用功用:

表示层:是系统的UI部分,负责使用者与所有系统的交互。在这一层中,理想的情事是不应包括系统的事情逻辑。表示层中的逻辑代码,仅与界面元素有关。在PetShop中,是接纳ASP.Net来设计的,由此包含了诸多Web控件和相关逻辑。

1、超时停车报警:抢先规定停车时间的车辆举行报警,并转移报表;避免因超时促成运料损坏。
2、非法开箱报警:如正在运输的车辆没有在确定的地址打开货箱即举行报警。
3、货箱温度实时监控,领先高温点和低温点报警。
4、冷库、客户点设置:客户可在地形图上自主标注冷库或客户地方所在地点。
5、分组管理:所有车辆可按施工工地分组管理,进行偏航报警装置、分段限速报警装置等。
6、出入指定区域趟数总括和时间总结。

分层式结构究竟其优势何在?Martin福勒在《Patterns
of Enterprise Application Architecture》一书中提交了答案:
1、开发人士可以只关注整整结构中的其中某一层;
2、可以很容易的用新的贯彻来替换原有层次的落实;
3、可以降低层与层之间的依赖;
4、有利于标准;
5、利于各层逻辑的复用。

冷链物流一体化解决方案:

包括来说,分层式设计可以达至如下目标:分散关注、松散耦合、逻辑复用、标准定义。

1、货物自动识别和采集子系统
采集子系统选用基于条形码的货品识别采集技能。当了冷链物流公司货物入库或者货物装柜的时候,将围观识别货物条码标,同时在系统中完成第一次货物数量录入。当货物在转运和配送过程中,使用手持终端设备扫描条码标同时扫视配送车辆的鉴别标签,建立商品与车辆的应和,完成运输过程的好多次货物数量录入。每个手持终端设备或扫描枪也都有ID编号和隶属地方等数码音信。车辆识别技术有多种办法,既可以应用条码标模式,也足以利用RFID卡情势,最简便易行的则是在手持机中编好程序设定好车子编号,确认时精选或输入车辆号码即可。全程跟踪监控技术依靠自动识别货物和冷藏车辆,冷链物流公司就可以实时查询和全程监督到入库、装柜、中途站点转运、变更运输调度和目的地下货处理的每趟操作和流程经过。

一个好的分层式结构,可以使得开发人士的分工更为显然。一旦定义好各层次之间的接口,负责不同逻辑设计的开发人士就足以疏散关注,并肩前进。例如UI人士只需考虑用户界面的体会与操作,领域的宏图人士可以仅关注工作逻辑的计划性,而数据库设计人士也无须为繁琐的用户交互而高烧了。每个开发人士的天职取得了肯定,开发进度就可以高速的提升。

2、冷运车辆GPS定位监控子系统

麻痹大意耦合的利益是妇孺皆知的。倘使一个体系绝非分支,那么各自的逻辑都密不可分缠绕在联合,相互间相互依赖,什么人都是不可替换的。一旦爆发变动,则牵一发而动全身,对项目标熏陶极为深重。降低层与层间的依赖性,既可以优异地保管以后的可扩充,在复用性上也是优势显然。每个功用模块一旦定义好统一的接口,就可以被逐个模块所调用,而不用为相同的功力拓展双重地付出。

透过利用移动通讯技术、GPS技术和GIS电子地图技术,冷运物流车辆可以建立冷藏车识别子系统和GPS监控子系统。每部冷藏车都有确定和唯一的车子编号和标识,分别为条码标和车载终端号。同时车辆调度人士能够遵照调度任务通过GPS车载终端、移动短信息就近将调度音信下达给车辆和人口。通过上述的分辨和跟踪,利用条码数据收集、无线音讯发送等技巧做到总体物流货物跟踪,使得物流全经过可视化跟踪,真正为物流集团建造了“看得见”的全程货况跟踪平台。真正达到了加强物流效用,降低物流资产的法力。每辆冷运物流货运车辆具有一个GPS车载终端(可以遵照需要充实RFID标签或条码标签)。待运的商品上富有可识其它条码标。终端设备使用接口软件负责通过GPRS发送数据给大旨平台,在两遍运送或转运任务中一辆车对应了三个商品,该车的职位音讯和在途状态就是这一批货物的同台状态音讯。

展开好的分层式结构设计,标准也是必不可少的。只有在一定水准的原则基础上,那一个系统才是可增添的,可替换的。而层与层之间的通信也毫无疑问保证了接口的尺码。

3、温度侦检控制子系统

“金无足赤,人无完人”,分层式结构也不可防止具有部分缺陷:
1、降低了系统的性质。这是强烈的。假设不利用分层式结构,很多事务可以平素访问数据库,以此博得相应的多少,目前却必须透过中间层来形成。
2、有时会招致级联的改动。这种修改尤其体现在自上而下的取向。假若在表示层中需要追加一个效用,为力保其设计适合分层式结构,可能需要在相应的事体逻辑层和数目访问层中都追加对应的代码。

热度侦检控制子系统适用于冷藏车厢内温度数据的募集传输、记录和超限报警,是冷藏运输车车箱和商品温度监控的有效性控制序列。该子系统依据冷藏车的现实性温度控制需求建立了热度管理接口程序和GPS车载终端控制指令程序。通过GPS车载终端随时侦测温度传感器的感知温度来调整控制,保证所运输的生鲜食品和药品处于恰当的温度,从而保证了商品的质量。货物的热度变化通过温度传感器记录到车载终端中,同时可以实时或定时上传到消息保管平斯特拉斯堡,这样就水到渠成了货物温度的全程跟踪,保证了商品生鲜程度、质料的全程实时监察,轻松地化解了食物流通过程中的质量监控问题。

眼前提到,PetShop的表示层是用ASP.Net设计的,也就是说,它应是一个BS系统。在.Net中,标准的BS分层式结构如下图所示:

4、物流业务管理系统

图二:.Net中标准的BS分层式结构

冷链物流业务管理系统依托后台服务器上的多个软件平台(数据互换数据平台,通讯平台,数据库平台),完成了对整个一体化服务类另外底子数据处理、电子地图数据处理和业务数据处理,负责接收车载终端GPRS和GPS数据,处理系统录入数据和调度管理数据等等各项应用层业务逻辑,提供了各样事务的周密服务襄助。实现对冷藏车资源的有效性跟踪定位管理,同时将定点新闻和商家的事情资源举行组合。该冷藏车业务管理系统不仅为冷藏集团和外勤职员提供了一个急迅,灵活的管理工具,同时还为冷链物流集团成立了一种全新高效的保管和控制冷藏车辆资源的不错格局。

乘机PetShop版本的更新,其分层式结构也在持续的统筹兼顾,例如PetShop2.0,就从未应用标准的三层式结构,如图三:

图三:PetShop
2.0的系统架构

从图中我们得以观望,并没有分明的数目访问层设计。这样的规划即使提升了数额访问的特性,但也同时招致了事情逻辑层与数据访问的职责混乱。一旦要求襄助的数据库暴发变化,或者需要修改数据访问的逻辑,由于并未清楚的道岔,会造成项目作大的修改。而随着硬件系统性能的加强,以及充裕利用缓存、异步处理等机制,分层式结构所带动的性能影响几乎可以忽略不计。

PetShop3.0纠正了从前层次不明的题材,将数据访问逻辑作为单身的一层独立出来:

图四:PetShop
3.0的连串架构

PetShop5.0几近延续了3.0的构造,但在性能上作了自然的改进,引入了缓存和异步处理体制,同时又充足利用了ASP.Net
2.0的新职能MemberShip,因而PetShop5.0的系统架构图如下所示:


图五:PetShop
5.0的系统架构

比较3.0和5.0的系统架构图,其主旨的始末并从未发生变化。在数码访问层(DAL)中,依然使用DAL
Interface抽象出多少访问逻辑,并以DAL
Factory作为数据访问层对象的厂子模块。对于DAL
Interface而言,分别有匡助MS-SQL的SQL
Server DAL和支撑Oracle的Oracle
DAL具体实现。而Model模块则带有了数量实体对象。其详细的模块结构图如下所示:


图六:数据访问层的模块结构图

可以见到,在多少访问层中,完全选用了“面向接口编程”思想。抽象出来的IDAL模块,脱离了与具象数据库的依赖,从而使得整个数据访问层利于数据库迁移。DALFactory模块专门管理DAL对象的创导,便于工作逻辑层访问。SQLServerDAL和OracleDAL模块均实现IDAL模块的接口,其中蕴蓄的逻辑就是对数据库的Select,Insert,Update和Delete操作。因为数据库类型的两样,对数据库的操作也截然不同,代码也会由此有所区别。

此外,抽象出来的IDAL模块,除了拔除了向下的倚重之外,对于其上的作业逻辑层,同样仅设有弱依赖关系,如下图所示:

图七:业务逻辑层的模块结构图

图七中BLL是事情逻辑层的为主模块,它含有了任何系统的中央工作。在工作逻辑层中,不可能一贯访问数据库,而必须经过数量访问层。注意图中对数码访问工作的调用,是由此接口模块IDAL来完成的。既然与实际的数目访问逻辑无关,则层与层之间的关联就是高枕无忧耦合的。假如此时亟待修改数据访问层的切切实实贯彻,只要不关乎到IDAL的接口定义,那么业务逻辑层就不会碰着其他影响。毕竟,具体实现的SQLServerDAL和OracalDAL根本就与作业逻辑层没有简单关系。

因为在PetShop
5.0中引入了异步处理体制。插入订单的政策可以分为同步和异步,两者的插入策略分明不同,但对于调用者而言,插入订单的接口是完全相同的,所以PetShop
5.0中筹划了IBLLStrategy模块。即便在IBLLStrategy模块中,仅仅是简单的IOrderStategy,但与此同时也交给了一个范例和消息,这就是在业务逻辑的拍卖中,假如存在业务操作的多样化,或者是从此恐怕的浮动,均应使用抽象的法则。或者应用接口,或者应用抽象类,从而脱离对切实业务的依赖。不过在PetShop中,由于事务逻辑相对简便易行,这种考虑展现得不够醒目。也正因为此,PetShop将基本的业务逻辑都放到了一个模块BLL中,并不曾将切实的贯彻和虚幻严苛的按照模块分开。所以表示层和业务逻辑层之间的调用关系,其耦合度相对较高:

图八:表示层的模块结构图

在图五中,各种层次中还引入了扶持的模块,如数据访问层的Messaging模块,是为异步插入订单的效率提供,选拔了MSMQ(Microsoft
Messaging Queue)技术。而表示层的CacheDependency则提供缓存功效。这多少个特此外模块,我会在之后的篇章中详尽介绍。

PetShop5.0
详解之二(数据访问层之数据库访问计划)
 

在浩如烟海一中,我从全部上分析了PetShop的架构设计,并提及了分层的定义。从本有的起初,我将各种对各层举行代码级的剖析,以求拿到越来越缜密而深刻的知晓。在PetShop
5.0中,由于引入了ASP.Net
2.0的部分新特征,所以数据层的情节也更加的广泛和错综复杂,包括:数据库访问、Messaging、MemberShip、Profile四部分。在一系列二中,我将介绍有关数据库访问的宏图。

在PetShop中,系统需要处理的数据库对象分为两类:一是数码实体,对应数据库中相应的数据表。它们从不作为,仅用于表现对象的多寡。那个实体类都被内置Model程序集中,例如数据表Order对应的实体类OrderInfo,其类图如下: 

这么些目的并不具有持久化的效用,简单地说,它们是当做数据的载体,便于工作逻辑针对相应数据表举行读/写操作。尽管这么些类的特性分别映射了数据表的列,而每一个目标实例也刚刚对应于数据表的每一行,但这多少个实体类却并不抱有相应的数据库访问能力。

出于数量访问层和事务逻辑层都将对这么些数量实体举行操作,因而先后集Model会被这两层的模块所引述。

第二类数据库对象则是数额的作业逻辑对象。这里所指的作业逻辑,并非业务逻辑层意义上的领域(domain)业务逻辑(从这个意思上,我更赞成于将事情逻辑层称为“领域逻辑层”),一般意义上说,这一个工作逻辑即为基本的数据库操作,包括Select,Insert,Update和Delete。由于这个事情逻辑对象,仅具有行为而与数量无关,由此它们均被架空为一个独门的接口模块IDAL,例如数据表Order对应的接口IOrder: 

将数据实体与有关的数据库操作分离出来,符合面向对象的动感。首先,它呈现了“职责分开”的尺度。将数据实体与其行事分开,使得两者之间看重减少,当数码作为发生改变时,并不影响Model模块中的数据实体对象,避免了因一个类职责过多、过大,从而致使该类的引用者暴发“灾难性”的震慑。其次,它体现了“抽象”的振奋,或者说是“面向接口编程”的特等突显。抽象的接口模块IDAL,与具体的数据库访问实现完全切断。这种与实现无关的筹划,保证了系统的可增加性,同时也准保了数据库的可移植性。在PetShop中,可以支撑SQL
Server和Oracle,那么它们具体的贯彻就分别位于五个不同的模块SQLServerDAL、OracleDAL中。

以Order为例,在SQLServerDAL、OracleDAL多少个模块中,有例外的实现,但它们同时又都实现了IOrder接口,如图: 

从数据库的贯彻来看,PetShop体现出了没有ORM框架的重合与丑陋。由于要对数据表举行Insert和Select操作,以SQL
Server为例,就利用了SqlCommand,SqlParameter,SqlData里德r等目的,以成功这一个操作。尤其复杂的是Parameter的传递,在PetShop中,使用了汪洋的字符串常量来保存参数的称谓。其余,PetShop还特地为SQL
Server和Oracle提供了抽象的Helper类,包装了有的常用的操作,如ExecuteNonQuery、ExecuteReader等艺术。

在一向不ORM的情况下,使用Helper类是一个相比较好的策略,利用它来成功数据库基本操作的包装,可以减去过多和数据库操作有关的代码,这反映了目的复用的准绳。PetShop将那个Helper类统一置于DBUtility模块中,不同数据库的Helper类显露的艺术基本相同,只除了有些特另外要求,例如Oracle中拍卖bool类型的不二法门就和SQL
Server不同,从而专门提供了OraBit和OraBool方法。此外,Helper类中的方法均为static方法,以利于调用。OracleHelper的类图如下: 

对于数据访问层来说,最胸闷的是SQL语句的处理。在先前时期的CS结构中,由于未拔取三层式架构设计,数据访问层和业务逻辑层是紧密糅合在一起的,因而,SQL语句遍布与系统的每一个角落。这给程序的维护带来极大的劳累。别的,由于Oracle使用的是PL-SQL,而SQL
Server和Sybase等利用的是T-SQL,两者即使都坚守了专业SQL的语法,但在成千上万细节上仍有分别,假设将SQL语句大量的运用到程序中,无疑为可能的数据库移植也带来了坚苦。

最好的法门是利用储存过程。这种情势使得程序更加清洁,此外,由于存储过程可以以数据库脚本的形式存在,也有益移植和修改。但这种措施依旧有弱点。一是储存过程的测试相对忙绿。即使有相应的调剂工具,但比起对代码的调节而言,仍然相比复杂且不便宜。二是对系统的改进带来阻力。假设数据库访问是由程序完成,在.Net平台下,我们仅需要在修改程序后,将再也编译的先后集xcopy到布置的服务器上即可。尽管拔取了蕴藏过程,出于安全的设想,必须有特意的DBA重新运行存储过程的本子,部署的措施受到了限制。

自家已经在一个门类中,利用一个特意的表来存放SQL语句。如要使用有关的SQL语句,就使用重大字搜索得到对应语句。这种做法近似于存储过程的调用,但却避免了配备上的问题。但是这种形式却在性质上无法取得保证。它仅符合于SQL语句较少的场地。可是,利用美观的规划,大家得以为各类事情提供不同的表来存放SQL语句。同样的道理,那么些SQL语句也得以存放到XML文件中,更有益于系统的恢弘或涂改。不过前提是,我们需要为它提供专门的SQL语句管理工具。

SQL语句的拔取不能制止,咋样更好的施用SQL语句也无定论,但有一个规格值得我们服从,就是“应该尽量让SQL语句尽存在于数量访问层的切实落实中”。

理所当然,倘诺采纳ORM,那么任何就变得不同了。因为ORM框架已经为多少访问提供了着力的Select,Insert,Update和Delete操作了。例如在NHibernate中,大家得以从来调用ISession对象的Save方法,来Insert(或者说是Create)一个数码实体对象:
public void Insert(OrderInfo order)
{
    ISession s = Sessions.GetSession();
    ITransaction trans = null;
    try
    {
    trans = s.BeginTransaction();
      s.Save( order);
      trans.Commit();
    }
    finally
    {
      s.Close();
    }
}

从未SQL语句,也从没这些烦人的Parameters,甚至不需要特地去考虑工作。此外,这样的统筹,也是与数据库无关的,NHibernate可以经过Dialect(方言)的建制援助不同的数据库。唯一要做的是,我们需要为OrderInfo定义hbm文件。

理所当然,ORM框架并非是万能的,面对纷繁复杂的作业逻辑,它并无法一心扑灭SQL语句,以及替代复杂的数据库访问逻辑,但它却很好的反映了“80/20(或90/10)法则”(也被喻为“帕累托法则”),也就是说:花相比较少(10%-20%)的劲头就可以化解大部分(80%-90%)的问题,而要解决剩余的少部分题材则需要多得多的竭力。至少,那个在数额访问层中据为己有了多方的CRUD操作,通过动用ORM框架,我们就仅需要提交极个别岁月和生命力来化解它们了。这活脱脱缩小了任何项目支出的周期。

仍旧回到对PetShop的座谈上来。现在我们早就有了多少实体,数据对象的空洞接口和促成,可以说关于数据库访问的重点就早已成功了。留待大家的还有六个问题需要缓解:
1、数据对象创造的军事管制
2、利于数据库的移植

在PetShop中,要创制的数据对象包括Order,Product,Category,Inventory,Item。在前边的筹划中,这个目的已经被架空为相应的接口,而其实现则按照数据库的不同而有所不同。也就是说,创制的对象有多种品类,而每种档次又有两样的落实,这是典型的纸上谈兵工厂格局的施用场景。而地方所述的多个问题,也都得以透过架空工厂情势来缓解。标准的架空工厂情势类图如下: 

譬如,创造SQL
Server的Order对象如下:
PetShopFactory factory = new SQLServerFactory();
IOrder = factory.CreateOrder();

要考虑到数据库的可移植性,则factory必须作为一个全局变量,并在主程序运行时被实例化。但这么的计划性固然早已达标了“封装变化”的目标,但在创设PetShopFactory对象时,仍不可制止的出现了切实可行的类SQLServerFactory,也即是说,程序在那一个范围上发出了与SQLServerFactory的强依赖。一旦整个系统要求协理Oracle,那么还需要修改这行代码为:
PetShopFactory factory = new oracleFactory();

修改代码的那种行为强烈是不可承受的。解决的点子是“看重注入”。“倚重注入”的意义通常是用专门的IoC容器提供的,在Java平台下,这样的容器包括Spring,PicoContainer等。而在.Net平台下,最广泛的则是Spring.Net。但是,在PetShop系统中,并不需要专门的容器来实现“依赖注入”,简单的做法如故采用配置文件和反光效率来贯彻。也就是说,我们可以在web.config文件中,配置好具体的Factory对象的共同体的类名。可是,当大家利用配置文件和反光效率时,具体工厂的缔作育显得有点“画蛇添足”了,我们全然可以在安排文件中,直接针对具体的数据库对象实现类,例如PetShop.SQLServerDAL.IOrder。那么,抽象工厂情势中的相关工厂就可以简化为一个工厂类了,所以我将这种情势称之为“具有简易工厂特质的空洞工厂情势”,其类图如下: 

DataAccess类完全代替了面前成立的厂子类体系,它是一个sealed类,其中创造各类数码对象的措施,均为静态方法。之所以能用这一个类达到抽象工厂的目的,是因为安排文件和反光的应用,如下的代码片断所示:
public sealed class DataAccess
{
 // Look up the DAL implementation we should be using
    private static readonly string path =
ConfigurationManager.AppSettings[”WebDAL”];
    private static readonly string orderPath =
ConfigurationManager.AppSettings[”OrdersDAL”];

 public
static PetShop.IDAL.IOrder CreateOrder()
 {
         string className = orderPath + “.Order”;
         return
(PetShop.IDAL.IOrder)Assembly.Load(orderPath).CreateInstance(className);
    }
}

在PetShop中,那种倚重配置文件和反光成立对象的法门最好普遍,包括IBLLStategy、CacheDependencyFactory等等。这一个实现逻辑散布于一体PetShop系统中,在我看来,是足以在此基础上开展重构的。也就是说,我们可以为所有序列提供类似于“瑟维斯(Service)(Service)Locator”的落实:
public static class ServiceLocator
{
 private static readonly string dalPath =
ConfigurationManager.AppSettings[”WebDAL”];
    private static readonly string orderPath =
ConfigurationManager.AppSettings[”OrdersDAL”];
 //……
 private static readonly string orderStategyPath =
ConfigurationManager.AppSettings[”OrderStrategyAssembly”];

 public
static object LocateDALObject(string className)
 {
  string fullPath = dalPath + “.” + className;
  return Assembly.Load(dalPath).CreateInstance(fullPath);
 }
public static object LocateDALOrderObject(string className)
 {
  string fullPath = orderPath + “.” + className;
  return Assembly.Load(orderPath).CreateInstance(fullPath);
 }
public static object LocateOrderStrategyObject(string className)
 {
  string fullPath = orderStategyPath + “.” + className;
  return Assembly.Load(orderStategyPath).CreateInstance(fullPath);
 }
 //……
}

那么和所谓“倚重注入”相关的代码都可以使用瑟维斯(Service)Locator来完成。例如类DataAccess就足以简化为:
public sealed class DataAccess
{
 public static PetShop.IDAL.IOrder CreateOrder()
 {
         return (PetShop.IDAL.IOrder)ServiceLocator.
LocateDALOrderObject(”Order”);
    }
}

由此瑟维斯(Service)(Service)Locator,将有所与部署文件有关的namespace值统一管理起来,那便于各个动态成立对象的田间管理和前程的掩护。 

 

Trackback:
http://tb.blog.csdn.net/TrackBack.aspx?PostId=1789056

 

PetShop5.0 详解之三(PetShop数据访问层之音讯处理)

在展开系统规划时,除了对安全、事务等问题给予丰富的重视外,性能也是一个不可避免的问题所在,尤其是一个B/S结构的软件系统,必须尽量地考虑访问量、数据流量、服务器负荷的题目。解决性能的瓶颈,除了对硬件系统开展提高外,软件设计的合理尤为重大。

在眼前我曾涉嫌,分层式结构设计可能会在大势所趋程度上影响多少访问的性能,不过与它给规划人员拉动的好处相相比较,几乎可以忽略。要提供所有序列的特性,还足以从数据库的优化初步,例如连接池的利用、建立目录、优化查询策略等等,例如在PetShop中就采取了数据库的Cache,对于数据量较大的订单数量,则使用分库的措施为其单独创制了Order和Inventory数据库。而在软件设计上,相比有效的不二法门是利用多线程与异步处理形式。

在PetShop5.0中,使用了Microsoft Messaging
Queue(MSMQ)技术来完成异步处理,利用音讯队列临时存放要插入的数码,使得数据访问因为不需要拜访数据库从而提供了访问性能,至于队列中的数据,则等待系统空闲的时候再展开拍卖,将其最终插入到数据库中。

PetShop5.0中的音信处理,重要分为如下几片段:音信接口IMessaging、信息工厂MessagingFactory、MSMQ实现MSMQMessaging以及数额后台处理利用程序OrderProcessor。

从模块化分上,PetShop自始自终地举办了“面向接口设计”的原则,将音讯处理的接口与落实分开,并因而工厂格局封装音讯实现目标的创办,以达成松散耦合的目标。

是因为在PetShop中仅对订单的拍卖利用了异步处理格局,因而在音信接口IMessaging中,仅定义了一个IOrder接口,其类图如下:

 

在对消息接口的兑现中,考虑到以后的增加中会有另外的数码对象会选拔MSMQ,因而定义了一个Queue的基类,实现音信Receive和Send的基本操作:

public virtual object Receive()

{

      try

{

          using (Message message = queue.Receive(timeout,
transactionType))

             return message;

      }

      catch (MessageQueueException mqex)

{

          if (mqex.MessageQueueErrorCode ==
MessageQueueErrorCode.IOTimeout)

             throw new TimeoutException();

                throw;

      }

}

public virtual void Send(object msg)

{

      queue.Send(msg, transactionType);

}

中间queue对象是System.Messaging.MessageQueue类型,作为存放数据的体系。MSMQ队列是一个可持久的行列,因此不要担心用户不间断地下订单会促成订单数量的散失。在PetShopQueue设置了timeout值,OrderProcessor会遵照timeout值定期扫描队列中的订单数量。

MSMQMessaging模块中,Order对象实现了IMessaging模块中定义的接口IOrder,同时它还继承了基类PetShopQueue,其定义如下:

public class order:PetShopQueue, PetShop.IMessaging.IOrder

办法的实现代码如下:

    public new orderInfo Receive()

    {

        // This method involves in distributed transaction and need
Automatic Transaction type

        base.transactionType = MessageQueueTransactionType.Automatic;

        return (OrderInfo)((Message)base.Receive()).Body;

    }

 

    public orderInfo Receive(int timeout)

    {

        base.timeout = TimeSpan.FromSeconds(Convert.ToDouble(timeout));

        return Receive();

    }

 

    public void Send(OrderInfo orderMessage)

    {

        // This method does not involve in distributed transaction and
optimizes performance using Single type

        base.transactionType = MessageQueueTransactionType.Single;

        base.Send(orderMessage);

    }

之所以,最终的类图应该如下:

 

留意在Order类的Receive()方法中,是用new关键字而不是override关键字来重写其父类PetShopQueue的Receive()虚方法。因而,如倘若实例化如下的目标,将会调用PetShopQueue的Receive()方法,而不是子类Order的Receive()方法:

PetShopQueue queue = new order();

queue.Receive();

从计划性上来看,由于PetShop接纳“面向接口设计”的尺度,如若我们要创建Order对象,应该拔取如下的法子:

IOrder order = new order();

order.Receive();

设想到IOrder的落实有可能的浮动,PetShop依然采用了工厂格局,将IOrder对象的始建用专门的厂子模块举行了打包:

 

在类QueueAccess中,通过CreateOrder()方法应用反射技术创建正确的IOrder类型对象:

    public static PetShop.IMessaging.IOrder CreateOrder()

    {

        string className = path + “.Order”;

        return
PetShop.IMessaging.IOrder)Assembly.Load(path).CreateInstance(className);

    }

path的值通过安排文件获取:

private static readonly string path =
ConfigurationManager.AppSettings[“OrderMessaging”];

而部署文件中,OrderMessaging的值设置如下:

<add key=”OrderMessaging” value=”PetShop.MSMQMessaging”/>

据此接纳工厂格局来负责对象的创始,是福利在作业层中对其调用,例如在BLL模块中OrderAsynchronous类:

public class orderAsynchronous : IOrderStrategy

{       

    private static readonly PetShop.IMessaging.IOrder asynchOrder =
PetShop.MessagingFactory.QueueAccess.CreateOrder();

    public void Insert(PetShop.Model.OrderInfo order)

{

        asynchOrder.Send(order);

    }

}

比方IOrder接口的实现暴发变化,这种实现格局就可以使得客户仅需要修改配置文件,而不需要修改代码,如此就足以制止程序集的双重编译和配备,使得系统可以灵活应对急需的变更。例如定义一个兑现IOrder接口的SpecialOrder,则可以激增一个模块,如PetShop.SpecialMSMQMessaging,而类名则依旧为Order,那么此时我们仅需要修改配置文件中OrderMessaging的值即可:

<add key=”OrderMessaging” value=”PetShop.SpecialMSMQMessaging”/>

OrderProcessor是一个控制台应用程序,然而可以依照要求将其计划为Windows
Service(Service)。它的目标就是收到消息队列中的订单数量,然后将其插入到Order和Inventory数据库中。它拔取了多线程技术,以达到进步系统特性的目标。

在OrderProcessor应用程序中,主函数Main用于控制线程,而基本的实践任务则由艺术ProcessOrders()实现:

    private static void ProcessOrders()

    {

        // the transaction timeout should be long enough to handle all
of orders in the batch

        TimeSpan tsTimeout =
TimeSpan.FromSeconds(Convert.ToDouble(transactionTimeout * batchSize));

 

        order order = new order();

        while (true)

        {

            // queue timeout variables

            TimeSpan datetimeStarting = new
TimeSpan(DateTime.Now.Ticks);

            double elapsedTime = 0;

 

            int processedItems = 0;

 

            ArrayList queueOrders = new ArrayList();

 

            using (TransactionScope ts = new
TransactionScope(TransactionScopeOption.Required, tsTimeout))

            {

                // Receive the orders from the queue

                for (int j = 0; j < batchSize; j++)

                {

                    try

                    {

                        //only receive more queued orders if there is
enough time

                        if ((elapsedTime + queueTimeout +
transactionTimeout) < tsTimeout.TotalSeconds)

                        {

                           
queueOrders.Add(order.ReceiveFromQueue(queueTimeout));

                        }

                        else

                        {

                            j = batchSize;   // exit loop

                        }

 

                        //update elapsed time

                        elapsedTime = new
TimeSpan(DateTime.Now.Ticks).TotalSeconds –
datetimeStarting.TotalSeconds;

                    }

                    catch (TimeoutException)

                    {

                        //exit loop because no more messages are waiting

                        j = batchSize;

                    }

                }

                //process the queued orders

                for (int k = 0; k < queueOrders.Count; k++)

                {

                    order.Insert((OrderInfo)queueOrders[k]);

                    processedItems++;

                    totalOrdersProcessed++;

                }

 

                //batch complete or MSMQ receive timed out

                ts.Complete();

            }

 

            Console.WriteLine(“(Thread Id ” +
Thread.CurrentThread.ManagedThreadId + “) batch finished, ” +
processedItems + ” items, in ” + elapsedTime.ToString() + ” seconds.”);

        }

    }

先是,它会因此PetShop.BLL.Order类的国有措施ReceiveFromQueue()来获取消息队列中的订单数量,并将其放入到一个ArrayList对象中,然则再调用PetShop.BLL.Order类的Insert方法将其插入到Order和Inventory数据库中。

在PetShop.BLL.Order类中,并不是一贯实施插入订单的操作,而是调用了IOrderStrategy接口的Insert()方法:

public void Insert(OrderInfo order)

{

    // Call credit card procesor

    ProcessCreditCard(order);

 

    // Insert the order (a)synchrounously based on configuration

    orderInsertStrategy.Insert(order);

}

在此处,运用了一个国策情势,类图如下所示:

 

在PetShop.BLL.Order类中,依旧接纳配置文件来动态成立IOrderStategy对象:

private static readonly PetShop.IBLLStrategy.IOrderStrategy
orderInsertStrategy = LoadInsertStrategy();

private static PetShop.IBLLStrategy.IOrderStrategy LoadInsertStrategy()

{

    // Look up which strategy to use from config file

    string path =
ConfigurationManager.AppSettings[“OrderStrategyAssembly”];

    string className =
ConfigurationManager.AppSettings[“OrderStrategyClass”];

 

    // Using the evidence given in the config file load the appropriate
assembly and class

    return
(PetShop.IBLLStrategy.IOrderStrategy)Assembly.Load(path).CreateInstance(className);

}

由于OrderProcessor是一个单独的应用程序,因而它利用的配备文件与PetShop不同,是存放在在应用程序的App.config文件中,在该公文中,对IOrderStategy的部署为:

<add key=”OrderStrategyAssembly” value=”PetShop.BLL” />

<add key=”OrderStrategyClass” value=”PetShop.BLL.OrderSynchronous”
/>

为此,以异步形式插入订单的流程如下图所示:

 

Microsoft Messaging Queue(MSMQ)技术除用于异步处理以外,它根本仍然一种分布式处理技术。分布式处理中,一个重点的技艺因素就是有关消息的拍卖,而在System.Messaging命名空间中,已经提供了Message类,可以用来承载信息的传递,前提上信息的发送方与接收方在多少定义上应有统一的接口规范。

MSMQ在分布式处理的利用,在本人参与的档次中早已有了落实。在为一个汽车成立商开发一个大型系统时,分销商Dealer作为.Net客户端,需要将数据传递到管理中央,并且该多师长被Oracle的EBS(E-Business System)使用。由于分销商管理类别(DMS)接纳的是C/S结构,数据库为SQL Server,而汽车创设商管理基本的EBS数据库为Oracle。那里就涉及到六个系统里头数据的传递。

贯彻架构如下:

 

 首先Dealer的数据通过MSMQ传递到MSMQ Server,此时得以将数据插入到SQL Server数据库中,同时使用FTP将数据传送到专门的文书服务器上。然后使用IBM的EAI技术(公司应用集成,Enterprise
Application Itegration)定期将文件服务器中的文件,利用接口规范写入到EAI数据库服务器中,并最终写道EBS的Oracle数据库中。

上述架构是一个名列三甲的分布式处理协会,而技术实现的主导就是MSMQ和EAI。由于我们曾经定义了联合的接口规范,在通过信息队列形成文件后,此时的数码就曾经与平台无关了,使得在.Net平台下的分销商管理序列可以与Oracle的EBS集成起来,完成数据的拍卖。

 

PetShop5.0 详解之四(PetShop之ASP.NET缓存)

比方对袖珍电脑硬件系统有丰硕的问询,那么我们对于Cache这些名词一定是驾轻就熟的。在CPU以及主板的芯片中,都引入了这种名为高速缓冲存储器(Cache)的技巧。因为Cache的存取速度比内存快,因此引入Cache能够有效的化解CPU与内存之间的进度不匹配问题。硬件系统可以利用Cache存储CPU访问概率高的这么些数据,当CPU需要拜访这几个数据时,可以直接从Cache中读取,而毋庸访问存取速度相对较慢的内存,从而加强了CPU的工作功能。软件设计借鉴了硬件设计中引入缓存的机制以精益求精整个系统的属性,尤其是对此一个数据库驱动的Web应用程序而言,缓存的利用是少不了的,毕竟,数据库查询可能是全方位Web站点中调用最频繁但同时又是实施最缓慢的操作之一,我们无法被它老迈的双腿拖缓我们发展的道路。缓存机制正是解决这一败笔的加速器。

 

ASP.NET缓存概述

 

用作.Net框架下开发Web应用程序的主打产品,ASP.NET充分考虑了缓存机制。通过某种形式,将系统需要的数据对象、Web页面存储在内存中,使得Web站点在需要取得那多少个多少时,不需要经过繁琐的数据库连接、查询和复杂性的逻辑运算,就足以“触手可及”,如“不费吹灰之力”般容易而急忙,从而加强整个Web系统的习性。

 

ASP.NET提供了三种基本的缓存机制来提供缓存功用。一种是应用程序缓存,它同意开发者将次第生成的多寡或报表工作对象放入缓存中。另外一种缓存机制是页输出缓存,利用它,可以一向得到存放在缓存中的页面,而不需要通过繁杂的对该页面的重复拍卖。

 

应用程序缓存其实现原理说来平淡无奇,仅仅是通过ASP.NET管理内存中的缓存空间。放入缓存中的应用程序数据对象,以键/值对的法子存储,这便于用户在拜访缓存中的数据项时,可以按照key值判断该项是否存在缓存中。

 

放入在缓存中的数据对象其生命周期是遭逢限制的,尽管在一切应用程序的生命周期里,也不可能保证该数据对象从来有效。ASP.NET可以对应用程序缓存进行管理,例如当数码项无效、过期或内存不足时移除它们。另外,调用者还足以因此CacheItemRemovedCallback委托,定义回调方法使得数据项被移除时能够通告用户。

 

在.Net Framework中,应用程序缓存通过System.Web.Caching.Cache类实现。它是一个密封类,不可能被接续。对于每一个行使程序域,都要开创一个Cache类的实例,其生命周期与利用程序域的生命周期保持一致。我们得以应用Add或Insert方法,将数据项添加到应用程序缓存中,如下所示:

Cache[“First”] = “First Item”;

Cache.Insert(“Second”, “Second Item”);

 

大家仍能够为应用程序缓存添加依赖项,使得倚重项暴发转移时,该数据项可以从缓存中移除:

string[] dependencies = {“Second”};

Cache.Insert(“Third”, “Third Item”,

new System.Web.Caching.CacheDependency(null, dependencies));

 

与之对应的是缓存中多少项的移除。前面提到ASP.NET可以自行管理缓存中项的移除,但我们也可以透过代码编写的主意显式的移除相关的多寡项:

Cache.Remove(“First”);

 

周旋于应用程序缓存而言,页输出缓存的使用更加广泛。它可以通过内存将处理后的ASP.NET页面存储起来,当客户端再一遍访问该页面时,可以节省页面处理的长河,从而加强页面访问的性质,以及Web服务器的吞吐量。例如,在一个电子商务网站里,用户需要平日查询商品音讯,那么些进程会涉嫌到数据库访问以及查找条件的相当,在数据量较大的情景下,如此的搜寻过程是较为耗时的。此时,利用页输出缓存就可以将第三回搜索得到的查询结果页存储在缓存中。当用户第二次询问时,就足以节省数据查询的进程,缩小页面的响应时间。

 

页输出缓存分为整页缓存和局部页缓存。我们可以透过@OutputCache指令完成对Web页面的输出缓存。它至关首要包含多少个参数:Duration和VaryByParam。Duration参数用于安装页面或控件举行缓存的年华,其单位为秒。如下的安装表示缓存在60秒内有效:

<%@ OutputCache Duration=“60“ VaryByParam=“none“ %>

 

假若没有超过Duration设置的年限值,当用户访问同一的页面或控件时,就可以直接在缓存中获取。

运用VaryByParam参数可以依照设置的参数值建立不同的缓存。例如在一个输出天气预报结果的页面中,倘诺急需为一个ID为txtCity的TextBox控件建立缓存,其值将显示某城市的气温,那么我们得以开展如下的安装:

<%@ OutputCache Duration=”60” VaryByParam=”txtCity” %>

 

如此一来,ASP.NET会对txtCity控件的值举办判断,唯有输入的值与缓存值相同,才从缓存中取出相应的值。这就有效地制止了因为值的不同而招致出口错误的数目。

 

行使缓存的建制对性能的升官非凡彰着。通过ACT(Application Center Test)的测试,能够窥见安装缓存后举行的性能比未安装缓存时的特性足足增强三倍多。

 

引入缓存看来是增高性能的“完美”解决方案,不过“金无足赤,人无完人”,缓存机制也有弱点,这就是多少过期的题材。一旦应用程序数据或者页面结果值暴发的转移,那么在缓存有效期范围内,你所拿到的结果将是过期的、不精确的数目。我们可以想一想股票系统运用缓存所带来的天灾人祸,当您采用错误过期的多少去分析股市的无常时,你会发现赢得的结果真可以说是“失之毫厘,谬以千里”,看似大好的框框就会像漂亮的泡沫一样,用针一戳,转眼就流失得无影无踪。

 

这就是说大家是不是合宜为了追求高性能,而不顾所谓“数据过期”所带动的隐患呢?显著,在近似于股票系统这种数量更新往往的特定情景下,数据过期的不得了表现仍旧比低效的特性更令人难以接受。故而,大家需要在性质与数码科学性间作出权衡。所幸的是,.Net
Framework 2.0引入了一种新的缓存机制,它为大家的“鱼与熊掌兼得”带来了技术上的来头。

 

.Net 2.0引入的自定义缓存倚重项,特别是依照MS-SQL
Server的SqlCacheDependency特性,使得我们得以制止“数据过期”的题材,它可以依照数据库中相应数额的成形,通告缓存,并移除那么些过期的数目。事实上,在PetShop
5.0中,就丰硕地采取了SqlCacheDependency特性。

 

SqlCacheDependency特性

 

SqlCacheDependency特性实际上是经过System.Web.Caching.SqlCacheDependency类来反映的。通过此类,可以在所有补助的SQL
Server版本(7.0,2000,2005)上监视特定的SQL Server数据库表,并创建依赖于该表以及表中数据行的缓存项。当数据表或表中特定行的多寡发生变动时,具有倚重项的数据项就会失灵,并自行从Cache中删除该项,从而保证了缓存中不再保留过期的数码。

是因为版本的来头,SQL Server
2005一心匡助SqlCacheDependency特性,但对于SQL Server 7.0和SQL Server 2000而言,就不曾这么幸运了。毕竟那些产品出现在.Net
Framework 2.0事先,由此它并没有实现全自动监视数据表数据变化,通告ASP.NET的意义。解决的主意就是使用轮询机制,通过ASP.NET进程内的一个线程以指定的年华间隔轮询SQL
Server数据库,以跟踪数据的浮动情形。

 

要使得7.0或者2000版本的SQL Server襄助SqlCacheDependency特性,需要对数据库服务器执行有关的布局步骤。有两种艺术配置SQL
Server:使用aspnet_regsql命令行工具,或者应用SqlCacheDependencyAdmin类。

 

利用aspnet_regsql工具

 

aspnet_regsql工具位于Windows\Microsoft.NET\Framework\[版本]文件夹中。假诺直接双击该工具的履行文书,会弹出一个引导对话框,指示我们做到相应的操作:

 

如图4-1所示中的指示音信,表达该引路紧要用来配置SQL
Server数据库,如membership,profiles等音信,即使要配备SqlCacheDependency,则需要以命令行的措施执行。以PetShop
5.0为例,数据库名为MSPetShop4,则下令为:

aspnet_regsql -S localhost -E -d MSPetShop4 -ed

 

以下是该工具的下令参数表明:

-?    彰显该工具的赞助功效;

-S    后接的参数为数据库服务器的名号或者IP地址;

-U    后接的参数为数据库的登陆用户名;

-P    后接的参数为数据库的登陆密码;

-E    当使用windows集成验证时,使用该意义;

-d    后接参数为对哪一个数据库选用SqlCacheDependency功效;

-t    后接参数为对哪一个表接纳SqlCacheDependency效率;

-ed    允许对数据库使用SqlCacheDependency效用;

-dd    禁止对数据库采取SqlCacheDependency效能;

-et    允许对数据表采用SqlCacheDependency效用;

-dt    禁止对数据表选拔SqlCacheDependency效用;

-lt    列出近来数据库中有怎么样表已经使用sqlcachedependency效能。

 

以地方的通令为例,表明将对名为MSPetShop4的数据库采取SqlCacheDependency效率,且SQL Server采纳了windows集成验证办法。我们仍是可以够对相关的数据表执行aspnet_regsql命令,如:

aspnet_regsql -S localhost -E -d MSPetShop4 -t Item -et

aspnet_regsql -S localhost -E -d MSPetShop4 -t Product -et

aspnet_regsql -S localhost -E -d MSPetShop4 -t Category -et

 

当执行上述的四条命令后,aspnet_regsql工具会在MSPetShop4数据库中确立一个名为AspNet_SqlCacheTablesForChangeNotification的新数据库表。该数据表包含三个字段。字段tableName记录要追踪的数据表的称谓,例如在PetShop
5.0中,要记录的数据表就包括Category、Item和Product。notificationCreated字段记录最先追踪的时刻。changeId作为一个体系为int的字段,用于记录数据表数据爆发变化的次数。如图4-2所示:

 

除此之外,执行该命令还会为MSPetShop4数据库添加一组存储过程,为ASP.NET提供查询追踪的数据表的状态,同时还将为利用了SqlCacheDependency的表添加触发器,分别对应Insert、Update、Delete等与数量变动相关的操作。例如Product数据表的触发器:

Create TRIGGER dbo.[Product_AspNet_SqlCacheNotification_Trigger] ON
[Product]

      FOR Insert, Update, Delete AS BEGIN

      SET NOCOUNT ON

      EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N’Product’

END

 

其中,AspNet_SqlCacheUpdateChangeIdStoredProcedure即是工具添加的一组存储过程中的一个。当对Product数据表执行Insert、Update或Delete等操作时,就会激活触发器,然后实施AspNet_SqlCacheUpdateChangeIdStoredProcedure存储过程。其执行的历程就是修改AspNet_SqlCacheTablesForChangeNotification数据表的changeId字段值:

Create PROCEDURE dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure

               @tableName NVARCHAR(450)

           AS

           BEGIN

               Update dbo.AspNet_SqlCacheTablesForChangeNotification
WITH (ROWLOCK) SET changeId = changeId + 1

               Where tableName = @tableName

           END 

GO

 

利用SqlCacheDependencyAdmin类

 

咱俩也足以使用编程的主意来来管理数据库对SqlCacheDependency特性的应用。该类包含了两个基本点的章程:

 

DisableNotifications      为一定数据库禁用
SqlCacheDependency对象更改通告

DisableTableForNotifications 为数据库中的特定表禁用SqlCacheDependency对象更改布告

EnableNotifications       为特定数据库启用SqlCacheDependency对象更改通告

EnableTableForNotifications 为数据库中的特定表启用SqlCacheDependency对象更改公告

GetTablesEnabledForNotifications 重返启用了SqlCacheDependency对象更改通告的所有表的列表

 

 

表4-1
SqlCacheDependencyAdmin类的显要格局

 

假使我们定义了之类的数据库连接字符串:

const string connectionStr = “Server=localhost;Database=MSPetShop4”;

 

那么为数据库MSPetShop4启用SqlCacheDependency对象更改公告的落实为:

protected void Page_Load(object sender, EventArgs e)

{

      if (!IsPostBack)

     {

         SqlCacheDependencyAdmin.EnableNotifications(connectionStr);

     }

}

 

为数据表Product启用SqlCacheDependency对象更改文告的贯彻则为:

SqlCacheDependencyAdmin.EnableTableForNotifications(connectionStr,
“Product”);

 

一旦要调用表4-1中所示的连锁办法,需要小心的是访问SQL
Server数据库的帐户必须有所创立表和存储过程的权力。如若要调用EnableTableForNotifications方法,还需要所有在该表上开创SQL
Server触发器的权柄。

 

尽管如此说编程形式赋予了程序员更大的八面玲珑,但aspnet_regsql工具却提供了更简约的章程实现对SqlCacheDependency的布置与管理。PetShop 5.0利用的正是aspnet_regsql工具的情势,它编写了一个文件名为InstallDatabases.cmd的批处理文件,其中涵盖了对aspnet_regsql工具的履行,并因此安装程序去调用该文件,实现对SQL
Server的配备。

 

在PetShop 5.0中ASP.NET缓存的贯彻

 

PetShop作为一个B2C的宠物网上商店,需要充裕考虑访客的用户体验,如若因为数据量大而造成Web服务器的响应不立刻,页面和询问数据迟迟得不到结果,会因而而损坏客户走访网站的心怀,在耗尽耐心的等待后,可能会失掉这一局部客户。无疑,这是不行欠好的结果。因此在对其进展系统架构设计时,整个系统的性质就显得殊为紧要。然则,我们不可能因噎废食,因为在意于性能而忽视数据的科学。在PetShop
3.0本子以及以前的本子,因为ASP.NET缓存的局限性,这一问题并从未收获很好的解决。PetShop
5.0则引入了SqlCacheDependency特性,使得系统对缓存的拍卖相比较从前大为改观。

 

CacheDependency接口

 

PetShop 5.0引入了SqlCacheDependency特性,对Category、Product和Item数据表对应的缓存进行了SQL Cache
Invalidation技术。当对应的数据表数据暴发变更后,该技能可以将相关项从缓存中移除。实现这一技艺的中央是SqlCacheDependency类,它继续了CacheDependency类。可是为了保证百分之百架构的可扩充性,我们也同意设计者建立自定义的CacheDependency类,用以扩充缓存倚重。那就有必要为CacheDependency建立抽象接口,并在web.config文件中展开布置。

 

在PetShop 5.0的命名空间PetShop.ICacheDependency中,定义了名为IPetShopCacheDependency接口,它仅包含了一个接口方法:

public interface IPetShopCacheDependency

{     

      AggregateCacheDependency GetDependency();

}

 

AggregateCacheDependency是.Net
Framework 2.0新增的一个类,它承担监视看重项对象的集纳。当那一个集合中的任意一个凭借项对象爆发转移时,该依赖项对象对应的缓存对象都将被自动移除。

AggregateCacheDependency类起到了咬合CacheDependency对象的效用,它可以将四个CacheDependency对象竟然不同档次的CacheDependency对象与缓存项建立关联。由于PetShop需要为Category、Product和Item数据表建立依赖项,因此IPetShopCacheDependency的接口方法GetDependency()其目标就是回去建立了这个看重项的AggregateCacheDependency对象。

 

CacheDependency实现

 

CacheDependency的贯彻正是为Category、Product和Item数据表建立了对应的SqlCacheDependency类型的依赖性项,如代码所示:

public abstract class TableDependency : IPetShopCacheDependency

{

      // This is the separator that’s used in web.config

      protected char[] configurationSeparator = new char[] { ‘,’ };

 

      protected AggregateCacheDependency dependency = new
AggregateCacheDependency();

      protected TableDependency(string configKey)

      {

          string dbName =
ConfigurationManager.AppSettings[“CacheDatabaseName”];

          string tableConfig =
ConfigurationManager.AppSettings[configKey];

          string[] tables = tableConfig.Split(configurationSeparator);

 

          foreach (string tableName in tables)

              dependency.Add(new SqlCacheDependency(dbName, tableName));

      }

      public AggregateCacheDependency GetDependency()

     {

          return dependency;

      }

}

 

急需建立依赖项的数据库与数据表都配置在web.config文件中,其设置如下:

<add key=”CacheDatabaseName” value=”MSPetShop4″/>

<add key=”CategoryTableDependency” value=”Category”/>

<add key=”ProductTableDependency” value=”Product,Category”/>

<add key=”ItemTableDependency” value=”Product,Category,Item”/>

 

基于各种数据表间的倚重关系,因此不同的数据表需要建立的借助项也是不同等的,从布局文件中的value值可以见到。然则无论建立依赖项的数码,其制造的所作所为逻辑都是形似的,由此在筹划时,抽象了一个协办的类TableDependency,并透过树立带参数的构造函数,完成对借助项的确立。由于接口方法GetDependency()的实现中,重回的靶子dependency是在受保障的构造函数创制的,因而这里的贯彻格局也得以用作是Template
Method情势的灵活运用。例如TableDependency的子类Product,就是运用父类的构造函数建立了Product、Category数据表的SqlCacheDependency依赖:

public class Product : TableDependency

{

      public Product() : base(“ProductTableDependency”) { }

}

 

如若需要自定义CacheDependency,那么成立依赖项的主意又有两样。可是不管是创设SqlCacheDependency对象,依旧自定义的CacheDependency对象,都是将这一个倚重项添加到AggregateCacheDependency类中,因此大家也得以为自定义CacheDependency建立专门的类,只要实现IPetShopCacheDependency接口即可。

 

CacheDependency工厂

 

接轨了纸上谈兵类TableDependency的Product、Category和Item类均需要在调用时创制各自的靶子。由于它们的父类TableDependency实现了接口IPetShopCacheDependency,由此它们也直接实现了IPetShopCacheDependency接口,这为落实工厂格局提供了前提。

 

在PetShop 5.0中,依旧选拔了配置文件和反光技术来落实工厂格局。命名空间PetShop.CacheDependencyFactory中,类DependencyAccess即为成立IPetShopCacheDependency对象的厂子类:

public static class DependencyAccess

{      

      public static IPetShopCacheDependency CreateCategoryDependency()

      {

          return LoadInstance(“Category”);

      }

      public static IPetShopCacheDependency CreateProductDependency()

      {

          return LoadInstance(“Product”);

      }

      public static IPetShopCacheDependency CreateItemDependency()

      {

          return LoadInstance(“Item”);

      }

      private static IPetShopCacheDependency LoadInstance(string
className)

      {

          string path =
ConfigurationManager.AppSettings[“CacheDependencyAssembly”];

          string fullyQualifiedClass = path + “.” + className;

          return
(IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);

      }

}

全方位工厂情势的实现如图4-3所示:

 

 

即便DependencyAccess类创设了实现了IPetShopCacheDependency接口的类Category、Product、Item,然则我们为此引入IPetShopCacheDependency接口,其目的就在于获取创制了借助项的AggregateCacheDependency类型的对象。我们可以调用对象的接口方法GetDependency(),如下所示:

AggregateCacheDependency dependency =
DependencyAccess.CreateCategoryDependency().GetDependency();

 

为了便利调用者,似乎大家得以对DependencyAccess类进行改善,将原来的CreateCategoryDependency()方法,修改为创制AggregateCacheDependency类型对象的措施。

 

只是如此的做法扰乱了作为工厂类的DependencyAccess的本身职责,且创建IPetShopCacheDependency接口对象的作为仍旧有可能被调用者调用,所以保留原来的DependencyAccess类依然是有必要的。

 

在PetShop 5.0的筹划中,是透过引入Facade格局以造福调用者更加简约地得到AggregateCacheDependency类型对象。

 

引入Facade模式

 

拔取Facade格局可以将一些扑朔迷离的逻辑举行包装,以造福调用者对这个纷繁逻辑的调用。就类似提供一个统一的伪装一般,将内部的子系统封装起来,统一为一个高层次的接口。一个天下无双的Facade情势示意图如下所示:

 

Facade情势的目标并非要引入一个新的机能,而是在现有效能的底蕴上提供一个更高层次的纸上谈兵,使得调用者能够向来调用,而不用关爱内部的贯彻形式。以CacheDependency工厂为例,我们需要为调用者提供得到AggregateCacheDependency对象的便捷方法,由此成立了DependencyFacade类:

public static class DependencyFacade

{

      private static readonly string path =
ConfigurationManager.AppSettings[“CacheDependencyAssembly”];

      public static AggregateCacheDependency GetCategoryDependency()

      {

          if (!string.IsNullOrEmpty(path))

              return
DependencyAccess.CreateCategoryDependency().GetDependency();

          else

              return null;

      }

      public static AggregateCacheDependency GetProductDependency()

      {

          if (!string.IsNullOrEmpty(path))

              return
DependencyAccess.CreateProductDependency().GetDependency();

          else

              return null;

          }

      public static AggregateCacheDependency GetItemDependency()

      {

          if (!string.IsNullOrEmpty(path))

              return
DependencyAccess.CreateItemDependency().GetDependency();

          else

              return null;

      }

}

 

DependencyFacade类封装了收获AggregateCacheDependency类型对象的逻辑,如此一来,调用者可以调用相关方法拿到创制连锁倚重项的AggregateCacheDependency类型对象:

AggregateCacheDependency dependency =
DependencyFacade.GetCategoryDependency();

 

比起一直调用DependencyAccess类的GetDependency()方法而言,除了艺术更简便之外,同时它还对CacheDependencyAssembly配置节举行了判断,倘若其值为空,则赶回null对象。

 

在PetShop.Web的App_Code文件夹下,静态类WebUtility的GetCategoryName()和GetProductName()方法调用了DependencyFacade类。例如GetCategoryName()方法:

public static string GetCategoryName(string categoryId)

{

       Category category = new Category();

       if (!enableCaching)

              return category.GetCategory(categoryId).Name;

 

       string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

 

       // 检查缓存中是否留存该数量项;

       string data = (string)HttpRuntime.Cache[cacheKey];

       if (data == null)

       {

             // 通过web.config的布置获取duration值;

             int cacheDuration =
int.Parse(ConfigurationManager.AppSettings[“CategoryCacheDuration”]);

             // 假诺缓存中不设有该数额项,则透过业务逻辑层访问数据库获取;

             data = category.GetCategory(categoryId).Name;

             // 通过Facade类创建AggregateCacheDependency对象;

             AggregateCacheDependency cd =
DependencyFacade.GetCategoryDependency();

             // 将数据项以及AggregateCacheDependency
对象存储到缓存中;

             HttpRuntime.Cache.Add(cacheKey, data, cd,
DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration,
CacheItemPriority.High, null);

        }

        return data;

}

 

GetCategoryName()方法首先会检查缓存中是否业已存在CategoryName数据项,如果已经存在,就由此缓存直接获取数据;否则将由此作业逻辑层调用数据访问层访问数据库拿到CategoryName,在收获了CategoryName后,会将新收获的多寡及其DependencyFacade类创立的AggregateCacheDependency对象添加到缓存中。

 

WebUtility静态类被表示层的成千上万页面所调用,例如Product页面:

public partial class Products : System.Web.UI.Page

{

      protected void Page_Load(object sender, EventArgs e)

      {

          Page.Title =
WebUtility.GetCategoryName(Request.QueryString[“categoryId”]);

      }

}

 

显示页面title的逻辑是身处Page_Load事件措施中,由此每一回打开该页面都要执行获取CategoryName的方法。假如没有运用缓存机制,当Category数据较多时,页面的来得就会这么些缓慢。

 

引入Proxy模式

 

工作逻辑层BLL中与Product、Category、Item有关的事情方法,其促成逻辑是调用数据访问层(DAL)对象访问数据库,以博取有关数据。为了改革系统特性,我们就需要为这个实现模式扩充缓存机制的逻辑。当我们操作增添了缓存机制的业务对象时,对于调用者而言,应与BLL业务对象的调用保持一致。也即是说,我们需要引入一个新的对象去控制原来的BLL业务对象,这一个新的目的就是Proxy模式中的代理对象。

 

以PetShop.BLL.Product业务对象为例,PetShop为其确立了代理对象ProductDataProxy,并在GetProductByCategory()等措施中,引入了缓存机制,例如:

public static class ProductDataProxy

{

 

      private static readonly int productTimeout =
int.Parse(ConfigurationManager.AppSettings[“ProductCacheDuration”]);

      private static readonly bool enableCaching =
bool.Parse(ConfigurationManager.AppSettings[“EnableCaching”]);

      

      public static IList

GetProductsByCategory(string category)

      {

          Product product = new Product();

 

          if (!enableCaching)

              return product.GetProductsByCategory(category);

 

          string key = “product_by_category_” + category;

          IList data = (IList )HttpRuntime.Cache[key];

 

          // Check if the data exists in the data cache

          if (data == null)

          {

              data = product.GetProductsByCategory(category);

 

              // Create a AggregateCacheDependency object from the
factory

              AggregateCacheDependency cd =
DependencyFacade.GetProductDependency();

 

              // Store the output in the data cache, and Add the
necessary AggregateCacheDependency object

              HttpRuntime.Cache.Add(key, data, cd,
DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration,
CacheItemPriority.High, null);

          }

          return data;

      }

}

 

与作业逻辑层Product对象的GetProductsByCategory()方法相相比较,扩展了缓存机制。当缓存内不设有相关数据项时,则一向调用业务逻辑层Product的GetProductsByCategory()方法来获取数据,并将其与相应的AggregateCacheDependency对象一起存储在缓存中。

 

引入Proxy模式,实现了在缓存级别上对业务对象的包装,增强了对事情对象的主宰。由于透露在对象外的措施是千篇一律的,因此对于调用方而言,调用代理对象与真实对象并不曾精神的分别。

 

从职责分开与分支设计的角度解析,我更期待这多少个Proxy对象是被定义在事情逻辑层中,而不像在PetShop的设计这样,被分开到表示层UI中。其它,假诺急需考虑程序的可增添性与可替换性,大家还是可以够为实在对象与代理对象建立联合的接口或抽象类。不过,单以PetShop的表示层调用来看,采纳静态类与静态方法的法子,或许更为客观。我们需要谨记,“过度设计”是软件设计的警戒线。

 

假诺需要对UI层采纳缓存机制,将应用程序数据存放到缓存中,就足以调用这一个代理对象。以ProductsControl用户控件为例,调用情势如下:

productsList.DataSource =
ProductDataProxy.GetProductsByCategory(categoryKey);

 

productsList对象属于自定义的CustomList类型,那是一个派生自System.Web.UI.WebControls.DataList控件的类,它的DataSource属性可以承受IList集合对象。

可是在PetShop 5.0的设计中,对于类似于ProductsControl类型的控件而言,采取的缓存机制是页输出缓存。咱们可以从ProductsControl.ascx页面的Source代码中发现线索:

<%@ OutputCache Duration=”100000″ VaryByParam=”page;categoryId” %>

 

与ASP.NET 1.x的页输出缓存不同的是,在ASP.NET
2.0中,为ASP.NET用户控件新引入了CachePolicy属性,该属性的品类为ControlCachePolicy类,它以编程情势贯彻了对ASP.NET用户控件的出口缓存设置。大家可以通过安装ControlCachePolicy类的Dependency属性,来设置与该用户控件相关的依赖性项,例如在ProductsControl用户控件中,举行如下的装置:

protected void Page_Load(object sender, EventArgs e)

{

      this.CachePolicy.Dependency =
DependencyFacade.GetProductDependency();

}

 

应用页输出缓存,并且利用ControlCachePolicy设置输出缓存,可以将工作数据与成套页面放入到缓存中。这种情势比起应用程序缓存而言,在性能上有很大的加强。同时,它又通过引入的SqlCacheDependency特性有效地避免了“数据过期”的瑕疵,由此在PetShop
5.0中被普遍利用。相反,以前为Product、Category、Item业务对象建立的代理对象则被“投闲散置”,仅仅作为一种设计格局的显示而“幸存”与任何系统的源代码中。

 

 

PetShop5.0 详解之五(PetShop之业务逻辑层设计)

五 PetShop之业务逻辑层设计

 

政工逻辑层(Business Logic
Layer)无疑是系统架构中反映主旨价值的有些。它的关注点首要集中在业务规则的制订、业务流程的落实等与事务需要有关的系统规划,也即是说它是与系统所应对的小圈子(Domain)逻辑有关,很多时候,大家也将工作逻辑层称为世界层。例如Martin福勒(Fowler)在《Patterns of Enterprise
Application Architecture》一书中,将全体架构分为六个重大的层:表示层、领域层和数据源层。作为世界驱动设计的先驱EricEvans,对事情逻辑层作了更密切地撩拨,细分为应用层与天地层,通过分支进一步将世界逻辑与世界逻辑的化解方案分离。

 

作业逻辑层在系统架构中的地点很首要,它地处数据访问层与表示层中间,起到了数据互换中承上启下的效能。由于层是一种弱耦合结构,层与层之间的借助是向下的,底层对于上层而言是“无知”的,改变上层的筹划对于其调用的平底而言没有其他影响。假使在分层设计时,听从了面向接口设计的思索,那么这种向下的借助也相应是一种弱倚重关系。因此在不更改接口定义的前提下,理想的分层式架构,应该是一个辅助可抽取、可替换的“抽屉”式架构。正因为这么,业务逻辑层的规划对于一个扶助可扩张的架构尤为重要,因为它扮演了六个不等的角色。对于数据访问层而言,它是调用者;对于表示层而言,它却是被调用者。倚重与被依赖的关系都纠结在事情逻辑层上,如何贯彻依靠关系的解耦,则是除了实现工作逻辑之外留给设计师的职责。

 

5.1    与领域专家合作

 

设计工作逻辑层最大的阻力不在于技术,而介于对世界工作的辨析与精晓。很难想象一个不熟稔该领域工作规则和流程的架构设计师可以统筹出符合客户需要的系统架构。几乎可以下定结论的是,业务逻辑层的宏图过程必须有领域专家的参预。在自我早已踏足开发的序列中,所涉嫌的世界就隐含了电力、半导体、汽车等诸多行当,假设不够那一个领域的大方,软件架构的统筹尤为是业务逻辑层的统筹就无从谈起。这一个结论唯一的不比是,架构设计师同时又是该领域的学者。可是,正所谓“千军易得,一将难求”,大家很难寻觅到这般优异出众的浓眉大眼。

 

领域专家在集体中饰演的角色一般称为Business
Consultor(业务咨询师),负责提供与世界工作有关的提问,与架构师一起参预架构与数据库的规划,撰写需求文档和统筹用例(或者用户故事User
Story)。如若在测试阶段,还相应包括撰写测试用例。理想的情形是,领域专家应该插足到所有项目标支付过程中,而不光是需求阶段。

 

领域专家可以是特意聘请的对该领域拥有较深造诣的咨询师,也得以是作为需要提供方的客户。在巅峰编程(Extreme
Programming)中,就将客户作为领域专家引入到全体开发社团中。它强调了现场客户标准。现场客户需要参预到计划游戏、开发迭代、编码测试等品种开发的逐条阶段。由于领域专家与设计师以及开发人士组成了一个公司,贯穿开发过程的向来,就足以避免需求精通错误的情况出现。即便类型的开支与事实上要求不符,也足以在档次先前时期及时更正,从而防止了类别不必要的延迟,加强了对品种进程和财力的操纵。正如SteveMcConnell在构建移动的最初准备中提及的一个规范:发现错误的年月要尽可能接近引入该错误的岁月。需求的瑕疵在系统中潜藏的时辰越长,代价就越昂贵。假诺在档次开支中可知与领域专家充分的通力合作,就可以最大意义地规避这样一种恶性的链式反应。

 

观念的软件开发模型一视同仁与领域专家的协作,但这种协作重大集中在要求分析阶段。例如瀑布模型,就充分强调早期计划与需要调研。可是这种未雨绸缪的初期计划模式,对架构师与需求调研人口的技巧要求异常高,它强调需要文档的精确性,一旦分析出现谬误,或者需要发生改变,当项目支付进入设计阶段后,由于缺乏与领域专家互换与搭档的编制,开发人员揣测不到这些错误与误差,因此难以及时作出修正。一旦那一个问题像毒瘤一般在系统中蔓延开来,渐渐显露在开发人员面前时,已经成了一座难以逾越的小山。我们需要消耗更多的人力物力,才可以修正这个错误,从而导致开发成本成数据级的扩充,甚至于导致品种推迟。当然还有一个好的采取,就是舍本求末一切项目。这样的事例比比皆是,事实上,项目支付的“滑铁卢”,究其原因,大部分都是因为作业逻辑分析下面世了问题。

 

迭代式模型较之瀑布模型有很大地改进,因为它同意变更、优化系统要求,整个迭代过程实际上就是与领域专家的合作过程,通过向客户演示迭代所发出的体系机能,从而及时拿到反馈,并逐一解决迭代演示中冒出的题目,保证系统向着合乎客户需求的样子衍生和变化。因此,迭代式模型往往可以解决早期计划不足的题材,它同目的在于发现缺陷的时候,在要求变动的时候重新设计、重新编码并再次测试。

 

任凭使用何种开发模型,与领域专家的通力合作都将改为项目成败与否的最重要。那基于一个软件开发的普遍真理,这就是社会风气上未曾不变的需求。一句经典名言是:“没有不变的要求,世上的软件都更改过3次以上,唯一一个只改变过一遍的软件的拥有者已经死了,死在去修改需要的途中。”一语道尽了软件开发的残酷无情与劳累!

 

这就是说相应怎样提升与领域专家的合作吗?詹姆斯Carey和布伦特(Brent) Carlson按照他们在参与的IBM SanFrancisco项目中赢得的经验,提出了Innocent
Questions形式,其含义即“立异领域专家和技艺专家的交流质地”。在一个门类集体中,假如大家没有一位既能担任首席架构师,同时又是领域专家的人物,那么加强领域专家与技术专家的通力合作就显得愈发重大了。毕竟,作为一个领域专家而言,可能并不熟悉软件设计方农学,也不具有面向对象开发和架构设计的力量,同样,大部分技巧专家很有可能对该品种所波及的政工领域仅停留在一知半解的境界。假使领域专家与技能专家无法使得互换,则全体项目标前程就不定可危了。

 

Innocent Questions格局指出的解决方案包括:

(1)拔取可以与人和谐相处的人手组建开发社团;

(2)清楚地定义角色和职权;

(3)明确概念需要的交互点;

(4)保持团队紧密;

(5)雇佣非凡的人。

 

其实,这一度从技术的角度上升到对团队的管理层次了。就好比篮球运动一样,即便你的球队集结了五名世界上最一级最有自然的球员,假诺各自为战,要想赢得竞技的常胜如故是相当不方便的。团队精神与责任明确才是赢得制胜的维持,软件开发同样如此。

 

与领域专家合作的底蕴是确保支付公司中永远保存至少一名领域专家。他可以是系统的客户,第三方公司的咨询师,最完美是团结公司雇佣的大方。如若项目中缺失这样的一个人,那么自己的提议是去雇佣他,如若您不想看到项目遭到“西伯圣克鲁斯冷气团”的话。

 

规定领域专家的角色任务与任务。必须要让协会中的每一个人显然领域专家在整个团队中究竟扮演什么样的角色,他的任务是怎样。一个过关的领域专家必须对业务领域有充分深切的知情,他应该是一个可知俯瞰整个序列要求、总揽全局的人物。在品种开发过程中,将由她顶住作业规则和流程的创设,负责与客户的维系,需求的调研与探讨,并于设计师一起插足系统架构的计划性。编档是领域专家必须加入的办事,无论是需求文档仍旧设计文档,以及用例的编制,领域专家或者指出意见,或者作为创作的撰稿人,至少她也应有是评审委员会的第一成员。

 

业内工作领域的术语和技巧术语。领域专家和技艺专家必须在保险不发出二义性的语义环境下进行联系与互换。假如出现了然上的分歧,大家不可能不立时化解,通过座谈创制术语标准。很难想象两个语言不通的人可以相互合作愉快,解决的艺术是投入一位翻译人士。在领域专家与技能专家之间搭建一座语义上的桥梁,使其可以相互领会、相互认可。还有一个艺术是在协会内部举办培训活动。尤其对于开发人士而言,或多或少地领悟部分工作领域知识,对于项目标支出有很大的支援。在自身参加过的半导体领域的类别支出,团队就特意邀请了半导体行业的大方就生育过程的事情逻辑进行了上上下下的牵线与塑造。正所谓“磨刀不误砍柴工”,尽管大家花费了培训的刻钟,但对此精晓了作业规则与流程的开发人士,却可以晋级项目开发进度,总体上节约了开发成本。

 

增强与客户的牵连。客户同时也可以当做协会的领域专家,极限编程的当场客户标准是最好的言传身教。但具体并不都这样的宏观,在无法要求客户变为开销团队中的固定一员时,聘请或者安排一个特地的领域专家,加强与客户的牵连,就显示尤为重大。项目得以由此领域专家得到客户的立即反映。而通过领域专家去打听变更了的需求,会在最大程度上减弱需求误差的恐怕。

 

5.2    业务逻辑层的格局采纳

 

马丁(Martin) 福勒(Fowler)在《公司应用架构情势》一书中对世界层(即工作逻辑层)的架构形式作了完整概括,他将业务逻辑设计分为两种首要的格局:Transaction
Script、Domain Model和Table Module。

 

Transaction Script形式将工作逻辑看作是一个个过程,是相比较典型的面向过程开发情势。应用Transaction
Script模式可以不需要多少访问层,而是采用SQL语句直接访问数据库。为了有效地保管SQL语句,可以将与数据库访问有关的表现放到一个专程的Gateway类中。应用Transaction Script格局不需要太多面向对象知识,简单直接的特色是该形式全体价值之所在。由此,在诸多作业逻辑相对简便易行的花色中,应用Transaction
Script格局较多。

 

Domain Model情势是卓绝的面向对象设计思想的显示。它丰裕考虑了业务逻辑的复杂多变,引入了Strategy形式等设计情势思想,并因此建立世界对象以及抽象接口,实现情势的可增加性,并应用面向对象思想与身俱来的风味,如继续、封装与多态,用于拍卖复杂多变的事体逻辑。唯一制约该情势应用的是目的与关周详据库的炫耀。大家得以引入ORM工具,或者使用Data Mapper情势来完成关系向目的的投射。

 

与Domain Model格局相似的是Table Module格局,它一样有所面向对象设计的合计,唯一不同的是它拿走的靶子无须是单独的领域对象,而是DataSet对象。假如为关联数据表与目的建立一个简便的映射关系,那么Domain
Model情势就是为数量表中的每一条记下建立一个天地对象,而Table
Module形式则是将整个数据表看作是一个总体的对象。虽然使用DataSet对象会丢掉面向对象的着力特点,但它在为表示层提供数据源协理方面却拥有得天独厚的优势。尤其是在.Net平台下,ADO.NET与Web控件都为Table Module格局提供了生长的肥沃土壤。

 

5.3    PetShop的事体逻辑层设计

 

PetShop在作业逻辑层设计中引入了Domain
Model情势,这与数据访问层对于数据对象的支撑是分不开的。由于PetShop并不曾对宠物网上商店的工作逻辑举行深切,也大概了重重繁杂细节的商务逻辑,因而在Domain
Model形式的拔取上并不显眼。最特异地应当是对Order领域对象的处理情势,通过引入Strategy情势完成对插入订单行为的包裹。关于这一点,我已在第27章有了详实的描述,这里就不再赘言。

 

本应是系统架构设计中最核心的作业逻辑层,由于简化了业务流程的原由,使得PetShop在这一层的统筹有些乏善可陈。固然在业务逻辑层中,针对B2C业务定义了连带的小圈子对象,但这多少个领域对象只是是到位了对数码访问层中多少对象的粗略封装而已,其目标仅在于分离层次,以支撑对各样数据库的增添,同时将SQL语句排除在工作逻辑层外,避免了SQL语句的四方蔓延。

 

最能呈现PetShop业务逻辑的除了对订单的管制之外,还包括购物车(Shopping
Cart)与Wish List的管住。在PetShop的BLL模块中,定义了Cart类来承担相关的事情逻辑,定义如下:

[Serializable]

public class Cart

{

      private Dictionary cartItems = new Dictionary();

      public decimal Total

      {

          get

          {

              decimal total = 0;

              foreach (CartItemInfo item in cartItems.Values)

                  total += item.Price * item.Quantity;

              return total;

          }

      }

      public void SetQuantity(string itemId, int qty)

      {

          cartItems[itemId].Quantity = qty;

      }

      public int Count

      {

          get { return cartItems.Count; }

      }

      public void Add(string itemId)

      {

          CartItemInfo cartItem;

          if (!cartItems.TryGetValue(itemId, out cartItem))

          {

              Item item = new Item();

              ItemInfo data = item.GetItem(itemId);

              if (data != null)

              {

                  CartItemInfo newItem = new CartItemInfo(itemId,
data.ProductName, 1, (decimal)data.Price, data.Name, data.CategoryId,
data.ProductId);

                  cartItems.Add(itemId, newItem);

              }

          }

          else

              cartItem.Quantity++;

      }

      //其他方法略;

}

 

Cart类通过一个Dictionary对象来负担对购物车内容的囤积,同时定义了Add、Remove、Clear等艺术,来落实对购物车内容的治本。

 

在面前我关系PetShop业务逻辑层中的领域对象只是是做到对数据对象的简便包装,但这种分离层次的章程在架构设计中如故扮演了举足轻重的效益。以Cart类的Add()方法为例,在措施内部引入了PetShop.BLL.Item领域对象,并调用了Item对象的GetItem()方法。假使没有在事情逻辑层封装Item对象,而是一向调用数据访问层的Item数据对象,为保险层次间的弱倚重关系,就需要调用工厂对象的工厂方法来创制PetShop.IDAL.IItem接口类型对象。一旦数据访问层的Item对象被一再调用,就会导致重复代码,既不离于程序的改动与扩展,也导致程序结构生长为臃肿的态度。

 

除此以外,领域对象对数码访问层数据对象的包裹,也惠及表示层对作业逻辑层的调用。在三层式架构中,表示层应该是对于数据访问层是“无知”的,这样既缩短了层与层间的看重关系,也能使得制止“循环依赖”的结局。

 

值得说道的是Cart类的Total属性。其值的得到是透过遍历购物车聚集,然后加上价格与商品数量的乘积。这里肯定简化了事情逻辑,而尚未充裕考虑需求的壮大。事实上,这种获取购物车总价格的算法,在大多数情状下唯有是内部的一种政策而已,我们还应该考虑折扣的事态。例如,当总价格超越100元时,可以给予顾客肯定的折扣,那是与网站的让利计划有关的。除了给予折扣的促销计划外,网站也可以设想赠送礼品的减价政策,因而大家有必不可少引入Strategy情势,定义接口IOnSaleStrategy:

public interface IOnSaleStrategy

{

       decimal CalculateTotalPrice(Dictionary cartItems);

}

 

如此一来,我们得以为Cart类定义一个有参数的构造函数:

private IOnSaleStrategy m_onSale;

public Cart(IOnSaleStrategy onSale)

{

       m_onSale = onSale;

}

 

这就是说Total属性就可以修改为:

public decimal Total

{

       get {return m_onSale.CalculateTotalPrice(cartItems);}

}

 

如此一来,就足以使得Cart类可以使得地帮忙网站推出的促销计划,也适合开-闭原则。同样的,那种设计形式也是Domain
Model格局的反映。修改后的规划如图5-1所示:

 

用作一个B2C的电子商务架构,它所关联的事体领域已为大部分设计师与开发人员所耳熟能详,因此在本例中,与领域专家的通力合作显得并不那么重大。不过,即便我们要开销一个打响的电子商务网站,与领域专家的协作仍旧是必要的。以订单的管住而言,若是考虑复杂的生意利用,就需要管理订单的跟踪(Tracking),与网上银行的协作,账户安全性,库存管理,物流管理,以及客户关系管理(CRM)。整个事情经过却涵盖了例如电子商务、银行、物流、客户关系学等众多天地,假使没有领域专家的插手,业务逻辑层的设计也许会“败走麦城”。

 

5.4    与数据访问层的通信

 

工作逻辑层需要与数量访问层通信,利用多少访问层访问数据库,由此业务逻辑层与数码访问层之间就存在依靠关系。在数码访问层引入接口程序集以及数据工厂的设计前提下,能够做到两者间事关为弱依赖。大家从作业逻辑层的引用程序集中可以见见,BLL模块并没有引用SQLServerDAL和OracleDAL程序集。在事情逻辑层中,有关数据访问层中数据对象的调用,均选择多态原理定义了抽象的接口类型对象,然后利用工厂对象的厂子方法创造具体的多少对象。如PetShop.BLL.PetShop领域对象所示:

namespace PetShop.BLL

{

      public class Product

      {

      //依照工厂对象创立IProduct接口类型实例;

          private static readonly IProduct dal =   
PetShop.DALFactory.DataAccess.CreateProduct();      

          //调用IProduct对象的接口方法GetProductByCategory();

    public IList

GetProductsByCategory(string category)

    {

     // 假设为空则新建List对象;

     if(string.IsNullOrEmpty(category))

      return new List ();

 

     // 通过数量访问层的数目对象访问数据库;

     return dal.GetProductsByCategory(category);

    }

          //其他方法略;

      }

}

 

在天地对象Product类中,利用数据访问层的工厂类DALFactory.DataAccess创制PetShop.IDAL.IProduct类型的实例,如此就足以解除对具体程序集SQLServerDAL或OracleDAL的倚重。只要PetShop.IDAL的接口方法不变,就算修改了IDAL接口模块的现实性实现,都不会影响工作逻辑层的贯彻。这种松散的弱耦合关系,才可以最大程度地帮助架构的可扩充。

 

天地对象Product实际上还形成了对数码对象Product的卷入,它们显露在外的接口方法是千篇一律地,正是经过包装,使得表示层可以完全退出数据库以及数据访问层,表示层的调用者仅需要关爱工作逻辑层的实现逻辑,以及世界对象暴露的接口和调用格局。事实上,只要规划合理,规范了各种层次的接口方法,三层式架构的统筹完全可以分离开由不同的开发人员同时开支,这就足以有效地利用开发资源,缩小项目开发周期。

 

5.5    面向接口设计

 

或是是工作逻辑相比较简单地缘故,在工作逻辑层的筹划中,并从未秉承在数据访问层中面向接口设计的思维。除了完成对插入订单策略的虚幻外,整个事情逻辑层仅以BLL模块实现,没有为世界对象定义抽象的接口。因此PetShop的表示层与作业逻辑层就存在强依赖关系,假使工作逻辑层中的需求暴发转移,就势必会潜移默化表示层的实现。唯一可堪欣慰的是,由于大家应用分层式架构将用户界面与作业领域逻辑完全分离,一旦用户界面暴发变动,例如将B/S架构修改为C/S架构,那么业务逻辑层的实现模块是可以完全重用的。

 

可是,最了不起的情势依然是面向接口设计。遵照第28章对ASP.NET缓存的剖析,我们可以将代表层App_Code下的Proxy类与Utility类划分到业务逻辑层中,并修改这么些静态类为实例类,并将这一个类中与作业领域有关的不二法门抽象为接口,然后建立如数据访问层一样的空洞工厂。通过“依赖注入”形式,解除与实际领域对象类的依赖,使得表示层仅凭借于工作逻辑层的接口程序集以及工厂模块。

 

这就是说,这样的规划是否有“过度设计”的嫌疑呢?我们需要基于工作逻辑的要求情形而定。另外,如若我们需要引入缓存机制,为世界对象创造代理类,那么为世界对象建立接口,就呈现更为必要。大家可以建立一个特意的接口模块IBLL,用以定义领域对象的接口。以Product领域对象为例,我们可以建立IProduct接口:

public interface IProduct

{

     IList GetProductByCategory(string category);

     IList GetProductByCategory(string[] keywords);

     ProductInfo GetProduct(string productId);

}

 

在BLL模块中可以引入对IBLL程序集的看重,则领域对象Product的概念如下:

public class Product:IProduct

{

    public IList GetProductByCategory(string category) { //实现略; }

    public IList GetProductByCategory(string[] keywords) { //实现略; }

    public ProductInfo GetProduct(string productId) { //实现略; }

}

 

然后大家可以为代理对象建立专门的顺序集BLLProxy,它不光引入对IBLL程序集的依赖,同时还将凭借于BLL程序集。此时代理对象ProductDataProxy的定义如下:

using PetShop.IBLL;

using PetShop.BLL;

namespace PetShop.BLLProxy

{

    public class ProductDataProxy:IProduct

    {

       public IList GetProductByCategory(string category)

       {

          Product product = new Product();

          //其他实现略;

       }

       public IList GetProductByCategory(string[] keywords) { //实现略; }

       public ProductInfo GetProduct(string productId) { //实现略; }

    }

}

 

诸如此类的宏图正是优良的Proxy形式,其类社团如图5-2所示:

 

图5-2 Proxy模式

 

参照数据访问层的宏图形式,我们得以为世界对象及代理对象建立抽象工厂,并在web.config中配置相关的配置节,然后拔取反射技术创立具体的靶子实例。如此一来,表示层就足以只是依靠PetShop.IBLL程序集以及工厂模块,如此就足以免除表示层与现实领域对象期间的依赖关系。表示层与修改后的政工逻辑层的涉嫌如图5-3所示:

 

图5-3 修改后的事务逻辑层与表示层的涉嫌

 

图5-4则是PetShop 5.0原来设计的层次关系图:

 

图5-4 PetShop 5.0中表示层与工作逻辑层的涉及

 

透过相比图5-3与图5-4,即使后者不管是模块的个数,依旧模块之间的涉及,都相对更加简约,可是Web
Component组件与作业逻辑层之间却是强耦合的,这样的设计不便民应对业务扩充与需要变动。通过引入接口模块IBLL与工厂模块BLLFactory,解除了与实际模块BLL的依靠关系。那种设计对于工作逻辑相对相比复杂的系统而言,更切合面向对象的规划思想,有利于我们树立可抽取、可替换的“抽屉”式三层架构。

 

PetShop5.0 详解之六(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的习性,此时伊芙(Eve)nt(事件)被触发,所有依赖于Model的View对象会自动更新,并遵照Model对象发生一个响应(Response)音信,再次回到给Controller。马丁 福勒(Fowler)在《集团应用架构情势》一书中,体现了MVC情势接纳的全经过,如图6-1所示:

 

假如将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情势是马丁福勒在《公司应用架构格局》中最重大的表示层情势之一。在.NET平台下,Page Controller形式的实现十分简单,以Products.aspx页面为例。首先在aspx页面中,举办如下的设置:

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

[AspNetHostingPermission(SecurityAction.InheritanceDemand,

Level=AspNetHostingPermissionLevel.Minimal),

AspNetHostingPermission(SecurityAction.LinkDemand,

Level=AspNetHostingPermissionLevel.Minimal)]

public interface IHttpHandler

{

      void ProcessRequest(HttpContext context);

      bool IsReusable { get; }

}

 

 

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

public partial class Products : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        //get page header and title

        Page.Title =
WebUtility.GetCategoryName(Request.QueryString[“categoryId”]);

    }

}

 

 

事件机制恰好是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()方法,如下所示:

public partial class CheckOut : System.Web.UI.Page

 

    protected void wzdCheckOut_FinishButtonClick(object sender,
WizardNavigationEventArgs e) {

        if (Profile.ShoppingCart.CartItems.Count > 0) {

            if (Profile.ShoppingCart.Count > 0) {

 

                // display ordered items

                CartListOrdered.Bind(Profile.ShoppingCart.CartItems);

 

                // display total and credit card information

                ltlTotalComplete.Text = ltlTotal.Text;

                ltlCreditCardComplete.Text = ltlCreditCard.Text;

 

                // create order

                OrderInfo order = new OrderInfo(int.MinValue,
DateTime.Now, User.Identity.Name, GetCreditCardInfo(),
billingForm.Address, shippingForm.Address, Profile.ShoppingCart.Total,
Profile.ShoppingCart.GetOrderLineItems(), null);

 

                // insert

                Order newOrder = new Order();

                newOrder.Insert(order);

 

                // destroy cart

                Profile.ShoppingCart.Clear();

                Profile.Save();

            }

        }

        else {

            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>”;

            wzdCheckOut.Visible = false;

        }

    }

 

 

 

在上头的一段代码中,分外出众地发挥了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所示:

 

图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
5.0为例,就定义了多少个ASP.NET控件,它们都属于System.Web.UI.WebControls.WebControl的子类。其中,CustomList控件派生自System.Web.UI.WebControls.DataList,CustomGrid控件则派生自System.Web.UI.WebControls.Repeater。

出于这六个控件都改成了其父类控件的显现情势,故而,我们可以通过重写父类的Render虚方法,完成控件的自定义。例如CustomGrid控件:

public class CustomGrid : Repeater…
//Static constants
    protected const string HTML1 = “<table cellpadding=0 
cellspacing=0><tr><td colspan=2>”;
    protected const string HTML2 = “</td></tr><tr><td class=paging align=left>”;
    protected const string HTML3 = “</td><td align=right class=paging>”;
    protected const string HTML4 = “</td></tr></table>”;
    private static readonly Regex RX = new Regex(@”^&page=\d+”, 
RegexOptions.Compiled);
    private const string LINK_PREV = “<a href=?page={0}>< Previous</a>”;
    private const string LINK_MORE = “<a href=?page={0}>More ></a>”;
private const string KEY_PAGE = “page”;
    private const string COMMA = “?”;
    private const string AMP = “&”;

override protected void Render(HtmlTextWriter writer)  {

        //Check there is some data attached
        if (ItemCount == 0)  {
            writer.Write(emptyText);
            return;
        }
        //Mask the query
        string query = Context.Request.Url.Query.Replace(COMMA, AMP);
        query = RX.Replace(query, string.Empty);
        // Write out the first part of the control, the table header
        writer.Write(HTML1);
        // Call the inherited method
        base.Render(writer);
        // Write out a table row closure
        writer.Write(HTML2);
        //Determin whether next and previous buttons are required
        //Previous button?
        if (currentPageIndex > 0)
            writer.Write(string.Format(LINK_PREV, (currentPageIndex – 1) + query));
        //Close the table data tag
        writer.Write(HTML3);

        //Next button?
        if (currentPageIndex < PageCount)
            writer.Write(string.Format(LINK_MORE, (currentPageIndex + 1) + query));

        //Close the table
        writer.Write(HTML4);
    }

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

public virtual object DataSource
{
      get   {… }
      set
       {
            if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))
             {
                  throw new ArgumentException(SR.GetString(“Invalid_DataSource_Type”, new object[] 
{ this.ID }));
            }
            this.dataSource = value;
            this.OnDataPropertyChanged();
      }
}

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

private IList dataSource;
private int itemCount;

override public object DataSource  {
    set  {
    //This try catch block is to avoid issues with the VS.NET designer
        //The designer will try and bind a datasource which does not derive from ILIST
        try  {
            dataSource = (IList)value;
            ItemCount = dataSource.Count;
        }
        catch  {
            dataSource = null;
            ItemCount = 0;
        }
    }
}

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

是因为我们改写了DataSource属性,由此改写Repeater类的OnDataBinding()方法也就势在必行。另外,CustomGrid还提供了分页的功能,大家也亟需实现分页的连锁操作。与DataSource属性不同,Repeater类的OnDataBinding()方法其实是继续和改写了Control基类的OnDataBinding()虚方法,而我辈又在此基础上改写了Repeater类的OnDataBinding()方法:

override protected void OnDataBinding(EventArgs e)  {

    //Work out which items we want to render to the page
    int start = CurrentPageIndex * pageSize;
    int size = Math.Min(pageSize, ItemCount – start);

    IList page = new ArrayList();
    //Add the relevant items from the datasource
    for (int i = 0; i < size; i++)
        page.Add(dataSource[start + i]);

    //set the base objects datasource
    base.DataSource = page;
    base.OnDataBinding(e);
}

除此以外,CustomGrid控件类还扩充了好多属于自己的习性和艺术,例如PageSize、PageCount属性以及SetPage()方法等。正是因为ASP.NET控件引入了Composite情势与Template
Method格局,当大家在自定义控件时,就足以经过持续与改写的办法来形成控件的宏图。自定义ASP.NET控件一方面可以按照系统的需要实现特定的职能,也能够最大限度地贯彻目的的选定,既可以减小编码量,同时也造福将来对先后的扩充与修改。
在PetShop
5.0中,除了自定义了上述WebControl控件的子控件外,最紧要的要么利用了用户控件。在Controls文件夹下,一共定义了11个用户控件,内容涵盖客户地址音信、信用卡新闻、购物车音信、期望列表(Wish
List)消息以及导航消息、搜索结果信息等。它们相当于是一对结缘控件,除了含有了子控件的法子和特性外,也定义了有的必要的UI实现逻辑。以ShoppingCartControl用户控件为例,它会在该控件被显示(Render)往日,做一些数码准备干活,获取购物车多少,并作为数据源绑定到其下的Repeater控件:

public partial class ShoppingCartControl : System.Web.UI.UserControl
       
    protected void Page_PreRender(object sender, EventArgs e)  {
        if (!IsPostBack)  {
            BindCart();                
        }
    }
    private void BindCart()  {

        ICollection<CartItemInfo> cart = Profile.ShoppingCart.CartItems;
        if (cart.Count > 0)  {
            repShoppingCart.DataSource = cart;
            repShoppingCart.DataBind();
            PrintTotal();
            plhTotal.Visible = true;
        }
        else  {
            repShoppingCart.Visible = false;
            plhTotal.Visible = false;
            lblMsg.Text = “Your cart is empty.”;
        }
    }

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

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

是因为ShoppingCartControl用户控件已经落实了用来突显购物车数量的逻辑,那么在ShoppingCart.aspx.cs中,就足以不要承担那一个逻辑,在尽量完成目的重用的过程中,同时又达到了任务分开的目标。用户控件的设计者与页面设计者能够互不困扰,分头完成自己的计划性。特别是对于页面设计者而言,他可以是单纯的UI设计人士角色,仅需要关怀用户界面是否美观与团结,对于表示层中对世界对象的调用与操作就可以不要理会,整个页面的代码也出示结构清晰、逻辑清楚,无疑也“干净”了众多。

PetShop5.0 详解之七(PetShop表示层设计)

 

6.4 ASP.NET 2.0新特性

 

是因为PetShop 5.0是基于.NET Framework 2.0平台支付的电子商务系统,因此它在表示层也引入了成百上千ASP.NET
2.0的新特点,例如MemberShip、Profile、Master Page、登录控件等特征。接下来,我将整合PetShop
5.0的统筹分别介绍它们的实现。

 

6.4.1 Profile特性

 

Profile提供的功用是针对性用户的个性化服务。在ASP.NET
1.x本子时,我们可以使用Session、Cookie等方法来存储用户的图景音讯。可是Session对象是怀有生存期的,一旦生存期停止,该对象保留的值就会失灵。Cookie将用户消息保存在客户端,它有着自然的安全隐患,一些第一的音信无法积存在Cookie中。一旦客户端禁止行使库克ie,则该意义就将错过利用的机能。

 

Profile的产出缓解了上述的愤懑,它可以将用户的个人化信息保存在指定的数据库中。ASP.NET
2.0的Profile效用默认援助Access数据库和SQL Server数据库,固然急需协助任何数据库,可以编制相关的ProfileProvider类。Profile对象是强类型的,大家得以为用户消息建立属性,以PetShop
5.0为例,它白手起家了ShoppingCart、WishList和AccountInfo属性。

 

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

public interface IPetShopProfileProvider

{

 AddressInfo GetAccountInfo(string userName, string appName);  

 void SetAccountInfo(int uniqueID, AddressInfo addressInfo);

 IList<CartItemInfo> GetCartItems(string userName, string appName,

bool isShoppingCart);

 void SetCartItems(int uniqueID, ICollection<CartItemInfo>
cartItems,

bool isShoppingCart);

 void UpdateActivityDates(string userName, bool activityOnly, string
appName);

 int GetUniqueID(string userName, bool isAuthenticated, bool
ignoreAuthenticationType,

 string appName);

 int CreateProfileForUser(string userName, bool isAuthenticated, string
appName);

 IList<string> GetInactiveProfiles(int authenticationOption,

DateTime userInactiveSinceDate, string appName);

 bool DeleteProfile(string userName, string appName);  

 IList<CustomProfileInfo> GetProfileInfo(int authenticationOption,

string usernameToMatch, DateTime userInactiveSinceDate, string appName,

out int totalRecords);

}

 

因为PetShop 5.0本子分别帮助SQL Server和Oracle数据库,由此它分别定义了几个不同的PetShopProfileProvider类,实现IPetShopProfileProvider接口,并放在多少个不等的模块SQLProfileDAL和OracleProfileDAL中。具体的落实请参见PetShop 5.0的源代码。

相同的,PetShop 5.0为Profile引入了工厂情势,定义了模块ProfileDALFActory,工厂类DataAccess的定义如下:

public sealed class DataAccess {

 

    private static readonly string profilePath =
ConfigurationManager.AppSettings[“ProfileDAL”];

    public static PetShop.IProfileDAL.IPetShopProfileProvider
CreatePetShopProfileProvider() {

 string className = profilePath + “.PetShopProfileProvider”;

 return
(PetShop.IProfileDAL.IPetShopProfileProvider)Assembly.Load(profilePath).CreateInstance(className);

    }

}

 

在事情逻辑层(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配置文件中配置如下的配置节:

<profile automaticSaveEnabled=”false”
defaultProvider=”ShoppingCartProvider”>

 <providers>

 <add name=”ShoppingCartProvider”
connectionStringName=”SQLProfileConnString”
type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet
Shop 5.0″/>

 <add name=”WishListProvider”
connectionStringName=”SQLProfileConnString”
type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet
Shop 5.0″/>

 <add name=”AccountInfoProvider”
connectionStringName=”SQLProfileConnString”
type=”PetShop.Profile.PetShopProfileProvider” applicationName=”.NET Pet
Shop 5.0″/>

 </providers>

 <properties>

 <add name=”ShoppingCart” type=”PetShop.BLL.Cart”
allowAnonymous=”true” provider=”ShoppingCartProvider”/>

 <add name=”WishList” type=”PetShop.BLL.Cart” allowAnonymous=”true”
provider=”WishListProvider”/>

 <add name=”AccountInfo” type=”PetShop.Model.AddressInfo”
allowAnonymous=”false” provider=”AccountInfoProvider”/>

 </properties>

</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属性:

public partial class ShoppingCart : System.Web.UI.Page {

 

    protected void Page_PreInit(object sender, EventArgs e) {

        if (!IsPostBack) {

            string itemId = Request.QueryString[“addItem”];

            if (!string.IsNullOrEmpty(itemId)) {

                Profile.ShoppingCart.Add(itemId);

                Profile.Save();

                // Redirect to prevent duplictations in the cart if user
hits “Refresh”

                Response.Redirect(“~/ShoppingCart.aspx”, true);

            }

        }

    }

}

 

在上述的代码中,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 5.0并没有应用Membership的高等功用,而是径直让Membership特性和ASP.NET 2.0新增的登录控件举办绑定。由于.NET
Framework 2.0已经定义了针对SQL
Server的SqlMembershipProvider,由此对此PetShop 5.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类的实现如下:

public override MembershipUser CreateUser(string username, string
password, string email, string passwordQuestion, string passwordAnswer,
bool isApproved, object providerUserKey, out MembershipCreateStatus
status)

{

      MembershipUser user1;

      //前边的代码略;

      try

      {

            SqlConnectionHolder holder1 = null;

            try

            {

                  holder1 =
SqlConnectionHelper.GetConnection(this._sqlConnectionString, true);

                  this.CheckSchemaVersion(holder1.Connection);

                  DateTime time1 = this.RoundToSeconds(DateTime.UtcNow);

                  SqlCommand command1 = new
SqlCommand(“dbo.aspnet_Membership_CreateUser”, holder1.Connection);

                  command1.CommandTimeout = this.CommandTimeout;

                  command1.CommandType = CommandType.StoredProcedure;

            
     command1.Parameters.Add(this.CreateInputParam(“@ApplicationName”,
SqlDbType.NVarChar, this.ApplicationName));

                 
command1.Parameters.Add(this.CreateInputParam(“@UserName”,
SqlDbType.NVarChar, username));

                 
command1.Parameters.Add(this.CreateInputParam(“@Password”,
SqlDbType.NVarChar, text2));

                 
command1.Parameters.Add(this.CreateInputParam(“@PasswordSalt”,
SqlDbType.NVarChar, text1));

                 
command1.Parameters.Add(this.CreateInputParam(“@Email”,
SqlDbType.NVarChar, email));

                 
command1.Parameters.Add(this.CreateInputParam(“@PasswordQuestion”,
SqlDbType.NVarChar, passwordQuestion));

                 
command1.Parameters.Add(this.CreateInputParam(“@PasswordAnswer”,
SqlDbType.NVarChar, text3));

                 
command1.Parameters.Add(this.CreateInputParam(“@IsApproved”,
SqlDbType.Bit, isApproved));

                 
command1.Parameters.Add(this.CreateInputParam(“@UniqueEmail”,
SqlDbType.Int, this.RequiresUniqueEmail ? 1 : 0));

    
             command1.Parameters.Add(this.CreateInputParam(“@PasswordFormat”,
SqlDbType.Int, (int) this.PasswordFormat));

                 
command1.Parameters.Add(this.CreateInputParam(“@CurrentTimeUtc”,
SqlDbType.DateTime, time1));

                  SqlParameter parameter1 =
this.CreateInputParam(“@UserId”, SqlDbType.UniqueIdentifier,
providerUserKey);

                  parameter1.Direction = ParameterDirection.InputOutput;

                  command1.Parameters.Add(parameter1);

                  parameter1 = new SqlParameter(“@ReturnValue”,
SqlDbType.Int);

                  parameter1.Direction = ParameterDirection.ReturnValue;

                  command1.Parameters.Add(parameter1);

                  command1.ExecuteNonQuery();

                  int num3 = (parameter1.Value != null) ? ((int)
parameter1.Value) : -1;

                  if ((num3 < 0) || (num3 > 11))

                  {

                        num3 = 11;

                  }

                  status = (MembershipCreateStatus) num3;

                  if (num3 != 0)

                  {

                        return null;

                  }

                  providerUserKey = new
Guid(command1.Parameters[“@UserId”].Value.ToString());

                  time1 = time1.ToLocalTime();

                  user1 = new MembershipUser(this.Name, username,
providerUserKey, email, passwordQuestion, null, isApproved, false,
time1, time1, time1, time1, new DateTime(0x6da, 1, 1));

            }

            finally

            {

                  if (holder1 != null)

                  {

                        holder1.Close();

                        holder1 = null;

                  }

            }

      }

      catch

      {

            throw;

      }

      return user1;

}

 

代码中,aspnet_Membership_CreateUser为aspnet_regsql工具为membership创设的储存过程,它的意义就是创设一个用户。

 

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

public override MembershipUser CreateUser(string username, string
password, string email, string passwordQuestion, string passwordAnswer,
bool isApproved, object userId, out MembershipCreateStatus status) {

    //后面的代码略;

 //Create connection

 OracleConnection connection = new
OracleConnection(OracleHelper.ConnectionStringMembership);

 connection.Open();

 OracleTransaction transaction =
connection.BeginTransaction(IsolationLevel.ReadCommitted);

 try {

 DateTime dt = DateTime.Now;

 bool isUserNew = true;

 

 // Step 1: Check if the user exists in the Users table: create if
not   

 int uid = GetUserID(transaction, applicationId, username, true, false,
dt, out isUserNew);

 if(uid == 0) { // User not created successfully!

   status = MembershipCreateStatus.ProviderError;

   return null;

 }

 // Step 2: Check if the user exists in the Membership table: Error if
yes.

 if(IsUserInMembership(transaction, uid)) {

   status = MembershipCreateStatus.DuplicateUserName;

   return null;

 }

 // Step 3: Check if Email is duplicate

 if(IsEmailInMembership(transaction, email, applicationId)) {

   status = MembershipCreateStatus.DuplicateEmail;

   return null;

 }

 // Step 4: Create user in Membership table    

 int pFormat = (int)passwordFormat;

 if(!InsertUser(transaction, uid, email, pass, pFormat, salt, “”, “”,
isApproved, dt)) {

   status = MembershipCreateStatus.ProviderError;

   return null;

 }

 // Step 5: Update activity date if user is not new

 if(!isUserNew) {

   if(!UpdateLastActivityDate(transaction, uid, dt)) {

    status = MembershipCreateStatus.ProviderError;

    return null;

   }

 }

 status = MembershipCreateStatus.Success;

 return new MembershipUser(this.Name, username, uid, email,
passwordQuestion, null, isApproved, false, dt, dt, dt, dt,
DateTime.MinValue);

 }

 catch(Exception) {

 if(status == MembershipCreateStatus.Success)

   status = MembershipCreateStatus.ProviderError;

 throw;

 }

 finally {

 if(status == MembershipCreateStatus.Success)

   transaction.Commit();

 else

   transaction.Rollback();

 connection.Close();

 connection.Dispose();

 }

}

 

PetShop5.0
详解之八(PetShop表示层设计)

代码中,InsertUser()方法就是负责用户的创建,而在前头则需要判定创立的用户是否曾经存在。InsertUser()方法的定义如下:

private static bool InsertUser(OracleTransaction transaction, int userId, string email, string password, int passFormat, string passSalt, string passQuestion, string passAnswer, bool isApproved, DateTime dt)  {

 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)”;
 OracleParameter[] insertParms =  { 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) };
 insertParms[0].Value = userId;
 insertParms[1].Value = email;
 insertParms[2].Value = password;
 insertParms[3].Value = passFormat;
 insertParms[4].Value = passSalt;
 insertParms[5].Value = passQuestion;
 insertParms[6].Value = passAnswer;
 insertParms[7].Value = OracleHelper.OraBit(isApproved);
 insertParms[8].Value = dt;
 insertParms[9].Value = dt;
 insertParms[10].Value = dt;

 if(OracleHelper.ExecuteNonQuery(transaction, CommandType.Text, insert, insertParms) != 1)
  return false;
 else
  return true;
}

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

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

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

<membership defaultProvider=”OracleMembershipProvider”>
 <providers>
  <clear/>
  <add name=”OracleMembershipProvider” 
   type=”PetShop.Membership.OracleMembershipProvider” 
   connectionStringName=”OraMembershipConnString” 
   enablePasswordRetrieval=”false” 
   enablePasswordReset=”false” 
   requiresUniqueEmail=”false” 
   requiresQuestionAndAnswer=”false” 
   minRequiredPasswordLength=”7″ 
   minRequiredNonalphanumericCharacters=”1″ 
   applicationName=”.NET Pet Shop 5.0″ 
   hashAlgorithmType=”SHA1″ 
   passwordFormat=”Hashed”/>
 </providers>
</membership>

至于部署节属性的意义,能够参见MSDN等息息相关文档。

6.4.3 
ASP.NET登录控件

此地所谓的报到控件并不是指一个控件,而是ASP.NET
2.0新提供的一组用于缓解用户登录的控件。登录控件与Membership举办合并,迅速方便地贯彻用户登录的处理。ASP.NET登录控件包括Login控件、LoginView控件、LoginStatus控件、LoginName控件、PasswordRescovery控件、CreateUserWizard控件以及ChangePassword控件。
PetShop 5.0犹如一本体现登录控件用法的面面俱到教程。大家得以从诸如SignIn、NewUser等页面中,看到ASP.NET登录控件的行使办法。例如在SignIn.aspx中,用到了Login控件。在该控件中,可以蕴涵TextBox、Button等连串的控件,用法如下所示:

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

又例如NewUser.aspx中对CreateUserWizard控件的施用:

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

拔取了登录控件后,我们毋需编写与用户登录相关的代码,登录控件已经为我们完成了相关的功效,这就大大地简化了这多少个体系的宏图与落实。

6.4.4  Master
Page特性

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

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

行使Master
Page可以为网站建立一个联结的体制,且可以选择它有利于地创制一组控件和代码,然后将其使用于一组页。对于那个体制与功能相似的页而言,利用Master
Page就可以集中处理为Master
Page,一旦举行改动,就足以在一个职务上进行翻新。

在PetShop
5.0中,建立了名为MasterPage.master的Master
Page,它涵盖了header、LoginView控件、导航菜单以及用于显示内容的html元素,如图6-3所示: 

图6-3
PetShop 5.0的Master
Page

@Master指令的概念如下:

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

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

public partial class MasterPage : System.Web.UI.MasterPage {

    private const string HEADER_PREFIX = “.NET Pet Shop :: {0}”;

    protected void Page_PreRender(object sender, EventArgs e) { 
        ltlHeader.Text = Page.Header.Title;
        Page.Header.Title = string.Format(HEADER_PREFIX, Page.Header.Title);          
    }
    protected void btnSearch_Click(object sender, EventArgs e) {
        WebUtility.SearchRedirect(txtSearch.Text);    
    }
}

留神Master
Page页面不再继续自System.Web.UI.Page,而是继续System.Web.UI.MasterPage类。与Page类继承TemplateControl类不同,它是UserControl类的子类。因而,可以动用在Master
Page上的有效性指令与UserControl的可用指令相同,例如Auto伊夫(Eve)ntWireup、ClassName、CodeFile、EnableViewState、WarningLevel等。

每一个与Master
Page相关的情节页必须在@Page指令的MasterPageFile属性中援引相关的Master
Page。例如PetShop
5.0中的CheckOut内容页,其@Page指令的定义如下:

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

void Page_PreInit(Object sender, EventArgs e)
{
    this.MasterPageFile = “~/NewMaster.master”;
}

因此重写Page_PreInit()方法,是因为Master
Page会在情节页起先化阶段展开合并,也即是说是在PreInit阶段完成Master
Page的分红。
ASP.NET 2.0引入的新特点,并不仅仅限于上述介绍的内容。例如Theme、Wizard控件等新特色在PetShop
5.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,可以在表示层设计上带来更多的悲喜。

 

 

 

 

 

 

 

 

 

 

 

 

 

VS2005快捷键

 

CTRL + SHIFT + B生成解决方案
CTRL + F7 生成编译
CTRL + O 打开文件
CTRL + SHIFT + O打开项目
CTRL + SHIFT + C突显类视图窗口
F4 展现属性窗口
SHIFT + F4显得档次性质窗口
CTRL + SHIFT + E展现资源视图
F12 转到定义
CTRL + F12转到注脚
CTRL + ALT + J对象浏览
CTRL + ALT + F1帮手目录
CTRL + F1 动态接济 
F1 帮助
SHIFT + F1脚下窗口帮忙
CTRL + ALT + F3帮助-搜索
SHIFT + ALT + ENTER全屏展现
CTRL + -向后一定
CTRL + SHIFT + -向前定位
CTRL + F4关门文档窗口
CTRL + PAGE DOWN光标定位到窗口上方
CTRL + PAGE UP光标定位到窗口下方
CTRL + F6
CTRL + TAB下一个文档窗口
CTRL + SHIFT + F6
CTRL + SHIFT + TAB上一个文档窗口
ALT + F6下一个面板窗口
CTRL + K, CTRL + L取消remark
CTRL + K, CTRL + C注释采用的代码
CTRL + K, CTRL + U废除对选用代码的阐明
CTRL + M, CTRL + O折叠代码定义
CTRL + M, CTRL + L展开代码定义
CTRL + Delete删除至词尾
CTRL + BACKSPACE删除至词头
SHIFT + TAB废除制表符
CTRL + U转小写
CTRL + SHIFT + U转大写
CTRL + SHIFT + END选取至文档末尾
CTRL + SHIFT + HOME接纳至文档末尾起始
SHIFT + END选用至行尾
SHIFT + HOME选取至行起先处
SHIFT + ALT + END垂直接纳到终极尾
SHIFT + ALT + HOME垂直选用到最前边
CTRL + A全选
CTRL + W选拔当前单词
CTRL + SHIFT + PAGE UP采用至本页前面
CTRL + SHIFT + PAGE DOWN采取至本页后边
CTRL + END文档定位到终极
CTRL + HOME文档定位到最前
CTRL + G转到…
CTRL + K, CTRL + P上一个标签
CTRL + K, CTRL + N下一个标签
ALT + F10调试-ApplyCodeChanges 
CTRL + ALT+ Break截至调试
CTRL + SHIFT + F9 撤销所有断点
CTRL + F9同意中断
CTRL + SHIFT + F5调剂-重新起初
F5运作调节
CTRL + F5运转不调试
F10跨过程序执行
F11单步逐句执行
CTRL + J列出成员 
CTRL + PAGE DOWN下一个视图
CTRL + B格式-粗体
CTRL + SHIFT + T格式-文字缩进 
调节连忙键
F6: 生成解决方案
Ctrl+F6: 生成当下项目
F7: 查看代码
Shift+F7: 查看窗体设计器
F5: 启动调试
Ctrl+F5: 开首执行(不调试)
Shift+F5: 截至调试
Ctrl+Shift+F5: 重启调试
F9: 切换断点
Ctrl+F9: 启用/结束断点
Ctrl+Shift+F9: 删除所有断点
F10: 逐过程
Ctrl+F10: 运行到光标处
F11: 逐语句

编纂神速键
Shift+Alt+Enter: 切换全屏编辑
Ctrl+B,T / Ctrl+K,K: 切换书签开关
Ctrl+B,N / Ctrl+K,N: 移动到下一书签
Ctrl+B,P: 移动到上一书签
Ctrl+B,C: 清除全体标签
Ctrl+I: 渐进式搜索
Ctrl+Shift+I: 反向渐进式搜索
Ctrl+F: 查找
Ctrl+Shift+F: 在文书中搜寻
F3: 查找下一个
Shift+F3: 查找上一个
Ctrl+H: 替换
Ctrl+Shift+H: 在文件中替换
Alt+F12: 查找符号(列出所有查找结果)
Ctrl+Shift+V: 剪贴板循环
Ctrl+左右箭头键: 五次可以运动一个单词
Ctrl+上下箭头键: 滚动代码屏幕,但不移动光标地方。
Ctrl+Shift+L: 删除当前行
Ctrl+M,M: 隐藏或举办当前嵌套的折叠状态
Ctrl+M,L: 将所有过程设置为同一的隐藏或举行状态
Ctrl+M,P: 截至大纲显示
Ctrl+E,S: 查看空白
Ctrl+E,W: 自动换行
Ctrl+G: 转到指定行
Shift+Alt+箭头键: 采取矩形文本
Alt+鼠标左按钮: 选取矩形文本
Ctrl+Shift+U: 全体化为大写
Ctrl+U: 全体改成小写
代码迅速键
Ctrl+J / Ctrl+K,L: 列出成员
Ctrl+Shift+空格键 / Ctrl+K,P: 参数音讯
Ctrl+K,I: 急忙音讯
Ctrl+E,C / Ctrl+K,C: 注释选定内容
Ctrl+E,U / Ctrl+K,U: 撤销选定注释内容
Ctrl+K,M: 生成方法存根
Ctrl+K,X: 插入代码段
Ctrl+K,S: 插入外侧代码
F12: 转到所调用过程或变量的概念
窗口急速键
Ctrl+W,W: 浏览器窗口
Ctrl+W,S: 解决方案管理器
Ctrl+W,C: 类视图
Ctrl+W,E: 错误列表
Ctrl+W,O: 输出视图
Ctrl+W,P: 属性窗口
Ctrl+W,T: 任务列表
Ctrl+W,X: 工具箱
Ctrl+W,B: 书签窗口
Ctrl+W,U: 文档大纲
Ctrl+D,B: 断点窗口
Ctrl+D,I: 即时窗口
Ctrl+Tab: 活动窗体切换
Ctrl+Shift+N: 新建项目
Ctrl+Shift+O: 打开项目
Ctrl+Shift+S: 全体封存
Shift+Alt+C: 新建类
Ctrl+Shift+A: 新建项

VS2005的隐藏快速键 
 
这边我将会把一些下意识中发现的VS2005中绝非明确指出的快捷键共享出来,并不是颇具的快捷键,或者大规模的一些快速键。
1、Ctrl+Space直接完成类或函数(本来那多少个并不算隐藏的迅速键,可是因为普通话输入法抢占这多少个快捷键,所以。。。,替代的急忙键是Alt+Right)
2、Shift+Delete整行删除,并且将这一行放到剪贴板(这时候无法当选一段内容)
3、Shift+Insert粘贴,有点匪夷所思,Ctrl+V就可以了,大概是为了和Shift+Delete对应吧
4、Ctrl+Up,Ctrl+Down滚动编辑器,但尽可能不移步光标,光标保证在可见范围内
5、Ctrl+BackSpace,Ctrl+Delete整词删除,有的时候很有用
6、Ctrl+Left,Ctrl+Right按整词移动光标(不算隐藏,和前面几条加起来就是Ctrl光标控制套件了)
7、Alt+Shift+F10开拓执行改名,实现接口和抽象类的小窗口(还足以用Ctrl+.,然而有些中文输入法用到这么些)
8、Shift+F9调试是开拓Quick沃特(Wat)ch,内容是时下光标所在处的始末
9、F12转跳到定义,很有用的神速键
10、Shift+F12物色所有引用
11、Ctrl+F10=F5,开始Debug
12、Ctrl+F6循环察看代码窗口,有点Ctrl+Tab的感觉
13、Ctrl+F3摸索当前光标选中的内容,可以和F3配合使用
14、Ctrl+F2将要旨转移到类的下拉框上
15、Alt+F7=Ctrl+Tab
16、Alt+F11新开VS2005并编辑宏
17、Alt+F12查找=Ctrl+F 

 

Leave a Comment.