Duwamish 7.0笔记

不太会设计框架,所以最近打算学学.net自带的Duwamish,便去网上找了一些好文章,这样学习起来速度会快一点,虽然有些还看不太懂,先贴出来,以后回头再看方便点



一、 Duwamish 7.0 的架构
Duwamish 7.0
vs.net 中微软提供的一个企业级的示例,最近在学设计方面的东西,所以有时间边看边学这个示例。做了一些笔记,和大家一起讨论。
    
学习 Duwamish 7.0 ,首先要看的当然是它的一个整体的结构式,在 msdn 自带的帮助文件中,我们看到了它的一个整体的结构,如下图所示:
Duwamish 7.0 分为四层,分别为 :
l    Web

相当于是用户界面层,直接与用户交互的 web 窗体,从源码中我们可以看到,它有以下的一些界面页:  
页面名称   
作用
book.aspx   
用以显示图书的详细信息的页面
default.aspx 
默认页,显示当天的精选书
categories.aspx 
用于分类显示图书的页面,它由两部分组成,上半部分显示当天推荐的该分类的图书信息,下半部分显示该分类的
errorpage.aspx
是一个静态页面,显示一成不变的错误信息
searchresults.aspx
显示搜索结果页面,用了一个 datalist 控件显示搜索的结果;不支持分页
shoppingcart.aspx
购物车页,用于填写购书的订单,用 datagrid 控件操作,支持批量修改和更新。不支持删除,设为零时能删除;不用单击 update 按钮就自动更新了 ( 我试了一下 , 好像不行 , 要手动单击后才能更新 ) update 按钮用于修改订购书的数量后刷新价格。
viewsource.aspx
既然是示例,当然可以看源代码了,这一页是专门用于查看源代码的
 
下面的页面是用于管理用户及用户订单系统,微软专门把它放在 secure 文件夹下:
页面名称   
作用
account.aspx 
新客户注册页及客户修改个人信息页;
checkout.aspx
确认购买页面,填写收货人的详细地址和联系方式,填入信用卡的信息,列出购买的清单及总的费用信息。
order.aspx 
显示用户的订单信息,以供用户打印该订单
 
Duwamish 7.0 中,大量的运用了用户控件,各个用户控件的功能不一,用户控件统一放在 modules 文件夹下:
页面名称   
作用
accountmodule.ascx
对应于 account.aspx 页面,新客户注册页及客户修改个人信息
bannermodule.ascx 
每一页都包含有该用户控件,它定义的页面的头部信息,在页面中看到的头上的哪片黑色的区域就是它了,包含一个图片,三个按钮。
categoriesmodule.ascx 
每一页都包含有该用户控件,它显示了书籍的分类信息。在页面的左边的” Browse Categories ”文字开始到”  Behind The Scenes ”文字结束就是该控件的界面内容了
checkoutmodule.ascx
对应于 checkout.aspx 页,因为 checkout.aspx 页是一个按步骤操作的页 ( panel 控件控制 ) ,每走一页, checkoutmodule.ascx 控件中的箭头就往前走或往后退一格。在父页面中通过控制 checkoutmodule.ascx 控件的 Stage 属性来控制
dailypickmodule.ascx 
用于显示推荐的图书信息,在 default.aspx 页和 categories.aspx 页中用到
searchmodule.ascx
搜索功能控件,每页的搜索功能都由这个控件实现
 viewsourcemodule.ascx 
查看源码功能控件,每页的查看源功能都是由这个用户控件实现
 
 
   
所有用户界面层就是上述的页面和用户控件,看起来其实也不多。

l    
业务外观层
什么是业务外观层,这是四层结构里面新增的东西?有什么用呢?现在我也不知道,先让我们看看 Duwamish 7.0 中业务层中包含一些什么?打开 BusinessFacade 项目,这里面的东西就是 Duwamish 7.0 业务外观层了,看看里面有下面的这些文件组成:

