.Net Petshop详解(一): petshop概览和准备工作
前言
.NET Pet Shop解决问题的描述
.NET Pet Shop是一个电子商务的实例,是在Microsoft的.NET平台上的一个具体实现。这个系统包含了B2C和B2B的实现。在这里我主要将的是B2C实现的部分,B2B在.NET Pet Shop里面不是重点要解决的问题。
.NET Pet Shop是一个在线的宠物购物系统,用户可以通过各种能够连接到internet的终端(包括移动终端)进行在线购物(具体界面见图一)。在这里,我将列出它将要实现的功能,也就是需求了:
(1) 用户帐号的管理功能:包括帐号创建,帐号登录,帐号维护;
(2) 产品浏览功能:类别浏览,具体产品浏览,详细信息,库存信息等等;
(3) 用户购物功能:添加购物,计算总价,下订单等等。
图一 .NET Pet Shop系统的登录主界面
.NET Pet Shop的逻辑结构
.NET Pet Shop的开发环境支持
.NET Pet Shop系统的安装
.Net Petshop详解(二): petshop三层结构之DataTier
交互的三层综述
从这个情景中,我们可以很清晰的了解到Internet Customer在购物时系统的运作情形。首先通过数据展示层的用户交互界面Cart.aspx页面输入顾客购买的商品以及数量,然后这些购物清单由后台的应用程序(cart.aspx.cs)做相应的预处理(如安全验证,校验,数据的格式化等等),接着调用中间件(在程序结构里面就是一些.NET Assembly,封装了购物的商业逻辑),最后通过数据访问接口来更新数据库里面的数据(数据层)。
数据层(Database)
.NET Petshop的数据库并不是十分的庞大,总共有12个用户表和23个存储过程。Petshop数据库里面存储的是用户数据,帐号数据,产品数据,用户配置数据,订单数据,库存数据以及供应商的数据。应用程序访问数据库的数据并不是直接的与数据库表打交道,而是通过存储过程的运行来获取所需要的数据。这样的设计有一个好处就是,避免了频繁的表操作,而通过运行在服务器端的存储过程可以极大的提高运作效率和提升访问数据的速度,同时也很好的屏蔽了数据库表的逻辑,使得数据库访问变成了数据库提供的服务访问。当然,也有人指责说这些存储过程迁移性是值得怀疑的。
下面我将通过表格把这些数据库的基本表列出来(见表1),并一一做说明,希望对大家深入的理解有帮助。
表名称 | 备注信息 |
Account | 基本用户信息。 |
BannerData | 存储的是系统界面的banner图片的设置信息。 |
Category | 宠物的类别目录表(比如鱼类,狗类等等)。 |
Inventory | 宠物产品的存货信息。 |
Item | 单个产品的详细信息。 |
LineItem | 订单的每一项的详细信息,包括产品名称和数量,价格等。 |
Orders | 用户购物的订单,一个订单可以包括多项LineItem |
OrderStatus | 订单的状态 |
Product | 宠物的产品列表,一条Product可能包括多个Item |
Profile | 用户配置表,用于记录他们的favorites。 |
Signon | 用户帐号登陆表,因为常用,故从Account独立出来。 |
Supplier | 供应商信息。 |
图2:petshop数据库物理设计模式
技巧:其实对于从事过数据库建模和设计的人都知道,得到上面的数据库模型图形并不是一件很困难的事情。正如我们前面提到的一样,我们可以用Visio做工程反转就可以得到上面这么美观的设计模型图了。同时,我们也可以在模型图中做修改设计,可以马上应用到你的物理数据库,使其保持同步。
说完了基本的数据库表,接下来我们看看存储过程。用微软的话说,只有设计称存储过程,才算是”cleaner separation of code from the middle-tier”,我个人觉得这样做是很好的。同样的,我把它列在一个表格里(表2)。
存储过程名称 | 备注信息 |
upAccountAdd | 增加一个帐号。 |
upAccountGetAddress | 获取用户的地址,主要用于下订单时注册地址与送货地址不一。 |
upAccountGetDetails | 获取帐号的详细信息。 |
upAccountLogin | 用户登陆验证。 |
upAccountUpdate | 更新用户帐号。 |
upCategoryGetList | 获取某个类别的产品列表。 |
upInventoryAdd | 增加指定的项到存活信息。 |
upInventoryGetList | 获取存货列表。 |
upItemAdd | 增加一项产品。 |
upItemGetDetails | 获取指定产品项的详细信息。 |
upItemGetList | 获取某一特定类别的产品的具体项目列表。 |
upItemGetList_ListByPage | 功能与上同,但是分页获取数据。 |
upOrdersAdd | 增加一项订单。 |
upOrdersGet | 获取某一订单的信息。 |
upOrdersGetDetails | 获取某一订单的详细信息。 |
upOrderStatusGet | 获取订单的状态。 |
upProductAdd | 增加一类别产品。 |
upProductGetList | 返回某类产品的列表。 |
upProductGetList_ListByPage | 与上同,但是分页获取结果数据。。 |
upProductSearch | 产品搜索。 |
upProductSearch_ListByPage | 与上同,但是翻页获取结果数据。 |
upProfileGetBannerOption | Banner的配置信息。 |
upProfileGetListOption | 获取用户配置信息。 |
表2:petshop存储过程列表
在这些存储过程里面使用了SQL Server2000的OpenXML的特性来代替传统的行集结果,使用在非常频繁的数据访问操作,可以减轻系统的负担。
.NET Petshop详解(三):petshop三层结构之MiddleTire
图1:.NET Petshop解决方案中间层的类视图和文件视图
1 客户有购买商品的意愿;
2 客户到登陆管理员处登记,且成功登记;
3 在登记处推一个购物车;
4 在超市内查找所购商品类别存放的货架;
5 在具体的货架上查找某一具体品牌的商品;
6 将符合意愿的商品放入自己的购物车;
7 重复4-6;
8 购物完毕;
9 到付款处计算总价格并付款;
10 打印购物清单;
11 退还购物车;
12 取走购物,购物完毕;
备注:在这个用例中,我们做了一些前提和假设,为的是方便.NET Petshop的分析,比如说在实际生活中根本就不需要第二步。
类名称 | 说明 |
BasketItem | 代表购物车ShoppingCart里的一项购物商品。 |
Customer | 用于帐号管理和登陆验证。 |
CustomerDetails | 用户帐号的详细信息。 |
CustomerAddress | 用户帐号的地址信息。 |
Error | 用于登陆出错的帮助功能。 |
Item | 代表某类产品中的具体一项商品。 |
ItemResults | 搜索Item的结果集。 |
Order | 购物完毕后的购物清单和订单。 |
Product | 大类别里面的某类产品。 |
ProductResults | 搜索产品的结果集。 |
Profile | 用户的配置。 |
ShoppingCart | 购物车,用于购物的整个过程,直到下订单。 |
Database | 通过ADO.NET访问数据库,封装了具体的访问方法。 |
SearchResults | 模糊搜索的结果集。 |
表1:.NET Petshop中间层的类
public class ProductResults
{
private string m_productid;
private string m_name;
public string productid {
get { return m_productid; }
set { m_productid = value; }
}
public string name {
get { return m_name; }
set { m_name = value; }
}
}
在.NET Petshop详解(二)中我们就说过数据库的访问是通过存储来进行的,我们看看下面这部分代码就知道了:
public string Login(string userName, string password) {
string customerID;
// params to stored proc
Database data = new Database();
SqlParameter[] prams = {
data.MakeInParam("@username", SqlDbType.VarChar, 25, password),
data.MakeInParam("@password", SqlDbType.VarChar, 25, userName),
data.MakeOutParam("@CustomerID", SqlDbType.VarChar, 25)
};
// create data object and params
data.RunProc("upAccountLogin", prams); // run the stored procedure
customerID = (string) prams[2].Value; // get the output param value
// if the customer id is an empty string, then the login failed
if (customerID == string.Empty)
return null;
else
return customerID;
}
.NET Petshop详解(四):petshop三层结构之PresentationTier
在前面的文章中,我们已经就.NET Petshop的数据层和中间的业务逻辑层作了说明,接下来的文章中,我们将就数据展示层作探究。与前面的两层有着很大的差别的是,.NET Petshop的展示层用了很多Microsoft最新的web Forms技术即Asp.NET。因此,在解说展示层之前,我想就Asp.NET在.NET Petshop的开发中使用到的非常重要的特性做一些说明,以示区别:
(1) Asp.NET代码不再是解释型代码,可以经由JIT编译器编译后运行,并且引入了很好的页面缓冲机制。
(2) ASP.Net的配置模型引入了基于XML文件的“零安装”配置模型。零安装的含义是只需将配置文件Web.config,应用程序拷贝到系统指定的目录下即可,需要更改时直接在文件里更改并保存。
(3) 安全管理 ASP.Net提供了比传统ASP更强大可靠的安全管理。Asp.NET提供了三种验证方式以及两种类型的授权服务。
(4) 支持代码和页面内容的分离。回想以前编写Asp程序的时候的问题:代码逻辑混乱,难于管理。
(5) 提供了更好的状态管理,包括会话状态的管理和视图状态的管理。
(6) 大量的ASP.NET服务器端控件和对用户控件的开发的支持。
.NET Petshop充分的使用了服务器控件技术和会话状态管理。展示层的交互界面均采用aspx页面,后端有分离的逻辑代码。.NET Petshop共有19个aspx页面和代码逻辑。在这里一一里列出,并说明设置参数和其作用。
ASP.NET Web页面 | EnableSessionState | EnableViewState | 备注说明 |
Cart.aspx | true | true | 购物清单 |
Category.aspx | True | false | 产品分类的列表 |
CheckOut.aspx | Readonly | false | 订单确认 |
CreateNewAccount.aspx | False | true | 创建新帐号 |
Default.aspx | False | false | 系统首页面 |
EditAccount.aspx | False | true | 账号编辑 |
Error.aspx | False | false | 错误处理 |
Help.aspx | False | false | 帮助 |
OrderAddressConfirm.aspx | readonly | false | 订单地址确认 |
OrderBilling.aspx | True | true | 订单信息 |
OrderProcess.aspx | readonly | false | 下订单 |
OrderShipping.aspx | True | true | 订单地址和姓名 |
Product.aspx | False | false | 产品列表 |
ProductDetails.aspx | false | false | 产品详细信息 |
Search.aspx | false | false | 搜索 |
SignIn.aspx | false | false | 帐号登陆 |
SignOut.aspx | true | false | 帐号退出 |
ValidateAccount.aspx | false | false | 帐号创建确认 |
VerifySignIn.aspx | false | false | 账号登陆确认 |
.NET Petshop使用了很多用户控件,这些控件位于web/Inc目录下面。表格2对用户控件作出说明:
User Controls | EnableViewState | 备注说明 |
ControlAddress | true | 详细地址列表 |
ControlBanner | false | 页面底部的banner |
ControlCart | true | 购物清单 |
ControlFavList | false | 个人Favorites列表 |
ControlHeader | false | 页面顶部的菜单和链接 |
ControlStaticAddress | false | 不能编辑的地址信息 |
下面列出一段代码,说明这些参数的设置和用户控件的引用:
/* 摘自Cart.aspx */
<%@ Register TagPrefix="PetShop" TagName="Cart" Src="Inc/ControlCart.ascx" %>
<%@ Register TagPrefix="PetShop" TagName="Header" Src="Inc/ControlHeader.ascx" %>
<%@ Page language="c#" Codebehind="CheckOut.aspx.cs" AutoEventWireup="false" Inherits="PetShop.Web.CheckOut" EnableSessionState="readonly" enableViewState="False"%>
开始的两个Registe指令用于引用ControlCart和ControlHeader的用户控件,Page指令定义 ASP.NET 页分析器和编译器使用的页特定的属性。
在控件的排放位置使用下面的代码既可以显示该控件了:
<PetShop:Cart id="ctlCart" runat="server" allowedit="false" />
在上面的代码中allowedit一项是向实例化的ctlCart传递参数。我个人认为用户控件是一个很好的东西,可以简化web页面的开发模型又可以达到代码复用的目的,而且其开发非常简单,与aspx的开发基本是一样的。图1说明了设计模式下和运行时的用户控件。
图1:设计模式和运行时的ctlCart用户控件
.NET Petshop详解(五):petshop输出缓存设置
ASP.NET的输出缓存
衡量高性能、可缩放的web应用程序最重要的一个指标就是缓存了。ASP.NET提供了高性能的web应用程序的缓存功能,ASP.NET 有三种可由 Web 应用程序使用的缓存:
· 输出缓存,它缓存请求所生成的动态响应。
· 片断缓存,它缓存请求所生成的响应的各部分。
· 数据缓存,它以编程方式缓存任意对象。为支持这种缓存,ASP.NET 提供了全功能的缓存引擎,使程序员能够轻松地在请求间保留数据。
页的输出缓存是非常有用的。在海量的访问站点中,有些页面的访问频率占了非常大的比重,即使对这些页使用输出缓存很少的时间,也会减轻系统不少的负担,因为后面对这些页面的请求将不在执行创建该页的代码。
但是,这样显得不够灵活,页的请求可能的确是很多,然而在页面上我们缓存了所有的东西,无论是构造成本高还是构造成本低的部分。能否有一种可以缓存页的部分的数据呢?幸运的是ASP.NET提供了针对每个请求来创建或自定义该页的各部分。比如说我们可以对页面上构造成本很高的用户控件做片断缓存。
ASP.NET 缓存支持文件和缓存键依赖项,使开发人员可以使缓存项依赖于外部文件或其他缓存项。此项技术可用于在项的基础数据源发生更改时使该项无效。 ASP.NET可以将这些项存储在 Web 服务器上或请求流中的其他软件上,例如代理服务器或浏览器。这可以使您避免重新创建满足先前请求的信息,特别是当在服务器上创建时要求大量处理器时间或其他资源的信息。
Petshop的页缓存设置
我们可以可通过使用低级别的 OutputCache API 或高级别的 @ OutputCache 指令来实现页的输出缓存。启用输出缓存后,当发出对页的第一个 GET 请求时创建一个输出缓存项。随后的 GET 或 HEAD 请求由该输出缓存项服务,直到该缓存请求过期。输出缓存还支持缓存的 GET 或 POST 名称/值对的变体。
输出缓存遵循页的过期和有效性策略。如果某页位于输出缓存中,并且有一个过期策略标记指示该页自缓存起 60 分钟后过期,则在 60 分钟后将该页从输出缓存中移除。如果此后接收到另一个请求,则执行页代码,并且可以再次缓存该页。
下面的指令在响应时激活输出缓存:
<%@ OutputCache Duration="60" VaryByParam="none"%>
Duration和VaryByParam是必选参数,前者标识过期时间,后者表示GET或 POST 名称/值对的字符串。如果不使用该属性,可是设置为none。在这里我们还要说明一个参数VaryByCustom,使用这个参数,我们可以自定义输出缓存要求的任意文本。除了在OutputCache指令里面申明该属性之外,我们还得在应用程序的 global.asax 文件的代码声明块中,重写 GetVaryByCustomString 方法来为自定义字符串指定输出缓存的行为。
举一列来说:
<%@ OutputCache VaryByParam="none" VaryByCustom="CategoryPageKey" Location="server" Duration="43200" %>
这里的VaryByCustom定义的为CategoryPageKey,那么在global.asax里面我们必须定义CategoryPageKey这个字符创输出缓存的行为,见下面代码。
public override string GetVaryByCustomString(HttpContext context, String arg) {
string cacheKey = "";
switch(arg) {
case "CategoryPageKey":
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];
}
break;
case "SearchPageKey" :
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];
}
break;
case "ProductPageKey" :
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];
}
break;
case "ProductDetailsPageKey" :
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];
}
break;
case "UserID" :
if (Request.IsAuthenticated == true) {
cacheKey = "UserID_In";
}
else {
cacheKey = "UserID_Out";
}
break;
}
return cacheKey;
}
从上面对CategoryPageKey字符创所作的行为来看,当我们的请求页面中含有对特定的category_id的某一分页显示的数据页的请求时,将调用缓存(自然是已经缓存了该页)。
下表列出了petshop的web应用程序的输出缓存设置。
ASP.NET WebForms | Cache setting | Duration |
ControlHeader | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="UserID" %> | 12 hours |
Default | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="UserID" %> | 12 hours |
Help | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="UserID" %> | 12 hours |
Category | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="CategoryPageKey " %> | 12 hours |
Product | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="ProductPageKey " %> | 12 hours |
ProductDetails | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="ProductDetailsPageKey " %> | 12 hours |
Search | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="SearchPageKey " %> | 12 hours |
显然petshop的web页面上部的ControlHeader是随着用户登陆的状态有关的,故其设置了VaryByCustom属性以来标识用户不同登陆状态的缓存版本。而Category页面由于可能被大量的访问,并且数据量很大,是十分有必要缓存的,但是由于数据的随机性很大,存在不同的版本,比如说是不同类别的Category,甚至不同的分页显示的数据页,在这里采用了VaryByCustom属性以缓存不同版本的页。
Petshop片断缓存
在前面我们提到ASP.NET可以提供页的局部数据的缓存,通常是一些构造代价较大的部分,诸如用户控件。在petshop里面,大量使用了用户控件(尤其是.NET示例程序Duwamish7.0使用了更多的用户控件,那些页面简直就是控件的拼装),用户控件的缓存设置方法和aspx页的缓存设置方法基本相同,在这里我们不再列出。只有ControlHeader控件使用了缓存设置,见上表。
.Net Petshop详解(二): petshop三层结构之DataTier
交互的三层综述
从这个情景中,我们可以很清晰的了解到Internet Customer在购物时系统的运作情形。首先通过数据展示层的用户交互界面Cart.aspx页面输入顾客购买的商品以及数量,然后这些购物清单由后台的应用程序(cart.aspx.cs)做相应的预处理(如安全验证,校验,数据的格式化等等),接着调用中间件(在程序结构里面就是一些.NET Assembly,封装了购物的商业逻辑),最后通过数据访问接口来更新数据库里面的数据(数据层)。
数据层(Database)
.NET Petshop的数据库并不是十分的庞大,总共有12个用户表和23个存储过程。Petshop数据库里面存储的是用户数据,帐号数据,产品数据,用户配置数据,订单数据,库存数据以及供应商的数据。应用程序访问数据库的数据并不是直接的与数据库表打交道,而是通过存储过程的运行来获取所需要的数据。这样的设计有一个好处就是,避免了频繁的表操作,而通过运行在服务器端的存储过程可以极大的提高运作效率和提升访问数据的速度,同时也很好的屏蔽了数据库表的逻辑,使得数据库访问变成了数据库提供的服务访问。当然,也有人指责说这些存储过程迁移性是值得怀疑的。
下面我将通过表格把这些数据库的基本表列出来(见表1),并一一做说明,希望对大家深入的理解有帮助。
表名称 | 备注信息 |
Account | 基本用户信息。 |
BannerData | 存储的是系统界面的banner图片的设置信息。 |
Category | 宠物的类别目录表(比如鱼类,狗类等等)。 |
Inventory | 宠物产品的存货信息。 |
Item | 单个产品的详细信息。 |
LineItem | 订单的每一项的详细信息,包括产品名称和数量,价格等。 |
Orders | 用户购物的订单,一个订单可以包括多项LineItem |
OrderStatus | 订单的状态 |
Product | 宠物的产品列表,一条Product可能包括多个Item |
Profile | 用户配置表,用于记录他们的favorites。 |
Signon | 用户帐号登陆表,因为常用,故从Account独立出来。 |
Supplier | 供应商信息。 |
图2:petshop数据库物理设计模式
技巧:其实对于从事过数据库建模和设计的人都知道,得到上面的数据库模型图形并不是一件很困难的事情。正如我们前面提到的一样,我们可以用Visio做工程反转就可以得到上面这么美观的设计模型图了。同时,我们也可以在模型图中做修改设计,可以马上应用到你的物理数据库,使其保持同步。
说完了基本的数据库表,接下来我们看看存储过程。用微软的话说,只有设计称存储过程,才算是”cleaner separation of code from the middle-tier”,我个人觉得这样做是很好的。同样的,我把它列在一个表格里(表2)。
存储过程名称 | 备注信息 |
upAccountAdd | 增加一个帐号。 |
upAccountGetAddress | 获取用户的地址,主要用于下订单时注册地址与送货地址不一。 |
upAccountGetDetails | 获取帐号的详细信息。 |
upAccountLogin | 用户登陆验证。 |
upAccountUpdate | 更新用户帐号。 |
upCategoryGetList | 获取某个类别的产品列表。 |
upInventoryAdd | 增加指定的项到存活信息。 |
upInventoryGetList | 获取存货列表。 |
upItemAdd | 增加一项产品。 |
upItemGetDetails | 获取指定产品项的详细信息。 |
upItemGetList | 获取某一特定类别的产品的具体项目列表。 |
upItemGetList_ListByPage | 功能与上同,但是分页获取数据。 |
upOrdersAdd | 增加一项订单。 |
upOrdersGet | 获取某一订单的信息。 |
upOrdersGetDetails | 获取某一订单的详细信息。 |
upOrderStatusGet | 获取订单的状态。 |
upProductAdd | 增加一类别产品。 |
upProductGetList | 返回某类产品的列表。 |
upProductGetList_ListByPage | 与上同,但是分页获取结果数据。。 |
upProductSearch | 产品搜索。 |
upProductSearch_ListByPage | 与上同,但是翻页获取结果数据。 |
upProfileGetBannerOption | Banner的配置信息。 |
upProfileGetListOption | 获取用户配置信息。 |
表2:petshop存储过程列表
在这些存储过程里面使用了SQL Server2000的OpenXML的特性来代替传统的行集结果,使用在非常频繁的数据访问操作,可以减轻系统的负担。
.NET Petshop详解(三):petshop三层结构之MiddleTire
图1:.NET Petshop解决方案中间层的类视图和文件视图
1 客户有购买商品的意愿;
2 客户到登陆管理员处登记,且成功登记;
3 在登记处推一个购物车;
4 在超市内查找所购商品类别存放的货架;
5 在具体的货架上查找某一具体品牌的商品;
6 将符合意愿的商品放入自己的购物车;
7 重复4-6;
8 购物完毕;
9 到付款处计算总价格并付款;
10 打印购物清单;
11 退还购物车;
12 取走购物,购物完毕;
备注:在这个用例中,我们做了一些前提和假设,为的是方便.NET Petshop的分析,比如说在实际生活中根本就不需要第二步。
类名称 | 说明 |
BasketItem | 代表购物车ShoppingCart里的一项购物商品。 |
Customer | 用于帐号管理和登陆验证。 |
CustomerDetails | 用户帐号的详细信息。 |
CustomerAddress | 用户帐号的地址信息。 |
Error | 用于登陆出错的帮助功能。 |
Item | 代表某类产品中的具体一项商品。 |
ItemResults | 搜索Item的结果集。 |
Order | 购物完毕后的购物清单和订单。 |
Product | 大类别里面的某类产品。 |
ProductResults | 搜索产品的结果集。 |
Profile | 用户的配置。 |
ShoppingCart | 购物车,用于购物的整个过程,直到下订单。 |
Database | 通过ADO.NET访问数据库,封装了具体的访问方法。 |
SearchResults | 模糊搜索的结果集。 |
表1:.NET Petshop中间层的类
public class ProductResults
{
private string m_productid;
private string m_name;
public string productid {
get { return m_productid; }
set { m_productid = value; }
}
public string name {
get { return m_name; }
set { m_name = value; }
}
}
在.NET Petshop详解(二)中我们就说过数据库的访问是通过存储来进行的,我们看看下面这部分代码就知道了:
public string Login(string userName, string password) {
string customerID;
// params to stored proc
Database data = new Database();
SqlParameter[] prams = {
data.MakeInParam("@username", SqlDbType.VarChar, 25, password),
data.MakeInParam("@password", SqlDbType.VarChar, 25, userName),
data.MakeOutParam("@CustomerID", SqlDbType.VarChar, 25)
};
// create data object and params
data.RunProc("upAccountLogin", prams); // run the stored procedure
customerID = (string) prams[2].Value; // get the output param value
// if the customer id is an empty string, then the login failed
if (customerID == string.Empty)
return null;
else
return customerID;
}
.NET Petshop详解(四):petshop三层结构之PresentationTier
在前面的文章中,我们已经就.NET Petshop的数据层和中间的业务逻辑层作了说明,接下来的文章中,我们将就数据展示层作探究。与前面的两层有着很大的差别的是,.NET Petshop的展示层用了很多Microsoft最新的web Forms技术即Asp.NET。因此,在解说展示层之前,我想就Asp.NET在.NET Petshop的开发中使用到的非常重要的特性做一些说明,以示区别:
(1) Asp.NET代码不再是解释型代码,可以经由JIT编译器编译后运行,并且引入了很好的页面缓冲机制。
(2) ASP.Net的配置模型引入了基于XML文件的“零安装”配置模型。零安装的含义是只需将配置文件Web.config,应用程序拷贝到系统指定的目录下即可,需要更改时直接在文件里更改并保存。
(3) 安全管理 ASP.Net提供了比传统ASP更强大可靠的安全管理。Asp.NET提供了三种验证方式以及两种类型的授权服务。
(4) 支持代码和页面内容的分离。回想以前编写Asp程序的时候的问题:代码逻辑混乱,难于管理。
(5) 提供了更好的状态管理,包括会话状态的管理和视图状态的管理。
(6) 大量的ASP.NET服务器端控件和对用户控件的开发的支持。
.NET Petshop充分的使用了服务器控件技术和会话状态管理。展示层的交互界面均采用aspx页面,后端有分离的逻辑代码。.NET Petshop共有19个aspx页面和代码逻辑。在这里一一里列出,并说明设置参数和其作用。
ASP.NET Web页面 | EnableSessionState | EnableViewState | 备注说明 |
Cart.aspx | true | true | 购物清单 |
Category.aspx | True | false | 产品分类的列表 |
CheckOut.aspx | Readonly | false | 订单确认 |
CreateNewAccount.aspx | False | true | 创建新帐号 |
Default.aspx | False | false | 系统首页面 |
EditAccount.aspx | False | true | 账号编辑 |
Error.aspx | False | false | 错误处理 |
Help.aspx | False | false | 帮助 |
OrderAddressConfirm.aspx | readonly | false | 订单地址确认 |
OrderBilling.aspx | True | true | 订单信息 |
OrderProcess.aspx | readonly | false | 下订单 |
OrderShipping.aspx | True | true | 订单地址和姓名 |
Product.aspx | False | false | 产品列表 |
ProductDetails.aspx | false | false | 产品详细信息 |
Search.aspx | false | false | 搜索 |
SignIn.aspx | false | false | 帐号登陆 |
SignOut.aspx | true | false | 帐号退出 |
ValidateAccount.aspx | false | false | 帐号创建确认 |
VerifySignIn.aspx | false | false | 账号登陆确认 |
.NET Petshop使用了很多用户控件,这些控件位于web/Inc目录下面。表格2对用户控件作出说明:
User Controls | EnableViewState | 备注说明 |
ControlAddress | true | 详细地址列表 |
ControlBanner | false | 页面底部的banner |
ControlCart | true | 购物清单 |
ControlFavList | false | 个人Favorites列表 |
ControlHeader | false | 页面顶部的菜单和链接 |
ControlStaticAddress | false | 不能编辑的地址信息 |
下面列出一段代码,说明这些参数的设置和用户控件的引用:
/* 摘自Cart.aspx */
<%@ Register TagPrefix="PetShop" TagName="Cart" Src="Inc/ControlCart.ascx" %>
<%@ Register TagPrefix="PetShop" TagName="Header" Src="Inc/ControlHeader.ascx" %>
<%@ Page language="c#" Codebehind="CheckOut.aspx.cs" AutoEventWireup="false" Inherits="PetShop.Web.CheckOut" EnableSessionState="readonly" enableViewState="False"%>
开始的两个Registe指令用于引用ControlCart和ControlHeader的用户控件,Page指令定义 ASP.NET 页分析器和编译器使用的页特定的属性。
在控件的排放位置使用下面的代码既可以显示该控件了:
<PetShop:Cart id="ctlCart" runat="server" allowedit="false" />
在上面的代码中allowedit一项是向实例化的ctlCart传递参数。我个人认为用户控件是一个很好的东西,可以简化web页面的开发模型又可以达到代码复用的目的,而且其开发非常简单,与aspx的开发基本是一样的。图1说明了设计模式下和运行时的用户控件。
图1:设计模式和运行时的ctlCart用户控件
.NET Petshop详解(五):petshop输出缓存设置
ASP.NET的输出缓存
衡量高性能、可缩放的web应用程序最重要的一个指标就是缓存了。ASP.NET提供了高性能的web应用程序的缓存功能,ASP.NET 有三种可由 Web 应用程序使用的缓存:
· 输出缓存,它缓存请求所生成的动态响应。
· 片断缓存,它缓存请求所生成的响应的各部分。
· 数据缓存,它以编程方式缓存任意对象。为支持这种缓存,ASP.NET 提供了全功能的缓存引擎,使程序员能够轻松地在请求间保留数据。
页的输出缓存是非常有用的。在海量的访问站点中,有些页面的访问频率占了非常大的比重,即使对这些页使用输出缓存很少的时间,也会减轻系统不少的负担,因为后面对这些页面的请求将不在执行创建该页的代码。
但是,这样显得不够灵活,页的请求可能的确是很多,然而在页面上我们缓存了所有的东西,无论是构造成本高还是构造成本低的部分。能否有一种可以缓存页的部分的数据呢?幸运的是ASP.NET提供了针对每个请求来创建或自定义该页的各部分。比如说我们可以对页面上构造成本很高的用户控件做片断缓存。
ASP.NET 缓存支持文件和缓存键依赖项,使开发人员可以使缓存项依赖于外部文件或其他缓存项。此项技术可用于在项的基础数据源发生更改时使该项无效。 ASP.NET可以将这些项存储在 Web 服务器上或请求流中的其他软件上,例如代理服务器或浏览器。这可以使您避免重新创建满足先前请求的信息,特别是当在服务器上创建时要求大量处理器时间或其他资源的信息。
Petshop的页缓存设置
我们可以可通过使用低级别的 OutputCache API 或高级别的 @ OutputCache 指令来实现页的输出缓存。启用输出缓存后,当发出对页的第一个 GET 请求时创建一个输出缓存项。随后的 GET 或 HEAD 请求由该输出缓存项服务,直到该缓存请求过期。输出缓存还支持缓存的 GET 或 POST 名称/值对的变体。
输出缓存遵循页的过期和有效性策略。如果某页位于输出缓存中,并且有一个过期策略标记指示该页自缓存起 60 分钟后过期,则在 60 分钟后将该页从输出缓存中移除。如果此后接收到另一个请求,则执行页代码,并且可以再次缓存该页。
下面的指令在响应时激活输出缓存:
<%@ OutputCache Duration="60" VaryByParam="none"%>
Duration和VaryByParam是必选参数,前者标识过期时间,后者表示GET或 POST 名称/值对的字符串。如果不使用该属性,可是设置为none。在这里我们还要说明一个参数VaryByCustom,使用这个参数,我们可以自定义输出缓存要求的任意文本。除了在OutputCache指令里面申明该属性之外,我们还得在应用程序的 global.asax 文件的代码声明块中,重写 GetVaryByCustomString 方法来为自定义字符串指定输出缓存的行为。
举一列来说:
<%@ OutputCache VaryByParam="none" VaryByCustom="CategoryPageKey" Location="server" Duration="43200" %>
这里的VaryByCustom定义的为CategoryPageKey,那么在global.asax里面我们必须定义CategoryPageKey这个字符创输出缓存的行为,见下面代码。
public override string GetVaryByCustomString(HttpContext context, String arg) {
string cacheKey = "";
switch(arg) {
case "CategoryPageKey":
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];
}
break;
case "SearchPageKey" :
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];
}
break;
case "ProductPageKey" :
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];
}
break;
case "ProductDetailsPageKey" :
if (Request.IsAuthenticated == true) {
cacheKey = "QQQ" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];
}
else {
cacheKey = "AAA" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];
}
break;
case "UserID" :
if (Request.IsAuthenticated == true) {
cacheKey = "UserID_In";
}
else {
cacheKey = "UserID_Out";
}
break;
}
return cacheKey;
}
从上面对CategoryPageKey字符创所作的行为来看,当我们的请求页面中含有对特定的category_id的某一分页显示的数据页的请求时,将调用缓存(自然是已经缓存了该页)。
下表列出了petshop的web应用程序的输出缓存设置。
ASP.NET WebForms | Cache setting | Duration |
ControlHeader | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="UserID" %> | 12 hours |
Default | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="UserID" %> | 12 hours |
Help | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="UserID" %> | 12 hours |
Category | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="CategoryPageKey " %> | 12 hours |
Product | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="ProductPageKey " %> | 12 hours |
ProductDetails | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="ProductDetailsPageKey " %> | 12 hours |
Search | <%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="SearchPageKey " %> | 12 hours |
显然petshop的web页面上部的ControlHeader是随着用户登陆的状态有关的,故其设置了VaryByCustom属性以来标识用户不同登陆状态的缓存版本。而Category页面由于可能被大量的访问,并且数据量很大,是十分有必要缓存的,但是由于数据的随机性很大,存在不同的版本,比如说是不同类别的Category,甚至不同的分页显示的数据页,在这里采用了VaryByCustom属性以缓存不同版本的页。
Petshop片断缓存