1.petshop中的Profile
Microsoft ASP.NET 2.0支持被称为Profile的新对象,它可以自动在多个Web应用程序的访问之间存储用户信息。一个User Profile中可以存储各种类型的信息,这些信息既可以是简单的string和integer类型,也可以是复杂的自定义类型。例如,你可以存储用户的姓、购物篮、用户属性或网站使用情况统计。Profile对象与Session对象十分相似,但是更好用一些。与Session相似的地方在于,Profile是相对于一个特定的用户的,也就是说,每个Web应用程序的用户都有他们自己的profile对象。与Session不同的是,Profile对象是持久对象。如果你向Session中添加一个项,在你离开网站时,该项就会消失。而Profile则完全不同,当你修改Profile的状态时,修改在多个访问之间均有效。profile使用provider模式来存储信息,默认情况下,user profile的内容会保存在SQL Server Express数据库中,该数据库位于网站的App_Data目录。
在petshop中,对profile机制的实现从代码结构上来说分为4个部分:
(1)在web.config中加入对Profile的声明:
代码截图 1
根据这里的声明,当web程序运行起来之后,就可以使用一个叫做Profile的全局对象。asp.net编译器会在Temporary asp.net files目录下生成一个临时文件,这个文件看上去是一个ProfileCommon类(其父类为ProfileBase),这个类会被编入App_Code程序集,所以App_Code下的所有web forms, user controls, master pages和类都能访问到它(参见下图的代码)。事实上,在管道中存在着一个名为ProfileModule的HttpModule,它负责在AcquireRequestState事件发生的时候去创建Profile对象,从而可以在一个请求周期的其余时间访问这个对象。
代码截图 2
通过单步跟踪,你会发现Profile对象中的this其实就是在web.config中定义的相应Provider的实际类型。因此当页面的codebehind访问Profile对象的时候,会通过这个临时对象来从相应的Provider里面获取数据。
(2)名字空间PetShop.Profile。这个空间里面只有一个类PetShopProfileProvider(在PetShop中,随处可见Provider模式)。这个类是profile信息的上层提供者。它并不直接去访问数据库,实际的数据库访问被放置在DAL中,通过工厂模式来访问相应的DAL。下面列出的代码截图3是PetShopProfileProvider中GetPropertyValues()方法中的一段,可以很清楚的看到它取得每一个属性值的过程。但我们实际在使用Profile的时候,利用MS提供的那个默认的ProfileProvider应该就可以了(并未实际进行试验)。
(3)名字空间PetShop.SQLProfileDAL以及PetShop.OracleProfileDAL。这是DAL层中对Profile的支持。
(4)名字空间PetShop.ProfileDALFactory。用来根据配置信息返回正确的ProfileDal对象。
小的总结:在对Profile的处理中,主要使用了Factory和Provider的模式,并且三层结构(界面、业务、数据)非常清晰。
2.运行在两端的“控件输入校验”。
(1)先看一个对“邮政编码”zipcode 进行校验的代码段:
<asp:TextBox ID="txtZip" runat="server" Columns="12" CssClass="checkoutTextbox" MaxLength="20"
Width="65px"></asp:TextBox><br />
<asp:RequiredFieldValidator ID="valZip" runat="server" ControlToValidate="txtZip"
CssClass="asterisk" Display="Dynamic" ErrorMessage="Please enter postal code."></asp:RequiredFieldValidator> <asp:RegularExpressionValidator
ID="valZip1" runat="server" ControlToValidate="txtZip" CssClass="asterisk" Display="Dynamic"
ErrorMessage="Postal code invalid." ValidationExpression="/d{5}(-/d{4})?"></asp:RegularExpressionValidator>
前一个Validator保证用户输入值(当验证执行时,如果输入控件包含的值仍为初始值而未更改,则该输入控件验证失败。这会防止用户使关联的输入控件保持不变),后一个Validator则是使用正则表达式来确保输入符合一定的格式(除非浏览器不支持客户端验证,或者显式禁用客户端验证(EnableClientScript 属性设置为 false),否则服务器端验证和客户端验证都要执行)。
(2)自定义的验证
<script type="text/javascript" language="javascript">
function ClientValidate(source, arguments) {
。。。
}
</script>
<asp:CustomValidator ID="valExpDate2" runat="server" ClientValidationFunction="ClientValidate"
ControlToValidate="txtExpDate" Display="Dynamic" ErrorMessage="Expiration date invalid."
OnServerValidate="ServerValidate"></asp:CustomValidator>
3.WizardSteps控件
这也是Asp.net2中新增加的一个控件,大大方便了向导类页面的创建。
(1)一个有用的特性:所有输入元素可以持续访问。我们知道,当server.transfer方法被调用的时候,target页面可以通过PreviousPage对象访问前一个对象中的控件。在向导控件中,则更进一步,你在向导的任何一个步骤中都可以访问任何其他步骤(包括出现在你后面的步骤)的控件。当然,从实现机制上,很有可能(未验证)整个向导就是在一个页面上,通过部分隐藏的方式就可以表现出页面跳转的效果。但从使用方式上来说,这样确实方便,参见如下代码(CheckOut.aspx.cs):
/// <summary>
/// Changing Wiszard steps
/// </summary>
protected void wzdCheckOut_ActiveStepChanged(object sender, EventArgs e) {
if (wzdCheckOut.ActiveStepIndex == 3) {
billingConfirm.Address = billingForm.Address;
shippingConfirm.Address = shippingForm.Address; //the control of step 4
ltlTotal.Text = Profile.ShoppingCart.Total.ToString("c");
if (txtCCNumber.Text.Length > 4)
ltlCreditCard.Text = txtCCNumber.Text.Substring(txtCCNumber.Text.Length - 4, 4);
}
}(2)一个向导步骤里至少要有一个asp:linkButton,不然页面无法呈现。StepType="Complete"的步骤可以除外。这一点感觉报错上稍微有点弱智,让我这样的新手比较摸不着头脑。当然,实际上这个强制性的要求并不过分,因为孤立的向导面板是不存在的。
(3)CommandName VS OnCommand VS onclick。在向导步骤的链接按钮中,看到了CommonName="MoveNext"这样的语句,当时想当然的就认为电击按钮就会触发这样一个方法,结果自己写了一个方法,把方法名付给CommandName,却没有起到想象中的效果,付给OnCommand属性才可以。目前还不清楚为什么会有这样雷同的两个属性的存在。但CommandName定义了一个单击 LinkButton 控件时会引发的 Command 事件的名字,并且可以配合CommandArguments提供事件的参数,感觉功能还挺强的,并且功能和事件处理程序所在的位置很有关系。另外,关于onclick与oncommand的区别,二者都是由click动作引起的,但是oncommand可以传递CommandName属性和commandargument属性,如果有很多按钮存在,可以通过commandname属性来区分是哪个按钮被按了(难道commandname就这么点儿用?)。留待以后再看吧。
4.数据层(DAL)和业务层(BLL)的交互
(1)关键词:工厂模式,依赖注入
DataAccess是连接DAL和BLL的关键角色:一方面,它是数据层的工厂,为上层提供合适的DAL;一方面,它充分利用了C#的反射能力,实现了运行期的DAL类型注入。下面这段摘自PetShop.DALFactory.DataAccess,只要看懂了这段代码,相关几部分的关系就很清楚了:
private static readonly string path = ConfigurationManager.AppSettings["WebDAL"];public static PetShop.IDAL.ICategory CreateCategory() {
string className = path + ".Category";
return (PetShop.IDAL.ICategory)Assembly.Load(path).CreateInstance(className);
}上面的代码中,path定义在web.config的<appSettings>子节点内,指明了DAL层代码所处的名字空间。默认的值是PetShop.SQLServerDAL,如果使用的数据库是Oracle,把这个字段改为PetShop.OrableDAL即可。通过这样的配置,很容易的就实现了程序对多种数据库类型的支持,并且代码的结构非常清晰、干净。
(2)关键代码:PetShop.BLL,PetShop.DALFactory, PetShop.IDAL, PetShop.SqlServerDAL
真正直接出现在三层结构中的是BLL和SQLServerDAL,另外两部分应附属于数据层。当然,如果你的系统不支持多个数据库,附属的两部分就可以省掉了。PetShop作为一个示范程序,考虑的东西比我们平时的开发要稍稍多一些。
5.使用SqlCacheDependency 实现数据库级缓存
6.使用消息队列进行非同步的订单处理
7.使用membership进行用户身份管理
身份验证(MemberShip)和身份信息管理(Profile)都是asp.net2.0中多次提到的新功能。其中MemberShip本身应该算一种业务层逻辑,但.net在界面层提供了Login,CreateUserWizard等登录和注册控件,都对MemberShip提供了很好的支持;而MemberShip本身又可以通过类库中的SqlMembershipProvider类去访问下层数据,所以确实可以为程序员省去不少代码。如果你好奇SqlMemberShipProvider是如何提供这些功能的,可以参考PetShop.MemberShip.OracleMemberShipProvider的实现。
再来说一说Provider的机制吧。不了解.net以前的实现,但就现在来看,这种模式带来了两方面的好处:(1)便于程序对不同数据库管理器的扩展。从直观上来说,如果你使用了不同的数据库,换一个Provider就行了。(2)便于业务的纵向切割。我们常说的Presentation,Business和Data三层结构是对程序的横向切割,而根据具体的业务类型进行的分解是纵向的。像MemberShip,Profile都是这种切割的实例,因为所有网站都有这样的业务需要,所以MS把它们给提取出来了。因此,我们也可以尝试思考一下自己开发的站点有哪些功能可以用类似的思想实现。
8.使用泛型创建强类型的列表
以下面的根据产品分类获取产品列表的代码为例,有了泛型技术的支持,列表中每一个都是强类型的实体(petshop中的所有实体类的定义都放置于Model目录下)。这样一方面提高了类型安全,一方面提高了运行效率,这些都是泛型的基本好处,此处就不赘述了。
public IList<ProductInfo> GetProductsByCategory(string category) {
IList<ProductInfo> productsByCategory = new List<ProductInfo>();
SqlParameter parm = new SqlParameter(PARM_CATEGORY, SqlDbType.VarChar, 10);
parm.Value = category;
//Execute a query to read the products
using (SqlDataReader rdr = SqlHelper.ExecuteReader(SqlHelper.ConnectionStringLocalTransaction, CommandType.Text, SQL_SELECT_PRODUCTS_BY_CATEGORY, parm)) {
while (rdr.Read()) {
ProductInfo product = new ProductInfo(rdr.GetString(0), rdr.GetString(1), rdr.GetString(2), rdr.GetString(3), rdr.GetString(4));
productsByCategory.Add(product);
}
}
return productsByCategory;
}9.使用System.Transactions进行事务管理
这个名字空间也是2.0中刚刚加入的,这使得我们可以在不注册COM+事务的前提下处理分布事务。在PetShop.BLL.OrderSynchronous中有这样一段代码,是在加入一个订单的时候从账户上扣除相应的金额,这显然是一个Transaction,要么commit,要么rollback。看看这段代码:
public void Insert(PetShop.Model.OrderInfo order) {
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required)) {
dal.Insert(order);
// Update the inventory to reflect the current inventory after the order submission
Inventory inventory = new Inventory();
inventory.TakeStock(order.LineItems);
// Calling Complete commits the transaction.
// Excluding this call by the end of TransactionScope's scope will rollback the transaction
ts.Complete();
}
}有经验的程序员一看就明白事务处理的方法了。如果有兴趣的话,大家可以找出以前petshop3中的相应代码看一看,是用COM+实现的。
10.其他
(1)看看PetShop安装目录下的DecryptWebConfig.bat脚本,可以发现aspnet_regiis.exe命令的两个被微软隐藏的用法,可用于web.config文件中指定字段的加密和解密:
C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/aspnet_regiis.exe -pef "connectionStrings" "E:/Program Files/.NET Pet Shop 4.0/Web" C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/aspnet_regiis.exe -pdf "connectionStrings" "E:/Program Files/.NET Pet Shop 4.0/Web"
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/xingyukun2006/archive/2006/08/04/1018124.aspx