页面名称   
作用
CustomerSystem.cs
CustomerSystem 类是客户系统的业务外观层,它为客户子系统提供了一个简单的接口,该类支持远程处理的应用程序中跨应用程序域边界访问。它继承自 MarshalByRefObject 类。从 Duwamish 7.0 中提供的 visio 图上看, CustomerSystem 类只有三个方法,分别是:
GetCustomerByEmail 方法(从 email 和密码获得客户的信息), UpdateCustomer 方法(更新客户的信息,接收一个 CustomerData 对象), CreateCustomer 方法(当然是用于创建一个新的客户了)
OrderSystem.cs
OrderSystem 类用于处理订单的业务外观,它只有两个方法: GetOrderSummary 方法(用于统计订单),
AddOrder 方法(用于新增一个订单)
ProductSystem.cs
ProductSystem 类用于处理书的业务外观,它的方法比较多,有五个分别是: GetCategories 方法(通过分类的 id 获得类别自身的信息); GetCategoryItems 方法(通过分类的 id 获得该类下的所有的书的信息); GetDailyPickItems 方法(通过分类的 id 获得该类下的推荐书的信息); GetItemById 方法(通过书的 id 获得有关书的信息);
GetSearchItems 方法(根据指定的检索字段条件以及书名的关键字查询书的信息)
    
    
看了上面业务层的类以后,我们发现所有的业务层类都只有方法,没有属性,我的理解是它是所有与用户界面有关的操作的一些方法的定义。
    
这两天又到微软中国网站上看看,发现了卢彦写的几篇关于 Duwamish 7.0 的文章其中的一篇就是有关为什么要加业务外观层的,看了后,才完全理解,下面我们来看看卢彦的这篇文章的片断:
    
Web 应用程序中,有部分操作只是简单的从数据库根据条件提取数据,不需要经过任何处理,而直接将数据显示到网页上,比如查询某类别的图书列表。而另外一些操作,比如计算定单中图书的总价并根据顾客的级别计算回扣等等,这部分往往有许多不同的功能的类,操作起来也比较复杂。我们可以先想象一下,如果我们采用三层结构,这些商业逻辑一般是会放在中间层,那么对内部的这些大量种类繁多,使用方法也各异的,不同的类的调用任务就完全落到了表示层。这样势必会增加表示层的代码量,将表示层的任务复杂化,和表示层只负责接受用户的输入并返回结果的任务不太相称,并增加了层与层之间的耦合程度。
    
为了解决这个问题,我们先来看看《设计模式》一文中对 Facade 模式的描述:
意图 :
    
为子系统中的一组接口提供一个一致的界面, Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性 :
    
当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。 Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过 Facade 层。  
    
客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 Facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。  
    
当你需要构建一个层次结构的子系统时,使用 Facade 模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过 Facade 进行通讯,从而简化了它们之间的依赖关系。  
结构图:
 
上文提出的这个矛盾,正好和设计模式中 Facade 模式中所描述的需要解决的问题非常吻合,在《设计模式》中提出的解决的办法就是引入一个 Facade 对象,让这个 Facade 来负责管理系统内部类的调用,并为表示层提供了一个单一而简单的接口。这个 Facade 对象,在我们的 Duwamish 的设计中,就是 BusinessFacade (业务外观)层。

l    
业务规则层
业务规则层包含各种业务规则和逻辑的实现 , Duwamish 7.0 中业务规则层完成如客户帐户和书籍订单的验证这样的任务。它包含了两个类:

