Saturday, August 20, 2005

Duwamish

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 ServerApplication Server分离,其中Web层(包括SystemFrameworkCommon项目)部署在IIS Web上,BusinessFacde/BusinessRules/DataAccess层(包括SystemFrameworkCommon项目)一起部署在Application Server上。
(image placeholder)
 
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 ApplicationDuwamish7_Facade),本地路径为:C:\Program Files\Microsoft Visual Studio .NET 2003\Enterprise Samples\Duwamish 7.0 CS\Duwamish7_Remote\web 作为Remote Server端,Bin目录下是BusinessFacde/BusinessRules/DataAccess层(包括SystemFrameworkCommon项目)DLL文件。
其中web.config文件中包含所有Remote Objects的配置,如
<wellknown mode="Singleton" type="Duwamish7.BusinessFacade.ProductSystem, Duwamish7.BusinessFacade" objectUri="ProductSystem.rem" />

2Web层创建remotingclient.cfg配置文件,对Application Server而言,Web层相当与Client端。
remotingclient.cfg配置文件中包含formatter的设置(binary ),选择二进制格式化程序来序列化消息,注意是出于性能的考虑。

3Web application加载remotingclient.cfg配置文件
Web applicationglobal.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.ApplicaitonConfigurationOnApplicationStart()方法,用来初始化application root和读取web.config中的配置信息(将在Duwamish代码分析篇》中进行具体分析)。

DuwamishMicrosoft提供一个企业级的分布式系统架构,如果开发企业级的分布式系统,可以模仿这种架构,如果是开发一些简单的系统,则完全可以简化。
 
以前也学习过Duwamish范例,只是发现不同时间,不同经历,有不同的体会。正如卢彦所说的一样:通过研究Duwamish示例,高手能够领悟到.Net应用架构的设计思想,低手能够学习到.Net的编程技巧,实在是老少皆宜。
 
因此,这里再次学习并体验一次Duwamish范例。
 
1Duwamish 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图示例:(image placeholder)
下面是Categories.aspx web页面获取CategoryDescription的整个调用过程。
1)实例化ProductSystem对象
2)调用ProductSystemGetCategories()方法
3)检测参数的合法性
4)创建Categories::DataAccess对象实例
5)返回上述对象
6)调用Categories::DataAccess对象的GetCategories()方法
7)创建CategoryData::Common对象实例
8)返回上述对象
9)返回CategoryData::Common对象实例,该实例中已经包含了需要的数据
10)返回CategoryData::Common对象实例给web/Client
11)检测数据的合法性
12)读取并显示结果:CategoryDescription
(image placeholder)
SystemFramework项目包含一些application需要的配置参数,ApplicationLog日志类和ApplicationAssert参数校验类。SystemFramework项目为所有其他的项目所引用。
 
Common项目包含了用于在各层间传递信息的数据集,如上述的CategoryData继承System.Data.DataSet,既不是所谓的typed DataSet,也不是一般的DataSet,不过简单实用,这是基于.Net Remoting开发分布式系统用来tiertier之间交互数据的一种方法。Common项目也被其他的项目引用,SystemFramework项目除外。
 
BusinessFacade项目中所有的Classes继承MarshalByRefObject class,显然是让准备将BusinessFacade tier部署为Remote Objects。不过,实际上默认这里并没有将其部署为Remote ObjectsWeb层仍然调用本地对象(《Duwamish部署方案篇》将分析这个问题)。
 
3Summary
 
在开发基于.Net Framework企业级分布式系统时,上述架构值得推荐,但也并非完美无暇,实际上存在一些值得改进的地方。显然,不可能一个范例适合所有的实际情况么,要求太苛刻了。其实,Enterprise Samples中的另外一个范例Fitch and Mather 7.0,其架构和Duwamish就有些不同了。
 
