yifeng's profile私人空间PhotosBlogListsMore ![]() | Help |
|
|
June 10 Duwamish深入剖析-结构篇 选择自 jogongchen 的 Blog
Duwamish深入剖析-结构篇 摘要: 本文深入详细的介绍了Duwamish网上电子书店例程的结构框架,并详细的分析了该结构的若干特点和设计模式。 目录: 引言 Duwamish介绍 结构分析 设计思想 代码示例 总结 引言: 能够作为Visual Studio .Net附带的例子,Duwamish一定包含了微软.Net设计队伍希望向开发者传达的某些信息,而事实上,Duwamish也的确能够称作是一个.Net开发者学习的经典示例,无论是从其设计架构,编程技巧或代码风格,都向我们展示了一个标准的.Net企业级应用程序所应该具有的特点。所以,通过研究Duwamish示例,高手能够领悟到.Net应用架构的设计思想,低手能够学习到.Net的编程技巧,实在是老少皆宜。 :) 不过,本文的目的更多的是针对中级.Net学习者,这类读者往往已经熟悉了C#或者是VB.NET的语法,会用一些基本的类库,并已经会做一些比较小的程序。但是当他们开始着手开发一个真正具有实用价值的企业级应用的时候,却有种无处下手的感觉。如果你正巧属于这类学习者,请跟着我深入到Duwamish的世界中去,相信你一定会得到收获。 Duwamish介绍: Microsoft公司每次推出新技术,总是会相应的推出一些公开源代码的应用范例来说明该项新技术的特点,而开发者也能通过研究该范例的代码来达到迅速掌握新技术并与以实施的目的。Microsoft通过对一个虚拟的在网上销售图书的电子商务公司网上销售系统应用的创建,向用户展现了典型的网上购物实践中最为普遍的电子商务企业对客户 (B2C) 模式,它包括成员资格、帐户管理、购物车、搜索和结帐过程等基本功能。Duwamish经历了三个版本4.0,5.0和7.0版,每一个版本的发布都印证了技术进步的过程,每一个版本都代表了当时最先进的技术动向。这里将要研究和讨论的是Duwamish的最高版本7.0版,经历了COM/COM+技术以及Microsoft DNA架构的Duwamish,在最新的版本中完全采用了.Net技术及架构,比以前显得更加先进和成熟。
如果您安装了Visual Studio .Net的话,您可以在您的VS.Net 的Enterprise Samples目录下找到并安装它,例如:C:\Program Files\Microsoft Visual Studio .NET\Enterprise Samples\,或者您还可以到http://astradigital.com/Duwamish7Vb/这个地址去看看它在Internet的一个演示实例。其它有关Duwamish的详细介绍资料请参考Visual Studio .Net附带的MSDN帮助,地址是:ms-help://MS.VSCC/MS.MSDNVS.2052/dwamish7/html/vtoriDuwamishBooks70.htm,这里不再赘述。 Duwamish结构分析: Duwamish 7.0 是一个典型的N层架构,其结构分为四个逻辑层: Web 层 Web 层为客户端提供对应用程序的访问。这一层是作为 Duwamish.sln 解决方案文件中的 Web 项目实现的。Web 层由 ASP.NET Web 窗体和代码隐藏文件组成。Web 窗体只是用 HTML 提供用户操作,而代码隐藏文件实现各种控件的事件处理。 业务外观层 业务外观层为 Web 层提供处理帐户、类别浏览和购书的界面。这一层是作为 Duwamish.sln 解决方案文件中的 BusinessFacade 项目实现的。业务外观层用作隔离层,它将用户界面与各种业务功能的实现隔离开来。除了低级系统和支持功能之外,对数据库服务器的所有调用都是通过此程序集进行的。 业务规则层 业务规则层是作为 Duwamish.sln 解决方案文件中的 BusinessRules 项目实现的,它包含各种业务规则和逻辑的实现。业务规则完成如客户帐户和书籍订单的验证这样的任务。 数据访问层 数据访问层为业务规则层提供数据服务。这一层是作为 Duwamish.sln 解决方案文件中的 DataAccess 项目实现的。 比较令人困惑的是其中的业务外观层和业务规则层,很多人在学习N层结构开发的时候,听得最多的是三层结构,分别为:表示层,中间层和数据层。Duwamish的WEB层和数据访问层比较好理解,也就是传统意义上的表示层和数据层,那么业务外观层和业务规则层和我们熟悉的中间层有什么联系呢? 设计思想: 在Web应用程序中,有部分操作只是简单的从数据库根据条件提取数据,不需要经过任何处理,而直接将数据显示到网页上,比如查询某类别的图书列表。而另外一些操作,比如计算定单中图书的总价并根据顾客的级别计算回扣等等,这部分往往有许多不同的功能的类,操作起来也比较复杂。我们可以先想象一下,如果我们采用三层结构,这些商业逻辑一般是会放在中间层,那么对内部的这些大量种类繁多,使用方法也各异的不同的类的调用任务,就完全落到了表示层。这样势必会增加表示层的代码量,将表示层的任务复杂化,和表示层只负责接受用户的输入并返回结果的任务不太相称,并增加了层与层之间的耦合程度。 为了解决这个问题,我们先来看看《设计模式》一文中对Facade模式的描述: 意图: 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 适用性: 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。 当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过Facade进行通讯,从而简化了它们之间的依赖关系。 结构图:
上文提出的这个矛盾,正好和设计模式中Facade模式中所描述的需要解决的问题非常吻合,在《设计模式》中提出的解决的办法就是引入一个Facade对象,让这个Façade来负责管理系统内部类的调用,并为表示层提供了一个单一而简单的接口。这个Façade对象,在我们的Duwamish的设计中,就是BusinessFacade(业务外观)层。 以下是Duwamish的结构关系图:
我们从图中可以清楚的看到,浏览器首先调用的是表示层WEB,然后WEB将请求发送给业务外观层,业务外观层对请求进行初步的处理,判断是否需要调用业务规则层,还是直接调用数据访问层获取数据。最后由数据访问层访问数据库并按照来时的步骤返回结果到浏览器(对于图中涉及到其它的结构模块以后会分别予以详细介绍)。 代码示例: 以下是两种不同处理路径的代码示例: 获取商品目录 表示层调用业务外观层: productSystem = new ProductSystem(); categorySet = productSystem.GetCategories(categoryID); 业务外观层直接调用数据层: public CategoryData GetCategories(int categoryId) { if ( dsCommand == null ) { throw new System.ObjectDisposedException( GetType().FullName ); } return FillCategoryData("GetCategories", "@CategoryId", categoryId); } 添加定单 表示层调用业务外观层: public void AddOrder() { ApplicationAssert.CheckCondition(cartOrderData != null, "Order requires data", ApplicationAssert.LineNumber); ApplicationLog.WriteTrace("Duwamish7.Web.Cart.AddOrder:\r\nCustomerId: " + cartOrderData.Tables[OrderData.CUSTOMER_TABLE].Rows[0][OrderData.PKID_FIELD].ToString()); cartOrderData = (new OrderSystem()).AddOrder(cartOrderData); } 业务外观层调用业务规则层: public OrderData AddOrder(OrderData order) { ApplicationAssert.CheckCondition(order != null, "Order is required", ApplicationAssert.LineNumber); (new BusinessRules.Order()).InsertOrder(order); return order; } 业务规则层调用数据层: public bool InsertOrder(OrderData order) { //此处省略复杂的处理逻辑 if ( isValid ) { using (DataAccess.Orders ordersDataAccess = new DataAccess.Orders()) { return (ordersDataAccess.InsertOrderDetail(order)) > 0; } } else return false; } 总结: 通过分析Duwamish7的结构设计,我们掌握了Façade模式,并学习到了如何通过Façade模式对应用结构进行改进,同时了解了Duwamish7的基本概念和处理流程,为以后深入分析和学习Duwamish7的的其它部分打下了一个基础 Duwamish深入剖析-配置篇 选择自 jogongchen 的 Blog
Duwamish深入剖析-配置篇 摘要: 本文详细介绍了Duwamish网上电子书店的Web.config配置文件的结构处理方式以及用途,阐述了配置文件的各功能模块中的作用。 目录: 引言 配置节处理程序声明 自定义配置节 配置节处理程序 总结 参考资料 作者 引言: 几乎在每本介绍Asp.Net编程的书里,在谈到如何管理数据库连接字符串的时候,都是采用将数据库连接字符串以如下形式放在Web.Config文件中: ppSettings> dd key="ConnectionString" value="data source=localhost;initial catalog=Database;user id=;password="/> </appSettings> 然后在程序中采用以下方式访问: System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"] 这样做的好处非常明显:当数据库有变动的时候,只需要改变web.config中的连接字符串,而不需要重新编译整个应用程序,给应用的部署和移植带来非常大的方便。 如果你以为web.config的作用仅限于此的话,那你就错了,web.config的配置功能非常强大,它可以支持使用自己的 XML 配置标记扩展标准的 ASP.NET 配置设置集,在Duwamish中一定程度上的体现了它的功能,下面我将要详细分析Duwamish的web.config文件,让大家能了解到开发一个典型的.Net WEB应用程序的配置技术。 配置节处理程序声明 在Duwamish解决方案中,Web.config文件是放在WEB项目下,因为web.config需要IIS和Asp.Net Runtime的管理和支持,所以它应该放在一个虚拟目录下,我们先来看看它的第一部分: <configSections> <section name="ApplicationConfiguration" type="Duwamish7.SystemFramework.ApplicationConfiguration, Duwamish7.SystemFramework" /> <section name="DuwamishConfiguration" type="Duwamish7.Common.DuwamishConfiguration, Duwamish7.Common" /> <section name="SourceViewer" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections> 这里定义了三个配置节处理程序声明(Section),按照规定它们必须出现在配置文件顶部 <configSections> 和 </configSections> 标记之间,在这里,它们只用到了name和type属性,其中,name属性定义了指定配置节的名称,而type属性则规定了指定从配置文件中读取节的配置节处理程序类的名称,有两个部分,前面为处理程序的类名,后面为Assembly名(Assembly必须位于bin目录中)以及版本号,公匙等信息。 他们具体表示什么意思呢?比如第一个section,意思就是告诉Asp.Net系统,当在程序中使用System.Configuration.ConfigurationSettings.GetConfig("ApplicationConfiguration")这个静态方法来读取ApplicationConfiguration配置节的时候,会调用Duwamish7.SystemFramework.ApplicationConfiguration这个类来对这个配置节进行处理。关于配置节处理类,我们会在后面详细讨论,我们先继续往下看web.config文件。 自定义配置节 在<system.web>节点之后,我们可以看见以下的XML元素(关于system.web节点的说明已经有大量文章介绍,这里不再重复): pplicationConfiguration> <!-- Trace file settings --> dd key="SystemFramework.Tracing.Enabled" value="False" /> <!-- Set this to the file with the trace settings. This file should be relative to the root application directory. --> dd key="SystemFramework.Tracing.TraceFile" value="DuwamishTrace.txt" /> <!-- The TraceLevel for this switch. --> dd key="SystemFramework.Tracing.TraceLevel" value="4" /> <!-- This switch name. The trace level for this name can be set through environment variables or the registry --> dd key="SystemFramework.Tracing.SwitchName" value="DuwamishTraceSwitch" /> <!-- This description of the Tracing.SwitchName switch --> dd key="SystemFramework.Tracing.SwitchDescription" value="Error and information tracing for Duwamish" /> <!-- Event log settings Note: The default Duwamish7 event source name is created in the local machine during setup. If you wish to log events to a different event source that event source must exist. --> dd key="SystemFramework.EventLog.Enabled" value="True" /> dd key="SystemFramework.EventLog.Machine" value="." /> dd key="SystemFramework.EventLog.SourceName" value="Duwamish7" /> <!-- Use the standard TraceLevel values: 0 = Off 1 = Error 2 = Warning 3 = Info 4 = Verbose --> dd key="SystemFramework.EventLog.LogLevel" value="1" /> </ApplicationConfiguration> <DuwamishConfiguration> <!-- Settings specific to the Duwamish application --> dd key="Duwamish.DataAccess.ConnectionString" value="server=LUYAN\NetSDK;User ID=Duwamish7_login;Password=password;database=Duwamish7;Connection Reset=FALSE" /> dd key="Duwamish.Web.EnablePageCache" value="True" /> dd key="Duwamish.Web.PageCacheExpiresInSeconds" value="3600" /> dd key="Duwamish.Web.EnableSsl" value="False" /> </DuwamishConfiguration> <SourceViewer> <!-- Valid directories for source browsing. Keep these lower case. --> dd key="." value=" " /> dd key="modules" value=" " /> dd key="..\common\data" value=" " /> dd key="..\systemframework" value=" " /> dd key="..\business\facade" value=" " /> dd key="..\business\rules" value=" " /> dd key="..\dataaccess" value=" " /> dd key="secure" value=" " /> dd key="docs\common" value=" " /> dd key="docs\dataaccess" value=" " /> dd key="docs\facade" value=" " /> dd key="docs\rules" value=" " /> dd key="docs\web" value=" " /> </SourceViewer> 配置节信息分为两个主区域:配置节处理程序声明区域和配置节设置区域,这里就是刚才定义的三个section的配置节设置区域,它包含实际的配置设置,其用途说明请参见注释,所有配置信息都必须驻留在 <configuration> 和 </configuration> 根 XML 标记之间,配置节设置区域位于 <configSections> 区域之后。 配置节处理程序 前面已经介绍了,section里定义了处理配置节的类:Duwamish7.SystemFramework.ApplicationConfiguration和Duwamish7.Common.DuwamishConfiguration,他们分别位于SystemFramework和Common项目中,.net规定,所有能够处理配置节的类必须要实现IConfigurationSectionHandler接口,而IConfigurationSectionHandler接口很简单,只有一个object Create(object parent,object configContext,XmlNode section)方法,这个方法不需要主动调用,它是在ConfigurationSettings.GetConfig这个静态方法的时候自动调用的,也就是说,当你在程序中使用ConfigurationSettings.GetConfig来获取配置节的时候,.net会根据改配置节声明中所定义的类名和路径自动实例化配置节处理类,并调用Create方法。下面是Duwamish的处理类调用流程: 1、在global.asax的Application_OnStart方法里面调用ApplicationConfiguration.OnApplicationStart静态方法,并获得应用程序根的绝对路径。 void Application_OnStart() { ApplicationConfiguration.OnApplicationStart(Context.Server.MapPath( Context.Request.ApplicationPath)); string configPath = Path.Combine(Context.Server.MapPath( Context.Request.ApplicationPath ), "remotingclient.cfg"); if(File.Exists(configPath)) RemotingConfiguration.Configure(configPath); } 2、ApplicationConfiguration.OnApplicationStart静态方法里调用System.Configuration.ConfigurationSettings.GetConfig方法处理配置节: public static void OnApplicationStart(String myAppPath) { appRoot = myAppPath; System.Configuration.ConfigurationSettings.GetConfig("ApplicationConfiguration"); System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration"); System.Configuration.ConfigurationSettings.GetConfig("SourceViewer"); } 大家已经注意到了,Duwamish并没有获取GetConfig返回的值,因为前面已经说过,GetConfig方法会引发配置节处理程序的Create方法,所以,只需要在Create方法中将配置值取出来就行了。 3、配置读取示例:Duwamish7.Common.DuwamishConfiguration类 public Object Create(Object parent, object configContext, XmlNode section) { NameValueCollection settings; try { NameValueSectionHandler baseHandler = new NameValueSectionHandler(); settings = (NameValueCollection)baseHandler.Create(parent, configContext, section); } catch { settings = null; } if ( settings == null ) { dbConnectionString = DATAACCESS_CONNECTIONSTRING_DEFAULT; pageCacheExpiresInSeconds = WEB_PAGECACHEEXPIRESINSECONDS_DEFAULT; enablePageCache = WEB_ENABLEPAGECACHE_DEFAULT; enableSsl = WEB_ENABLESSL_DEFAULT; } else { dbConnectionString = ApplicationConfiguration.ReadSetting(settings, DATAACCESS_CONNECTIONSTRING, DATAACCESS_CONNECTIONSTRING_DEFAULT); pageCacheExpiresInSeconds = ApplicationConfiguration.ReadSetting(settings, WEB_PAGECACHEEXPIRESINSECONDS, WEB_PAGECACHEEXPIRESINSECONDS_DEFAULT); enablePageCache = ApplicationConfiguration.ReadSetting(settings, WEB_ENABLEPAGECACHE, WEB_ENABLEPAGECACHE_DEFAULT); enableSsl = ApplicationConfiguration.ReadSetting(settings, WEB_ENABLESSL, WEB_ENABLESSL_DEFAULT); } return settings; } 这里可以看到,Duwamish其实并没有自己手工从一个XmlNode里面读取数据,而是直接将数据转给一个NameValueSectionHandler做实际的配置读取,它自己所做的工作只是检查是否有实际定义的配置值,如果没有的话,就赋给默认值。 总结: 至此,web.config中的配置值就被读到了配置类的静态变量中,以后在程序的其它地方就可以使用配置类的静态变量来直接访问配置值了,例如,在程序的任何地方,只要输入Duwamish7.Common.DuwamishConfiguration.ConnectionString就可以得到:server=LUYAN\NetSDK;User ID=Duwamish7_login;Password=password;database=Duwamish7;Connection Reset=FALSE这个字符串。更为理想的是,你可以扩展自己的配置节和配置节处理程序,对比较复杂的自定义配置进行预处理。 对比.NET PetShop和Duwamish来探讨Ado.NET的数据库编程模式 选择自 jogongchen 的 Blog
对比.NET PetShop和Duwamish来探讨Ado.NET的数据库编程模式 概述 Ado.NET为我们提供了强大的数据库开发能力,它内置的多个对象为我们的数据库编程提供了不同的选择。但是在允许我们灵活选用的同时,许多初学者也很迷惑,我到底是应该使用DataReader还是应该使用DataAdapter?我只想读取一小部分数据,难道我一定要Fill满整个DataSet吗?为什么DataReader不能和RecordSet一样提供一个数据更新的方法?DataSet到底有什么好处? 在本文中,我将对.NET PetShop的数据库编程模式和Duwamish的数据库编程模式进行一些简单的分析和对比。如果您也有以上疑问的话,相信在读完本文之后,就可以根据具体的需要来制定一个最适合您应用的数据库编程模式。 目录
.NET PetShop和Duwamish简单介绍 相信大家一定听说过有名的"宠物店大战",没错,本文的主角之一就是获胜方.NET PetShop,微软号称以27倍的速度和1/4的代码量遥遥领先于基于J2EE的PetStore宠物商店。虽然SUN也曾对此抱怨过不满,指责此"大战"有水分,不过无论如何,.NET PetShop绝对是一个经典的.NET实例教程,至少为我们提供了一条赶超J2EE的“捷径” :),它的下载地址是:http://www.gotdotnet.com/team/compare
.NET PetShop宠物网上商店首页 而Duwamish则是一个外表简单,内部却极其复杂的一个网上书店的.NET完整应用范例,作为一个微软官方的Sample,它同时提供了C#和VB.NET两种语言版本,并且还附上了大量详尽的中文资料,如果打印出来,实在是居家旅行,临睡入厕必备之物。什么?您没听说过?呵呵,如果您装了Visual Studio .NET的话,它就在您的硬盘上静静的躺着呢,不过还没有被安装,您可以在您的VS.NET 的Enterprise Samples目录下找到并安装它,例如:C:\Program Files\Microsoft Visual Studio .NET\Enterprise Samples\Duwamish 7.0 CS。
Duwamish网上电子书店首页 结构简述 两家商店都采用了n层应用结构(毫无疑问,n层结构的应用架构应该绝对是您开发.NET应用的首选,哪怕您只想做一个网页计数器),不同的是,PetShop采用的是最常见的三层应用结构,分别为表示层,中间层和数据层。而Duwamish则采用的是一个四层应用结构,并使用不同的项目分隔开,分别为表示层,业务外观层,业务规则层和数据层。至于这两种结构分别有什么优点和缺点,以及为什么要这么分层,我们不进行详细讨论,因为本文的重点不在于此。我们主要分析的是他们的数据库编程的模式。 Duwamish数据访问剖析 首先,我们来看看Duwamish书店,它采用的是DataAdapter和DataSet配合的数据存储模式,所不同的是,它对DataSet进行子类化扩展作为数据载体,也就是采用定制的DataSet来进行层间的数据传输,下面是一个定制的DataSet示例: public class BookData : DataSet { public BookData() { // // Create the tables in the dataset // BuildDataTables(); } private void BuildDataTables() { // // Create the Books table // DataTable table = new DataTable(BOOKS_TABLE); DataColumnCollection columns = table.Columns; columns.Add(PKID_FIELD, typeof(System.Int32)); columns.Add(TYPE_ID_FIELD, typeof(System.Int32)); columns.Add(PUBLISHER_ID_FIELD, typeof(System.Int32)); columns.Add(PUBLICATION_YEAR_FIELD, typeof(System.Int16)); columns.Add(ISBN_FIELD, typeof(System.String)); columns.Add(IMAGE_FILE_SPEC_FIELD, typeof(System.String)); columns.Add(TITLE_FIELD, typeof(System.String)); columns.Add(DESCRIPTION_FIELD, typeof(System.String)); columns.Add(UNIT_PRICE_FIELD, typeof(System.Decimal)); columns.Add(UNIT_COST_FIELD, typeof(System.Decimal)); columns.Add(ITEM_TYPE_FIELD, typeof(System.String)); columns.Add(PUBLISHER_NAME_FIELD, typeof(System.String)); this.Tables.Add(table); } ……… } 我们可以看到它有一个BuildDataTables方法,并且在构造函数中调用,这样,定制的Books表就和这个DataSet捆绑在一起了,省得以后还要进行Column Mapping,这真是个好主意,我怎么就没有想到呢? :) 解决了数据结构,接下来看看数据层的代码实现,在Duwamish中,数据层中有5个类,分别是Books,Categories,Customers和Orders,每个类分别只负责有关数据的存取。下面是其中一个类的示例代码: private SqlDataAdapter dsCommand; public BookData GetBookById(int bookId) { return FillBookData("GetBookById", "@BookId", bookId.ToString()); } private BookData FillBookData(String commandText, String paramName, String paramValue) { if (dsCommand == null ) { throw new System.ObjectDisposedException( GetType().FullName ); } BookData data = new BookData(); SqlCommand command = dsCommand.SelectCommand; command.CommandText = commandText; command.CommandType = CommandType.StoredProcedure; // use stored proc for perf SqlParameter param = new SqlParameter(paramName, SqlDbType.NVarChar, 255); param.Value = paramValue; command.Parameters.Add(param); dsCommand.Fill(data); return data; } 这里就是数据层的代码了,我们在这里可以看到Duwamish采用了DataAdapter来将数据填充到定制的DataSet中,然后返回该DataSet。我感到很奇怪的是在数据存取层中竟然可以看到GetBookById这样具体的数据存取方法,虽然最后还是有一个抽象出来的FillBookData方法,但是上面还有三层啊,底层都做到这份上了,那上层都做些什么呢?答案是数据检查,上层基本上都在做一些很严密的数据合法性校验(当然也会包括一些比较复杂的事务逻辑,但是并不多),示例代码如下: public CustomerData GetCustomerByEmail(String emailAddress, String password) { // // Check preconditions // ApplicationAssert.CheckCondition(emailAddress != String.Empty, "Email address is required", ApplicationAssert.LineNumber); ApplicationAssert.CheckCondition(password != String.Empty, "Password is required", ApplicationAssert.LineNumber); // // Get the customer dataSet // CustomerData dataSet; using (DataAccess.Customers customersDataAccess = new DataAccess.Customers()) { dataSet = customersDataAccess.LoadCustomerByEmail(emailAddress); } // // Verify the customer's password // DataRowCollection rows = dataSet.Tables[CustomerData.CUSTOMERS_TABLE].Rows; if ( ( rows.Count == 1 ) && rows[0][CustomerData.PASSWORD_FIELD].Equals(password) ) { return dataSet; } else { return null; } } 在这个方法中,真正进行数据存取的实际上只有 dataSet = customersDataAccess.LoadCustomerByEmail(emailAddress); 这么一句,是直接调用的数据层。其它都是在进行合法性校验,我们可以感悟到,进行一个真正的企业级开发需要考虑的系统健壮性有多么重要。 .NET PetShop数据访问剖析 OK,Duwamish看完了,下面我们来看看PetShop的数据访问机制。 PetShop只有一个项目,它采用的分层办法是将中间层和数据层都写成cs文件放在Components目录里,其中数据层就是一个名为Database的类,它封装了所有对数据库的底层操作。下面是示例代码段: public void RunProc(string procName, out SqlDataReader dataReader) { SqlCommand cmd = CreateCommand(procName, null); dataReader = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection); } 我们看到了一个跟Duwamish截然不同的另一种数据访问方式,它将所有的数据访问方法抽象出来做成一个RunProc方法,至于返回数据呢,呵呵,它有点偷懒,直接返回一个DataReader给你,你自己去读吧。还记得Duwamish采用的层间数据传输载体是什么吗?对了,是DataSet,它被数据层填充后返回给了中间层。但是这里,数据层和传输层的数据传输载体变成了DataReader,实际上,还不能称它为数据载体,因为数据还没开始读呢,在这里,DataReader的作用和指针有点类似,也许我们应该称它为“数据引用”:) 接着往下看,DataReader被怎么“处理”的: public ProductResults[] GetList(string catid, int currentPage, int pageSize, ref int numResults) { numResults = 0; int index=0; SqlDataReader reader = GetList(catid); ProductResults[] results = new ProductResults[pageSize]; // now loop through the list and pull out items of the specified page int start = (int)((currentPage - 1) * pageSize); if (start <= 0) start = 1; // skip for (int i = 0; i < start - 1; i++) { if (reader.Read()) numResults++; } if (start > 1) reader.Read(); // read the data we are interested in while (reader.Read()) { if (index < pageSize) { results[index] = new ProductResults(); results[index].productid = reader.GetString(0); results[index].name = reader.GetString(1); index++; } numResults++; } reader.Close(); // see if need to redim array if (index == pageSize) return results; else { // not a full page, redim array ProductResults[] results2 = new ProductResults[index]; Array.Copy(results, results2, index); return results2; } } 注意到currentPage和pageSize了吗?原来在这里就进行了数据分页,只返回满足需要的最少的数据量,而不是象我们很多喜欢偷懒的人一样,简单的将整个DataTable一股脑的绑定到DataGrid,造成大量的数据冗余。 在这里,数据被真正的读出来,并且被手动填充到一个自定义的对象数组中,我们来看看这个数组的定义: public class ProductResults { private string m_productid; private string m_name; // product props public string productid { get { return m_productid; } set { m_productid = value; } } public string name { get { return m_name; } set { m_name = value; } } } 非常之简单,不过我有点奇怪为什么不使用struct呢?是不是.NET中struct和class的性能差距已经可以忽略不计了? 分析总结 通过观察这两个商店的具体实现,我们得到了两个不同的数据访问模式,Duwamish采用的是以DataSet为核心,因为DataSet提供了这方面大量的相关方法,所以整个应用的数据传输,数据格式定义,数据校验都围绕着DataSet来进行,整个架构定义非常清晰和严谨,但是却显得有些庞大。PetShop在整个程序中没有采用一个DataSet,程序非常的简洁,轻灵,但是没有Duwamish那么强的健壮性。这两个程序是Microsoft公司不同的小组写出来的代码,所以有着不同风格。不过都应该能代表.NET的标准模式。看到这里,你应该对文章开头提出的那些疑问有一个比较形象的认识了吧。 另外,请再次注意,PetShop在打开数据连接之后,并没有马上读取数据,而是将DataReader传递给另外的对象来执行数据读的操作,然后才关闭连接。这样,数据连接的时间加长了,而数据库连接是一项非常宝贵的服务器资源,相比之下,Dawamish在连接数据库之后马上进行填充,然后迅速释放掉数据库连接的方式更加有利于大量用户的并发访问。 再一点,上文的程序中没有提到更新操作,PetShop采用的是使用Command对象执行单个存储过程的方式来进行更新操作,是属于一种在线即时数据更新模式。而Dawamish采用的是DataAdapter的Update方法,将DataSet的改变一次性的提交到数据库中,属于离线数据更新模式。这种模式的好处是可以一次性更新大批量数据,减少数据库的连接次数。缺点是如果数据库在改动非常频繁的情况下需要实时的跟踪数据变化就不合适了。需要根据具体的情况采用具体的数据更新办法。 总的来说,如果您只需要快速的读取数据并显示出来,推荐您采用DataReader,如果您需要对数据进行大量的修改,还有大量并发访问的可能,而且不需要实时的跟踪数据库的变化,推荐您使用DataSet。当然,这两种情况有点极端了,实际的应用环境也许有着很复杂的条件,具体需要您自己审时度势,综合采用,不过我个人还是比较喜欢PetShop那种轻灵的风格 :) May 13 Duwamish部署方案篇Duwamish 7.0 支持两种多计算机部署方案。非分布式部署方案在一台 Web 主机上部署 Web 层、业务外观、业务规则和数据访问层,但可以在群集间复制该 Web 主机以达到负载平衡。分布式方案在单独的服务器上部署特定的组件。例如,业务外观、业务规则和数据访问层可能位于独立于 Web 主机的服务器上。在实际部署中数据库服务器通常位于单独的计算机上。
1, 非分布式部署方案 在一台 Web 主机上部署 Web 层、业务外观、业务规则和数据访问层,然后通过软件(如Application Center 2000)或硬件来实现网络场(Web Farm)内各个Web Server的负载平衡。
在本机默认安装Duwamish 7.0时,是采用非分布式部署方案。
2, 分布式部署方案 使用 .NET Framework 远程处理技术将应用程序分布到多台计算机中。简单而言,就是IIS Web Server和Application Server分离,其中Web层(包括SystemFramework和Common项目)部署在IIS Web上,BusinessFacde/BusinessRules/DataAccess层(包括SystemFramework和Common项目)一起部署在Application Server上。
Duwamish 7.0 使用 HTTP/二进制而不是 HTTP/SOAP。使用 HTTP 的决定基于要通过端口 80 上的防火墙的要求。使用二进制而不是 SOAP 的决定基于性能上的考虑。对于大的数据块,二进制的性能优于 SOAP。因此,如果要传递大的数据块(例如,数组、数据集或数据表),则使用二进制格式化程序。如果要传递小的数据块,则选择使用 SOAP 还是二进制格式化程序是无关紧要的。传递整数时两者的性能都很好。
3, 如何将Duwamish 7.0部署为基于.Net Remoting的分布式系统 下面采用Microsoft提供的Deploytool工具自动进行(其实手工也很方便): C:\Program Files\Microsoft Visual Studio .NET 2003\Enterprise Samples\Duwamish 7.0 CS>deploytool deploy RemoteMachine=localhost path="C:\Program Files\Microsoft Visual Studio .NET 2003\Enterprise Samples\Duwamish 7.0 CS\Duwamish7_Remote" 在command line窗口输入上述命令行代码。
[10/29/2004 6:43:43 AM] Creating directory C:\Program Files\Microsoft Visual Studio .NET 2003\Enterprise Samples\Duwamish 7.0 CS\Duwamish7_Remote on W1MIS38 [10/29/2004 6:43:43 AM] Stopping all internet services on W1MIS38 [10/29/2004 6:43:59 AM] Deploying Duwamish7 Business Facade on W1MIS38 [10/29/2004 6:43:59 AM] Creating web site on W1MIS38 [10/29/2004 6:44:00 AM] Generating remoting configuration files [10/29/2004 6:44:00 AM] Starting all internet services on W1MIS38 [10/29/2004 6:44:02 AM] Starting Default Web Site on W1MIS38 [10/29/2004 6:44:02 AM] Deployment successful
运行结果: (1)在IIS创建中创建Web Application(Duwamish7_Facade),本地路径为:C:\Program Files\Microsoft Visual Studio .NET 2003\Enterprise Samples\Duwamish 7.0 CS\Duwamish7_Remote\web 作为Remote Server端,Bin目录下是BusinessFacde/BusinessRules/DataAccess层(包括SystemFramework和Common项目)DLL文件。 其中web.config文件中包含所有Remote Objects的配置,如 <wellknown mode="Singleton" type="Duwamish7.BusinessFacade.ProductSystem, Duwamish7.BusinessFacade" objectUri="ProductSystem.rem" />
(2)Web层创建remotingclient.cfg配置文件,对Application Server而言,Web层相当与Client端。 remotingclient.cfg配置文件中包含formatter的设置(binary ),选择二进制格式化程序来序列化消息,注意是出于性能的考虑。
(3)Web application加载remotingclient.cfg配置文件 Web application在global.asax文件包括如下代码,在Application_OnStart事件中加载Retmoting配置文件。 void Application_OnStart() { ApplicationConfiguration.OnApplicationStart(Context.Server.MapPath( Context.Request.ApplicationPath )); string configPath = Path.Combine(Context.Server.MapPath( Context.Request.ApplicationPath ),"remotingclient.cfg"); if(File.Exists(configPath)) RemotingConfiguration.Configure(configPath); }
其中前面代码ApplicationConfiguration.OnApplicationStart()是调用Duwamish7.SystemFramework.ApplicaitonConfiguration的OnApplicationStart()方法,用来初始化application root和读取web.config中的配置信息(将在《Duwamish代码分析篇》中进行具体分析)。 Duwamish架构分析篇Duwamish是Microsoft提供一个企业级的分布式系统架构,如果开发企业级的分布式系统,可以模仿这种架构,如果是开发一些简单的系统,则完全可以简化。
以前也学习过Duwamish范例,只是发现不同时间,不同经历,有不同的体会。正如卢彦所说的一样:通过研究Duwamish示例,高手能够领悟到.Net应用架构的设计思想,低手能够学习到.Net的编程技巧,实在是老少皆宜。
因此,这里再次学习并体验一次Duwamish范例。
1,Duwamish 7.0 结构分为四个逻辑层(FROM MSDN): Web 层 - Presentation Web 层为客户端提供对应用程序的访问。这一层是作为 Duwamish.sln 解决方案文件中的 Web 项目实现的。Web 层由 ASP.NET Web 窗体和代码隐藏文件组成。Web 窗体只是用 HTML 提供用户操作,而代码隐藏文件实现各种控件的事件处理。 业务外观层 - Business Facade 业务外观层为 Web 层提供处理帐户、类别浏览和购书的界面。这一层是作为 Duwamish.sln 解决方案文件中的 BusinessFacade 项目实现的。业务外观层用作隔离层,它将用户界面与各种业务功能的实现隔离开来。除了低级系统和支持功能之外,对数据库服务器的所有调用都是通过此程序集进行的。 业务规则层 - Business Rules 业务规则层是作为 Duwamish.sln 解决方案文件中的 BusinessRules 项目实现的,它包含各种业务规则和逻辑的实现。业务规则完成如客户帐户和书籍订单的验证这样的任务。 数据访问层 - Data Access 数据访问层为业务规则层提供数据服务。这一层是作为 Duwamish.sln 解决方案文件中的 DataAccess 项目实现的。
除了上述四个逻辑层外,Duwamish 7.0 还包含封装在 Duwamish.sln 解决方案文件中的 Common 项目内的共享函数。“通用”(Common) 层包含用于在各层间传递信息的数据集。Common 项目还包含 Duwamish.sln 解决方案文件中的 SystemFramework 项目内的应用程序配置和跟踪类。
2,各个逻辑层之间的关系图(FROM MSDN)及其调用Sequeance图示例: 下面是Categories.aspx web页面获取Category的Description的整个调用过程。 (1)实例化ProductSystem对象 (2)调用ProductSystem的GetCategories()方法 (3)检测参数的合法性 (4)创建Categories::DataAccess对象实例 (5)返回上述对象 (6)调用Categories::DataAccess对象的GetCategories()方法 (7)创建CategoryData::Common对象实例 (8)返回上述对象 (9)返回CategoryData::Common对象实例,该实例中已经包含了需要的数据 (10)返回CategoryData::Common对象实例给web/Client端 (11)检测数据的合法性 (12)读取并显示结果:Category的Description
SystemFramework项目包含一些application需要的配置参数,ApplicationLog日志类和ApplicationAssert参数校验类。SystemFramework项目为所有其他的项目所引用。
Common项目包含了用于在各层间传递信息的数据集,如上述的CategoryData继承System.Data.DataSet,既不是所谓的typed DataSet,也不是一般的DataSet,不过简单实用,这是基于.Net Remoting开发分布式系统用来tier与tier之间交互数据的一种方法。Common项目也被其他的项目引用,SystemFramework项目除外。
BusinessFacade项目中所有的Classes继承MarshalByRefObject class,显然是让准备将BusinessFacade tier部署为Remote Objects。不过,实际上默认这里并没有将其部署为Remote Objects,Web层仍然调用本地对象(《Duwamish部署方案篇》将分析这个问题)。
3,Summary
在开发基于.Net Framework企业级分布式系统时,上述架构值得推荐,但也并非完美无暇,实际上存在一些值得改进的地方。显然,不可能一个范例适合所有的实际情况么,要求太苛刻了。其实,Enterprise Samples中的另外一个范例Fitch and Mather 7.0,其架构和Duwamish就有些不同了。
如果是开发本地的系统,就不要模仿Duwamish架构(看看上面获取Category的Description调用过程就知道了,太费劲。),如Business Facade和Business Rules中Classes应采用fine-grained interface设计,层与层之间的交互参数也不必全部采用DataSet,适当的时候采用setter/getter就可以了,这样不仅可以提高开发效率,而且有助于提高performance, maintainability and reusability。 Duwamish代码分析篇二(2)Code Snippet 2 – CheckCondition Method public static void CheckCondition(bool condition, String errorText, int lineNumber) { //Test the condition if ( !condition ) { //Assert and throw if the condition is not met String detailMessage; GenerateStackTrace(lineNumber, out detailMessage); Debug.Fail(errorText, detailMessage);
throw new ApplicationException(errorText); } }
该方法一般用来在进行前置条件判断,如condition为false,则抛出exception。
4,log日志类-SystemFramework\ApplicationLog.cs ApplicationLog 类实现 Duwamish 7.0 中的记录和跟踪。Web.Config 文件中的配置设置确定是输出到 EventLog 文件、跟踪日志文件还是两者。下面是 Web.Config 文件中的 <ApplicationConfiguration> 节,它指定 EventLog 设置:
<ApplicationConfiguration> <!-- Event log settings --> <add key="SystemFramework.EventLog.Enabled" value="True" /> <add key="SystemFramework.EventLog.Machine" value="." /> <add key="SystemFramework.EventLog.SourceName" value="Duwamish7" />
<add key="SystemFramework.EventLog.LogLevel" value="1" /> <!-- Use the standard TraceLevel values: 0 = Off 1 = Error 2 = Warning 3 = Info 4 = Verbose --> Web.Config 文件的同一节还指定跟踪配置。Duwamish 7.0 跟踪日志的默认位置是:[安装 Visual Studio .NET 的驱动器号]:\Program Files\Microsoft Visual Studio .NET 2003\Enterprise Samples\Duwamish 7.0 CS\Web\DuwamishTrace.txt
在实际的应用系统开发中,用来提供Log功能的类应该比这个ApplicationLog类要好,这样就不去分析了,如Microsoft Exception Management Application Block就不错。
5,Web.config配置文件-使用Web.congfig文件存储application设置 Duwamish 7.0 通过使用 Forms 身份验证来实现安全性。Forms 身份验证将未经授权的用户重定向到Web 窗体,该窗体提示用户输入其电子邮件地址和密码。
(1)配置 Forms 身份验证 Web.config 文件中的设置配置 Forms 身份验证。对于 Duwamish 7.0,Web.Config 文件按如下所述指定 Forms 身份验证的使用: <authentication mode="Forms"> <forms name=".ADUAUTH" loginUrl="secure\logon.aspx" protection="All"> </forms> </authentication> <authorization> <allow users="*" /> </authorization>
authentication元素只能在计算机、站点或应用程序级别声明。如果试图在配置文件中的子目录或页级别上进行声明,则将产生分析器错误信息。
如上所示,Web.Config 将 .ADUAUTH 指定为身份验证 Cookie 的名称。当用户请求受限资源时,公共语言运行库将未经授权的用户重定向到在上面的 Web.Config 设置中指定的Login.aspx。protection="All" 设置指定应用程序使用数据验证和加密来保护 Cookie。若要进一步限制资源,Duwamish 7.0 会将安全资源放置到名为 secure 的子文件夹中并使用额外的 Web.Config 文件(在secure文件夹),在该文件中指定只有经过身份验证的用户才可访问该子文件夹的内容。 <authorization> <deny users="?" /> <allow users="*" /> </authorization>
<deny users="?" /> 标记指定拒绝对所有匿名用户的访问。<allow users="*" /> 标签允许访问所有已验证身份的用户。
经过secure\logon.aspx认证通过的请求,重新定向最初的URL。 // 将已验证身份的用户重定向回最初请求的 URL。 FormsAuthentication.RedirectFromLoginPage("*", false);
(2)用户定义的配置节 <configSections> <section name="ApplicationConfiguration" type="Duwamish7.SystemFramework.ApplicationConfiguration, Duwamish7.SystemFramework" /> <section name="DuwamishConfiguration" type="Duwamish7.Common.DuwamishConfiguration, Duwamish7.Common" /> <section name="SourceViewer" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections>
section元素包含配置节声明,name 指定配置节的名称,type 指定从配置文件中读取节的配置节处理程序类的名称。配置节处理程序(即实现 IConfigurationSectionHandler 接口的类)读取设置。
上面定义了3个配置节处理程序:Duwamish7.SystemFramework.ApplicationConfiguration Duwamish7.Common.DuwamishConfiguration System.Configuration.NameValueSectionHandler
前面两个配置节处理程序是由application提供的,后面的System.Configuration.NameValueSectionHandler是.Net Framework提供的,该类也实现了IconfigurationSectionHandler接口,用来提供配置节中的名称/值对配置信息。
6,web\PageBase.cs基类 最后谈谈web\PageBase.cs基类吧,所有 Duwamish 7.0 中的所有web页都继承名为 PageBase 的基类,该类实现 Duwamish 7.0 应用程序的 ASP.Net 页中使用的常见属性和方法,这种设计方法值得推荐。
Code Snippet分析: /// <summary> /// Handles errors that may be encountered when displaying this page. /// <param name="e">An EventArgs that contains the event data.</param> /// </summary> protected override void OnError(EventArgs e) { ApplicationLog.WriteError(ApplicationLog.FormatException(Server.GetLastError(), UNHANDLED_EXCEPTION)); base.OnError(e); } 重载OnError方法,使application遭遇到未处理错误的时候,自动调用ApplicationLog.WriteError()来记录错误日志。
另外,觉得Duwamish的password处理有些特别,是以byte形式存放在Database中,避免明文的方式,以提高安全性(将在《Duwamish密码分析篇》进行中分析)。 Duwamish代码分析篇一继续前面的2篇POST《Duwamish架构分析篇》和《Duwamish部署方案篇》,这里在代码层次上分析Duwamish 7.0范例,主要目的是解析Duwamish范例中值得推荐的编码风格和提炼出可以重用的代码或Class。
1,读取配置文件类-SystemFramework\ApplicationConfiguration.cs ApplicationConfiguration类用来读取web.config文件中自定义section的配置信息,初始化一些基本设置。 ApplicationConfiguration类实现IconfigurationSectionHandler接口,并需要实现[C#] object Create( object parent, object configContext, XmlNode section )方法,以分析配置节的 XML。返回的对象被添加到配置集合中,并通过 GetConfig 访问。
部分代码片断解释: (1)Code Snippet 1 – ApplicationConfiguration. OnApplicationStart()方法 public static void OnApplicationStart(String myAppPath) { appRoot = myAppPath; System.Configuration.ConfigurationSettings.GetConfig("ApplicationConfiguration"); System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration"); System.Configuration.ConfigurationSettings.GetConfig("SourceViewer"); } ConfigurationSettings 类还提供了一个公共方法ConfigurationSettings.GetConfig() 用于返回用户定义的配置节的配置设置,传入的参数section name,如"ApplicationConfiguration",表示要读取的配置节。
NameValueCollection nv=new NameValueCollection(); //实例化NameValueCollection 类对象 nv=(NameValueCollection)ConfigurationSettings.GetConfig("ApplicationConfiguration "); //返回用户定义的配置节的设置 return nv["SystemFramework.Tracing.Enabled"].ToString(); //返回特定键值,如SystemFramework.Tracing.Enabled
不过,ConfigurationSettings.GetConfig()方法在调用时,自动调用Create()方法,可以看到ApplicationConfiguration.Create()方法正是用来读取指定section的配置,并初始化设置参数。
Global.asax 的 Application_OnStart 事件处理程序向 SystemFramework 的ApplicationConfiguration 类 OnApplicationStart 方法发出调用,正是上述的代码片断。
(2)Code Snippet 2 - Global.asax中Application_OnStart()方法 void Application_OnStart() { ApplicationConfiguration.OnApplicationStart(Context.Server.MapPath( Context.Request.ApplicationPath )); string configPath = Path.Combine(Context.Server.MapPath( Context.Request.ApplicationPath ),"remotingclient.cfg"); if(File.Exists(configPath)) RemotingConfiguration.Configure(configPath); } 该方法肩负二大任务:(1)调用ApplicationConfiguration.OnApplicationStart()方法,并传入application的根目录(Root Directory)。(2)检测Client端的remoting配置文件是否存在(其实是web server端),如果存在,则读取并初始化remoting配置信息,如配置通道Channel等等,详见《Duwamish部署方案篇》。
2,读取web.config中Duwamish相关的一些配置-Common\DuwamishConfiguration.cs Common\DuwamishConfiguration.cs也实现IconfigurationSectionHandler接口,与SystemFramework\ApplicationConfiguration.cs类相似。
DuwamishConfiguration配置节包括如下一些配置信息: Database connection string(Database连接串)Duwamish.DataAccess.ConnectionString,是否允许页面缓存Duwamish.Web.EnablePageCache,页面缓存过期时间Duwamish.Web.PageCacheExpiresInSeconds,是否允许SSL连接Duwamish.Web.EnableSsl等等。
如上所述,调用DuwamishConfiguration Class 是由SystemFramework\ApplicationConfiguration.cs的OnApplicationStart()方法完成的: System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration");
看看页面缓存配置在web page中如何使用的(web\book.aspx.cs文件为例): // // If everything succeeded, then enable page caching as indicated // by the current application configuration. // if ( DuwamishConfiguration.EnablePageCache ) { //Enable Page Caching... Response.Cache.SetExpires ( DateTime.Now.AddSeconds(DuwamishConfiguration.PageCacheExpiresInSeconds)); Response.Cache.SetCacheability(HttpCacheability.Public); } 在Page_Load事件中最后判断是否允许页面缓存。
3,验证数据合法性类-SystemFramework\ApplicationAssert.cs SystemFramework\ApplicationAssert.cs Class用来进行错误检测,并调用SystemFramework\ApplicationLog.cs Class记录错误日志。
学习其中的部分代码片断: (1)Code Snippet 1 – Check Method [ConditionalAttribute("DEBUG")] public static void Check(bool condition, String errorText, int lineNumber) { if ( !condition ) { String detailMessage = String.Empty; StringBuilder strBuilder; GenerateStackTrace(lineNumber, out detailMessage); strBuilder = new StringBuilder(); strBuilder.Append("Assert: ").Append("\r\n").Append(errorText).Append("\r\n").Append(detailMessage); ApplicationLog.WriteWarning(strBuilder.ToString()); System.Diagnostics.Debug.Fail(errorText, detailMessage); } }
[ConditionalAttribute("DEBUG")]定义Check()方法为conditional method,如果预处理符号(preprocessor symbol)没有定义,compiler不仅忽略该方法,而且忽略对该方法的调用,和#if DEBUG / #else / #endif有些类似。
该方法用来判断条件condition是否为true,如果为false,则调用SystemFramework\ApplicationLog.WriteWarning()方法记录错误日志。
|
|
|