页面名称   
作用
Customer.cs 
它有一个私有的常量 REGEXP_ISVALIDEMAIL ,用于验证客户的输入的 email 地址是否正确;
insert 方法用于验证和新建一条客户记录;
update 方法用于更新某个客户的信息;
GetCustomerByEmail 私有方法通过 email 验证是否已存在该客户的信息; Validate 私有方法用于验证 customer 数据是否正确,它通过调用类内的 IsValidEmail( 验证 email 方法 ) IsValidField( 验证字段的内容是否超长字段规定的长度 ) 来验证整个 customer 对象是否正确
Order.cs
私有的常量 MINIMUM_SHIPPING_CHARGE ,私有的常量 STANDARD_ITEM_COUNT
CalculateTax 方法用于计算税收。
CalculateShipping 用于计算购物的总价格。
InsertOrder 方法用于插入一个订单;
IsValidField 用于验证字段的正确性

    
从上面的两个类设计来看,业务逻辑层的功能是对业务对象是否符合业务逻辑的验证,无需验证的对象则无需写其业务层,从业务外观层我们看到, CustomerSystem. 类对应于 Customer 类,它对 CustomerSystem. 提交的 CustomeData 对象进行验证。 Order 类则对 OrderSystem 类提交的 OrderData 对象进行验证。但是我们看到没有对 ProductSystem 类的验证的对象,这是因为在 Duwamish 7.0 中没有提供对 product (在这里是指书)对象的更新或新增操作。我想,如果它提供了对书的维护功能的话,它也肯定有这个业务规则对象。
    
原文请看: http://www.microsoft.com/china/community/ ... icle/TechDoc/duwamish.asp
l    
数据访问层
数据访问层负责对业务层提供数据操作,也就是它负责和底层的数据库打交道。业务层或者通用层中所有的对象都通过数据访问层的对象访问数据库。数据访问层中的类是按业务对象来组织的,每个业务对象中包含的数据可能存在不同的几种数据表中,它由数据访问类统一组织成一个概念中的对象,它相当于是一个面向对象的数据库层,负责映射面向对象与关系数据库间的关系。
对数据库的所有操作均由存储过程完成,数据层只是在前台调用后台的存储过程。

页面名称   
作用
Books.cs
Books 类有许多的方法,构造函数 Books 首先创建了一个 SqlDataAdapter 对象,然后指定了该对象的 SqlConnection 对象。 Books 类实现了 Idisposable 接口,实现的 Dispose 方法,用于销毁该对象。下面的各个方法都是根据不同的参数获得 BookData ,包括: GetBooksByCategorId( 对应于同名的存储过程,通过类别 id 获得 bookdata 数据集对象 ),GetDailyPickBooksByCategoryId (对应于同名的存储过程,通过类别 id 获得推荐的 bookdata 数据集对象), GetBookById (通过书的 id 取得书的信息), GetBooksByAuthor (通过作者名获得书的信息,可能有 N 条记录), GetBooksByISBN( 通过 isbn 获得书的信息 ) GetBooksBySubject GetBooksByTitle ,上述的方法其它都是通过 FillBookData (通过传进行存储过程名作参数和参数值执行 SqlDataAdapter 对象的 fill 方法填充 bookdata 对象)方法执行对应的存储过程来获得 BookData
Categories.cs
Categories 类的方法比较少,除了构造函数和 dispose 方法外,就只有 GetCategories 方法(通过分类的 id 获得该类的父、本、子三级的分类对象)和 FillCategoryData 私有方法(为 GetCategories 方法从底层数据库中获取数据到 CategoriesData )。
Customers.cs
Customers 类除了构造函数和 dispose 方法外,有三个公开的方法, LoadCustomerByEmail 方法(调用 GetLoadCommand 方法 , 获得 sqlcommand 对象后,执行 GetCustomerByEmail 存储过程,获得 customerdata 对象), UpdateCustomer 方法(更新整个的 customer 对象,在 UpdateCustomer 存储过程中又调用了 UpdateCustomerAddress 存储过程来更新 Addresses 表。它有一个缺点就是每次更都会完全的更新两张表的所有的内容), InsertCustomer (与 UpdateCustomer 方法类似,它调用 InsertCustomer InsertAddress 两个存储过程完成两个表的插入工作 , 唯一不同的插入操作是先插入主表,然后再插入从表。更新是先从表,后主表。),及三个私有的为前三个公开的方法服务的方法。 GetLoadCommand 方法 ( 生成调用 GetCustomerByEmail 存储过程的 sqlcommand 命令返回 ) GetInsertCommand( 生成调用 InsertCustomer 存储过程的 sqlcommand 命令返回,它用了 sqlparameter 对象的 sourcecolumn 属性映射对 dataset 的某个对象 ) GetUpdateCommand( 它与 GetInsertCommand 方法类似 )
Orders.cs 
Orders 除了构造函数和 dispose 方法外,也只有一个公开的方法 InsertOrderDetail 方法(插入一个订单到数据库 , 详细的说明看源码注释)及一个私有方法 GetInsertCommand( 初始化 DataAdapter 对象的 Insert 命令参数集 )