如果是开发本地的系统,就不要模仿Duwamish架构(看看上面获取CategoryDescription调用过程就知道了,太费劲。),如Business FacadeBusiness RulesClasses应采用fine-grained interface设计,层与层之间的交互参数也不必全部采用DataSet,适当的时候采用setter/getter就可以了,这样不仅可以提高开发效率,而且有助于提高performance, maintainability and reusability

Duwamish代码分析篇
 
Written by: Rickie Lee
Nov. 02, 2004
 
继续前面的2POSTDuwamish架构分析篇》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 访问。
 
 
部分代码片断解释:
1Code 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 方法发出调用,正是上述的代码片断。
 
2Code Snippet 2 Global.asaxApplication_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.configDuwamish相关的一些配置-Common\DuwamishConfiguration.cs
Common\DuwamishConfiguration.cs也实现IconfigurationSectionHandler接口,与SystemFramework\ApplicationConfiguration.cs类相似。
 
DuwamishConfiguration配置节包括如下一些配置信息:
Database connection stringDatabase连接串)Duwamish.DataAccess.ConnectionString,是否允许页面缓存Duwamish.Web.EnablePageCache,页面缓存过期时间Duwamish.Web.PageCacheExpiresInSeconds,是否允许SSL连接Duwamish.Web.EnableSsl等等。
 
如上所述,调用DuwamishConfiguration Class 是由SystemFramework\ApplicationConfiguration.csOnApplicationStart()方法完成的:
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记录错误日志。
 
学习其中的部分代码片断:
1Code 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()方法记录错误日志。
 
2Code 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);
    }
}
 
该方法一般用来在进行前置条件判断,如conditionfalse,则抛出exception
 
4log日志类-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就不错。
 
5Web.config配置文件-使用Web.congfig文件存储application设置
Duwamish 7.0 通过使用 Forms 身份验证来实现安全性。Forms 身份验证将未经授权的用户重定向到Web 窗体,该窗体提示用户输入其电子邮件地址和密码。
 
1)配置 Forms 身份验证
Web.config 文件中的设置配置 Forms 身份验证。对于 Duwamish 7.0Web.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.aspxprotection="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接口,用来提供配置节中的名称/值对配置信息。
 
 
6web\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()来记录错误日志。
 
另外,觉得Duwamishpassword处理有些特别,是以byte形式存放在Database中,避免明文的方式,以提高安全性(将在《Duwamish密码分析篇》进行中分析)
 
 

一、Duwamish 7.0的架构Duwamish 7.0vs.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_COUNTCalculateTax方法用于计算税收。CalculateShipping用于计算购物的总价格。InsertOrder方法用于插入一个订单;IsValidField用于验证字段的正确性    从上面的两个类设计来看,业务逻辑层的功能是对业务对象是否符合业务逻辑的验证,无需验证的对象则无需写其业务层,从业务外观层我们看到,CustomerSystem.类对应于Customer类,它对CustomerSystem.提交的CustomeData对象进行验证。Order类则对OrderSystem类提交的OrderData对象进行验证。但是我们看到没有对ProductSystem类的验证的对象,这是因为在Duwamish 7.0中没有提供对product(在这里是指书)对象的更新或新增操作。我想,如果它提供了对书的维护功能的话,它也肯定有这个业务规则对象。    原文请看:http://www.microsoft.com/china/community/TechZone/TechArticle/TechDoc/duwamish.aspl    数据访问层数据访问层负责对业务层提供数据操作,也就是它负责和底层的数据库打交道。业务层或者通用层中所有的对象都通过数据访问层的对象访问数据库。数据访问层中的类是按业务对象来组织的,每个业务对象中包含的数据可能存在不同的几种数据表中,它由数据访问类统一组织成一个概念中的对象,它相当于是一个面向对象的数据库层,负责映射面向对象与关系数据库间的关系。对数据库的所有操作均由存储过程完成,数据层只是在前台调用后台的存储过程。文件名称    类作用Books.cs    Books类有许多的方法,构造函数Books首先创建了一个SqlDataAdapter对象,然后指定了该对象的SqlConnection对象。Books类实现了Idisposable接口,实现的Dispose方法,用于销毁该对象。下面的各个方法都是根据不同的参数获得BookData,包括:GetBooksByCategorId(对应于同名的存储过程,通过类别id获得bookdata数据集对象),GetDailyPickBooksByCategoryId(对应于同名的存储过程,通过类别id获得推荐的bookdata数据集对象),GetBookById(通过书的id取得书的信息),GetBooksByAuthor(通过作者名获得书的信息,可能有N条记录),GetBooksByISBN(通过isbn获得书的信息)GetBooksBySubjectGetBooksByTitle,上述的方法其它都是通过FillBookData(通过传进行存储过程名作参数和参数值执行SqlDataAdapter对象的fill方法填充bookdata对象)方法执行对应的存储过程来获得BookDataCategories.cs    Categories类的方法比较少,除了构造函数和dispose方法外,就只有GetCategories方法(通过分类的id获得该类的父、本、子三级的分类对象)和FillCategoryData私有方法(为GetCategories方法从底层数据库中获取数据到CategoriesData)。Customers.cs    Customers类除了构造函数和dispose方法外,有三个公开的方法,LoadCustomerByEmail方法(调用GetLoadCommand方法,获得sqlcommand对象后,执行GetCustomerByEmail存储过程,获得customerdata对象),UpdateCustomer方法(更新整个的customer对象,在UpdateCustomer存储过程中又调用了UpdateCustomerAddress存储过程来更新Addresses表。它有一个缺点就是每次更都会完全的更新两张表的所有的内容),InsertCustomer(与UpdateCustomer方法类似,它调用InsertCustomerInsertAddress两个存储过程完成两个表的插入工作,唯一不同的插入操作是先插入主表,然后再插入从表。更新是先从表,后主表。),及三个私有的为前三个公开的方法服务的方法。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    DuwamishConfiguration    太乱了,我把《项目总结》系列和这个打成包,要的,留个email!三、编程技巧学习1.    存储过程技巧1)    输出参数可以当输入参数使用2)    字符串字段的累加的方法我们看看GetBookById存储过程的一段源码:PROCEDURE GetBookById    @BookId INTAS    -- 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    就是把书的作者名一个个取出来,累加到@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方法中我们可看到源码: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,利用DataViewRowFilter属性,设置其过滤条件。            //接下来用DataViewcount属性执行查询并返回满足条件的记录数            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.系统配置技巧4.    其它1)    对象的序列化在cart类中,我们可以看到该类实现了Iserializable接口,首先我们了解一下什么是序列化,我以前也没有接触过,所以上网查了一下,在微软的网站中找到了这篇文章---.NET 中的对象序列化(原文网址:http://www.microsoft.com/china/msdn/library/dndotnet/html/objserializ.asp)。为什么要使用序列化?最重要的两个原因是:将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;按值将对象从一个应用程序域发送至另一个应用程序域。例如,序列化可用于在 ASP.NET 中保存会话状态,以及将对象复制到 Windows 窗体的剪贴板中。它还可用于按值将对象从一个应用程序域远程传递至另一个应用程序域。序列化是指将对象实例的状态存储到存储媒体(例如说硬盘)的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。要实现 ISerializable,需要实现 GetObjectData 方法以及一个特殊的构造函数,在反序列化对象时要用到此构造函数。我们看看在眼里cart类的持殊的构造函数: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} }              这个构造函数在反序列化的时候被调用,用于从磁盘中获得OrderData对象。再看看GetObjectData 方法的代码:void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)        {            if (cartOrderData != null)            {                info.AddValue(KEY_ORDERDATA, cartOrderData);            }        }该方法把cartOrderData对象序列化,也就相同于把它存到磁盘了。在反序列化,也就是调用上面的那个构造函数,再把它从磁盘中取出来.

0 Comments:

Post a Comment

<< Home