l    
通用层
映射关系数据库表到实际应用的类(对象)层。相当于是一个面向对象的数据库层,把物理的数据库表的字段映射成业务对象:

页面名称   
作用
BookData.cs
BookData 类继承自 dataset 类,创建了一个 datatable 表,用于存储书的数据。支持序列化。(序列化有什么用?它没有声明 authors 字段,但填充的时却有这个字段?)
CategoryData.cs 
CategoryData 类继承自 dataset 类,创建了一个 datatable 表,用于存储书分类的数据。支持序列化。
CustomerData.cs
CustomerData 类继承自 dataset 类,创建了一个 datatable 表,用于存储书的数据。支持序列化。
OrderData.cs
OrderData 类也继承自 dataset 类,但它包含了五个表,各个表字段不一样。

l    
系统架构层

页面名称   
作用
ApplicationConfiguration
读取 web.config 里自定义节点的值
DuwamishConfiguration
应用程序配置和跟踪

三、编程技巧学习
1
     存储过程技巧
1
     输出参数可以当输入参数使用
2
     字符串字段的累加的方法
我们看看 GetBookById 存储过程的一段源码:
把这段单独拷出来,在查询分析器运行,我们发现这段是用来取某本书的作者的列表,一本书可能有多个作者,
就是把书的作者名一个个取出来,累加到 @AuthorList 变量中,各个作者用逗号隔开,最后一句 SELECT @AuthorList = LEFT(@AuthorList,LEN(@AuthorList) - 1) 把最后一个作者后的逗号去掉。
3
     存储过程参数与 dataset 中字段的映射
Customers 类中的 GetInsertCommand 方法中,我们看到了下面的代码:
sqlParams[PKID_PARM].SourceColumn = CustomerData.PKID_FIELD;
                    sqlParams[PKID_PARM].Direction = ParameterDirection.Output; 

sqlParams[EMAIL_PARM].SourceColumn = CustomerData.EMAIL_FIELD;
        
它运用了 sqlparameter 对象的 SourceColumn 属性,该属性用于指定 sqlparameter 对象的值与 dataset 中字段的映射。它是双向,即是输入值,也是输出值。 sqlParams[EMAIL_PARM].SourceColumn = CustomerData.EMAIL_FIELD; 这一句表示把 CustomerData(dataset 对象 ) EMAIL_FIELD 字段值映射成 sqlParams[EMAIL_PARM] 的参数值。
4
    
2
    Web  编程技巧
1
     运用 ado.net 中的 dataview 对已存在结果集进行再查询
cart 类的 AddItem 方法中我们可看到源码:
.系统配置技巧
4.    
其它
1
     对象的序列化
cart 类中,我们可以看到该类实现了 Iserializable 接口,首先我们了解一下什么是序列化,我以前也没有接触过,所以上网查了一下,在微软的网站中找到了这篇文章 ---.NET  中的对象序列化(原文网址: http://www.microsoft.com/china/msdn/ ... t/html/objserializ.asp )。为什么要使用序列化?最重要的两个原因是:将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;按值将对象从一个应用程序域发送至另一个应用程序域。例如,序列化可用于在  ASP.NET  中保存会话状态,以及将对象复制到  Windows  窗体的剪贴板中。它还可用于按值将对象从一个应用程序域远程传递至另一个应用程序域。
序列化是指将对象实例的状态存储到存储媒体(例如说硬盘)的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。
要实现  ISerializable ,需要实现  GetObjectData  方法以及一个特殊的构造函数,在反序列化对象时要用到此构造函数。我们看看在眼里 cart 类的持殊的构造函数:
这个构造函数在反序列化的时候被调用,用于从磁盘中获得 OrderData 对象。
再看看 GetObjectData  方法的代码:
该方法把 cartOrderData 对象序列化,也就相同于把它存到磁盘了。在反序列化,也就是调用上面的那个构造函数,再把它从磁盘中取出来。
PROCEDURE  GetBookById
    
@BookId  INT
AS
    
--  max size = 10 Authors
     DECLARE  @AuthorList  nvarchar ( 480 )

    
SET  NOCOUNT  ON

    
--  initialize @AuthorList
     SELECT  @AuthorList  =  ""

    
--  build list of authors
     SELECT  @AuthorList  =  @AuthorList  +  a.Name  +  ", "
      
FROM  Books b, 
           BookAuthor ba, 
           Authors a
     
WHERE  b.ItemId  =  @BookId
       
AND  ba.ItemId  =  b.ItemId
       
AND  a.PKId  =  ba.AuthorId

--  remove last comma
  SELECT  @AuthorList  =  LEFT ( @AuthorList , LEN ( @AuthorList -  1 )


      
SELECT  @AuthorList  =  @AuthorList  +  a.Name  +  ", "
      
FROM  Books b, 
           BookAuthor ba, 
           Authors a
     
WHERE  b.ItemId  =  @BookId
       
AND  ba.ItemId  =  b.ItemId
       
AND  a.PKId  =  ba.AuthorId

    
public  void  AddItem( int  itemId, String itemDescription, Decimal itemPrice)
        
{
            DataTable itemTable 
= OrderItems;
            DataView itemSource 
= new DataView(itemTable);
        
            
//search for item, check to see if the item has already been ordered
            
//通过itemid查找item,检查是否已预定该item,利用DataView的RowFilter属性,设置其过滤条件。
            
//接下来用DataView的count属性执行查询并返回满足条件的记录数
            itemSource.RowFilter = "ItemNumber = " + itemId.ToString();
            
            
//如果已经订了该书,增加数量
            if (itemSource.Count > 0)
            
{
                DataRowView sourceRow 
= itemSource[0];
                
short count = (short)(sourceRow[OrderData.QUANTITY_FIELD]);
                
//maximum allowed for any specific item is 50
                
//每种书最多允许订50本
                if (count < 50)
                
{
                    
//bump the quantity by one
                    
//订购书本的数量加一,更新orderdata对象(数量,总价)
                    count += 1;
                    sourceRow[OrderData.QUANTITY_FIELD] 
= count;
  sourceRow[OrderData.EXTENDED_FIELD] 
= (Decimal)sourceRow[OrderData.PRICE_FIELD] * count;
                }

            }

            
//先前没有订该书
            else
            
{
        
                
//It's a new item
                
//在orderdata里面新增一条记录
                DataRow itemRow = itemTable.NewRow();
                itemRow[OrderData.ITEM_NUMBER_FIELD] 
= itemId;
                itemRow[OrderData.QUANTITY_FIELD] 
= 1;
                itemRow[OrderData.DESCRIPTION_FIELD] 
= itemDescription;
                itemRow[OrderData.PRICE_FIELD] 
= itemPrice;
                itemRow[OrderData.EXTENDED_FIELD] 
= itemPrice;
        
                
//Add it to the table
                itemTable.Rows.Add(itemRow);
}

}


3
private  Cart(SerializationInfo info, StreamingContext context)
{
            
try
            
{
                cartOrderData 
= (Common.Data.OrderData)info.GetValue(KEY_ORDERDATA, typeof(Common.Data.OrderData));
            }

            
catch
            
{
                
// Leave cartOrderData null if it hasn't been serialized
        }

}

void  ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
      
if (cartOrderData != null)
      
{
           info.AddValue(KEY_ORDERDATA, cartOrderData);
       }

 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值