开始新项目
无论是迁移已有的ASP.NET 1.1应用程序,还是从头开始一个全新的ASP.NET 2.0应用程序,都最好先制定一个策略,确定如何开始。本章就探讨一些可以在开始新项目时使用的不同技术,帮助用户制定策略。这个策略应在所有的项目中贯彻始终,并随着时间的推移进行一些演化,以执行最佳实践方式。在项目中始终贯彻一个策略,不仅便于在各个项目之间迁移,还有助于培训团队中的新人。本章将介绍一些在开始新项目时可以作为最佳实践使用的技术。
在建立新应用程序时,需要考虑解决方案的组织方式。例如,把所有的代码放在一个网站上,且不准备在以后与其他项目共享,就不是一个好的选择。考虑与其他项目共享代码或定制控件,就比开始一个新项目更好。如果已建立了在每个项目中都要使用的公共服务器控件或实用例程,则只要设置必要的引用,就可以使用它们。
本章要讨论的一个概念是在每个Web窗体上使用BaseUIPage类,而不是让Web窗体继承System.Web.UI.Page,这是在项目中添加新Web窗体时的默认配置。这需要修改每个Web窗体,所以,从一开始就使用BaseUIPage类,比以后在项目中采用它更划算。如果读者对使用基类进行继承比较陌生,不必担心。本章的后面将介绍如何使用基类。Master Page是另一个重要的例子,因为它会影响每个Web窗体,但不需要预先考虑它。如果以后决定使用Master Page,只需修改已有的页面,利用新的Master Page模型即可。
第11章将详细讨论从1.x迁移到2.0的过程,本章是第11章的补充。在迁移过程中,我们可能要执行本章所讨论的一些改变。例如,在迁移过程中,要采用Master Page,或把一些公共代码提取到一个类库中,进行更广泛的重用。在阅读本章时,读者应考虑其中的技术是否能在自己的迁移过程中采用。
本章讨论的许多概念最好在开始新项目时实现。在项目生存期的后期实现这里描述的技术并不是不可能,但如果这些技术不是从一开始就实现,其效果一般不是很好。这里讨论的许多技术还可以打包,通过Visual Studio 2005中的导出模板功能在建立站点时使用。采用最佳实践方式,一个页面接一个页面地重用模板,将省去许多重复工作。本章描述了如何在建立项目的过程中应用模板,第13章会更详细地讨论模板的概念。
假定本章的所有建议和讨论在所有的项目中都有意义是不切实际的。与本书的大多数主题一样,不需要完全采纳这里的建议,只需采用适合于自己公司的技术,开发需要的应用程序即可。
2.1 组织项目和解决方案
在开始开发新的应用程序时,首先要做的一个决策是如何组织项目。对于小型应用程序而言,只建立一个ASP.NET Web文件夹是可以的,有时也很简单,但通常最好有一个多项目的结构。如果应用程序将来会包含几个网站,共享一个数据库结构,或要提供Windows窗体、Windows服务,或其他共享某些公共项目的非Web应用程序类型,就一定要建立多项目结构。
例如,在项目之间共享的最常见的形式是公司多年来开发的、不专用于一个应用程序的实用代码。如果要把这样的代码放在自己的Web项目中,并没有一种简单的方式从其他的Web应用程序中引用它们。而只能复制执行代码,最终,一个版本的几个副本会略有区别。以后如果发现一个错误,该错误将只在一个副本中修改,当产品出现问题时,就必须有三四个问题处理方案,而不是只有一个。
当然,如果Web项目中已经有实用代码或公共代码,完全可以把它们移动到一个公共的类库中。但是,这会导致更多的工作,因为必须修改当前的应用程序,以引用新库。在ASP.NET 2.0中,某些特定的应用程序命名空间在默认情况下并不使用,所以,必须在使用该实用代码或公共代码的每个类中添加命名空间引用。如果一开始采用了多项目结构,并决定在应用程序之间共享代码,就可以避免这个额外的工作。
组织应用程序中的项目并没有什么正确的方式,这里提出的方法并不是一个适用于所有情况的解决方案,但可以给读者指明正确的方向。除非所建立的应用程序非常小,否则,首先创建一个空白的解决方案是最好的方式。
解决方案允许加载多个项目,使用Solution Explorer可以在这些项目中导航。除了作为多个项目的容器之外,在一个项目中引用同一个解决方案的另一个项目时,引用信息会存储在解决方案文件中。
所有的项目都有一个解决方案文件,但有时它是不可见的。在VB.NET和Visual Web Developer项目设置中,用户必须选择File | Add | New Project,添加第二个项目,解决方案才会显示在Solution Explorer中。在Visual C#设置中,解决方案总是显示在Solution Explorer中。无论把网站保存在什么地方,Visual Studio都会在Default Projects Location目录下创建一个解决方案文件。在VB.NET和Visual C#中,选择Tools | Options | Environment | General可以设置这个位置。Visual Web Developer没有修改这个目录的属性。但是,进入My Documents | Visual Studio 2005 | Settings,选择CurrentSettings.vssettings,就可以修改这个位置。在这个文件中,如果找到字符串name="ProjectsAndSolution",再在XML配置文件中找到如下元素,就找到了需要修改的字符串。修改该字符串,使之指向自己选择的位置。
<PropertyValue
name="ProjectsLocation">%vsspv_visualstudio_dir%\Projects</PropertyValue>
下面的XPath表达式可以在XML配置文件中找到该位置:
/UserSettings/ToolsOptions/ToolsOptionsCategory[@name=Environment]/Tool
sOptionsSubCategory[@name=ProjectsAndSolution]/PropertyValue
[@name=ProjectsLocation]
2.1.1 创建解决方案
创建解决方案有三种基本方式:
● 如上所述,可以创建一个新的网站,在Default Project Locations目录下就会自动创建一个解决方案。根据设置的不同,在有多个激活的项目之前,可能在Solution Explorer中看得到或看不到该解决方案。
● 在创建像类库这样的项目时,如果打开了一个已有的项目,就可以把这个新项目添加到已有的解决方案中,或创建一个新的解决方案。如果选择Add to Existing,就不会创建新的解决方案,项目只是添加到当前激活的解决方案中。否则,就必须确定是否要为解决方案创建一个新目录。如果没有选择Create a New Directory,解决方案的名称就与我们添加的项目相同,且放在同一个目录下。对于包含多个项目的解决方案而言,这不是一个理想的设置。
如果选择创建新目录,就应输入新目录的名称,该名称也是解决方案文件的名称。向导执行完后,就在输入位置指定的路径下创建解决方案的目录,在该目录下创建项目。例如,如果项目的名称是Common,路径是c:\,解决方案是MySolution,就会得到如下目录结构:
C:\MySolution
C:\MySolution\MySolution.sln
c:\MySolution\Common\
c:\MySolution\Common\Common.csproj
采用这个方法,只要创建了新目录,就可以在创建公共项目的同时创建解决方案文件,将两步合并为一步。
● 创建解决方案的最后一种常见方法是选择File | New Project。然后,在New Project Wizard中,扩展Other Project Types选项,找到Blank Solution项。
注意:
如果使用了Source Safe,包含在解决方案中的所有项目都必须绑定到同一个Source Safe数据库上,否则就会产生一个错误,Visual Studio 2005将只从一个数据库中加载项目。如果因为项目位于不同的位置,而使加载失败,就应使用本章后面讨论的bin文件引用技术。
2.1.2 创建Web项目
在Visual Studio 2005中,创建ASP.NET项目的方式有了变化。不再选择File | New | Project,接着选择ASP.NET Web Application Wizard;而是只选择File | New Web Site,这会自动运行向导。向导提供了三个选项:File、FTP和HTTP。HTTP项目需要IIS,类似于以前使用ASP.NET v1.1的情况。在开发过程中,我们常常使用基于文件的方法,再使用IIS进行部署。
FTP选项可以在服务器上保存文件,通过FTP访问它们。这是共享环境的一种可能配置,在共享环境下,许多人可以同时使用项目。另外,还可以使用FTP设置远程编辑文件,其中,远程服务器IIS有一个虚拟目录映射到FTP文件驻留的位置。FTP选项的最大缺陷是,不能使用源控件,团队中的多个人可能对项目进行相互矛盾的改动。
文件项目基于开发人员所使用的物理目录。在该目录及其子目录中创建或复制的任何文件或文件夹都是项目的一部分。
如果使用File选项,从Visual Studio中启动网站,就要使用Visual Studio附带的内置Web服务器。但这样就不再有项目文件。Web项目由该Web项目标识的文件夹中的文件组成。不再需要IIS,也不会在默认情况下创建新的IIS虚拟应用程序。
还要注意,不会创建web.config或global.asax文件。虽然这取决于我们选择的模板,但ASP.NET 2.0的目标是创建尽可能少的额外文件,并允许在需要时添加它们。例如,第一次运行调试器或执行其他操作时,如使用Web Administration应用程序,web.config文件会自动创建。global.asax文件不会创建,除非明确添加它。
2.1.3 从Web文件夹中删除文件
在ASP.NET 2.0中没有项目文件的一个副作用是,损失了删除文件的功能。为了解决这个问题,ASP.NET小组在每个Web文件夹项的弹出菜单中增加了Exclude from Project or Include in Project菜单项,从而加入了从Visual Studio中删除文件的功能。
从Web文件夹中删除一项时,Visual Studio会重新命名这一项,加上.exclude后缀,如表2-1所示:
表 2-1
删 除 前 | 删 除 后 |
Default.aspx | Default.aspx.exclude |
Default.aspx.cs | Default.aspx.cs.exclude |
虽然该项从Web文件夹的建立过程中删除,但并没有从源代码控制提供程序管理的项目中删除。在删除源代码控制的项目时,需要指定是否把文件的新名称反映到源代码控制库中。
2.1.4 ASP.NET小组的一个窍门程序
本节也可以称为“最新的项目消息”,因为笔者在完成最后的书稿校对时,ASP.NET小组在几个月的辛勤工作后,公布了一个插件,它可以为Web应用程序创建新的项目模型。创建新模型的原因是回应Visual Studio 2005发布的无项目模型上的早期反馈。在发布的版本中引入新模型的最大问题与建立新应用程序并不相关,而与迁移已有的Visual Studio 2003网站相关。我们主要关注的是新编译模型会生成多个程序集。在迁移不需要这种孤立级别的应用程序时,需要的工作量比预期的大。
将发布的新项目模型并没有替代Visual Studio 2005中的模型,它是一个附加的选项。新模型的运转方式非常类似于Visual Studio 2003中人们已经习惯的方式:
● 在每次编译过程中,所有的代码都编译到\bin文件夹的一个程序集中。
● 所有的文件都在一个项目文件中定义,如果它们只存在于文件夹中,就不编译。
● Visual Studio 2005附带的新Web项目模型要求类文件只能放在app_code文件夹中,而这个模型可以把独立的类文件放在任何地方。
● 该模型使用标准的MSBuild编译,还可以使用标准的MSBuild扩展功能扩展它。
它的编译模型与Visual Studio 2003模型更接近,所以这个模型的转换和使用对已有应用程序的修改要求更少。对于新应用程序,可以根据工作流和开发风格来决定。
这类项目常常用于支持Web公共用户控件和Master Page在项目之间的共享。这是因为单个程序集编译模型非常适合于使用标准的Visual Studio引用功能进行简单的引用。
在出版本书的过程中,这个选项只存在于ASP.NET小组组长Scott Guthrie的预展版本中。在http://webproject.scottgu.com站点上可以找到这个预展版本的更多信息。
2.1.5 创建公共类库项目
一个比较重要的决策是:把所有的代码都放在网站上,还是把公共代码放在一个独立的类库中。完全可以把独立的类、实用的类、定制服务器控件和数据类都放在网站的App_Code文件夹下。
把所有代码都放在一个Web项目中,以后当有另一个项目需要引用这些代码时,问题就来了。此时,有两个选择:可以复制代码,这样代码就重复了;还可以把这些代码移到一个公共类库中,更新已有应用程序中对它的所有引用。如果决定复制代码,则在一个项目中对这些代码的修改不会反映在其他项目中。可以把代码移到一个共享库中,但不能设置很好的命名空间层次,所以必须修改许多代码,才能使用这个新的共享副本。
比较好的方法是事先确定哪些代码要在项目之间共享,再创建一个公共库,其中放置尽可能多的代码。这个新的公共库不仅可以由当前应用程序引用,还可以由将来的应用程序引用。
为了在解决方案中添加新的类库,应确保在Solution Explorer中选择Solution Level,再选择File | Add | New Project。如果这是在网站的外部添加到解决方案中的第一个项目,Solution Explorer会把项目显示为解决方案的分支。
根据要创建的这些类库的数量,策略会有所不同,这大多取决于项目和开发团队的规模。共享的代码类型也可以影响策略。例如,如果要共享数据库代码,并使用两个不同的数据库供应商,则可以把这些代码放在两个不同的库中,通过一个提供程序模型来引用。有关提供程序的更多信息请参见第3章。
在创建库时,还应考虑依赖性,但一定要避免循环引用。例如,创建下面的库就非常糟糕:
● 库A依赖库B
● 库B依赖库C
● 库C依赖库A
这不仅创建起来很复杂,而且,即使在建立过程中没有错误,它也并不是一个强壮的体系结构。这里不深入讨论体系结构,阻止发生这种情况的一种较简单的方式是使用“层”的概念,如图2-2所示。在层的概念中,所有的引用都是向下的,这表示库永远都不能引用其上面的层中的项。
下面更具体地解释这个例子:“实用例程”中的类永远都不会访问其上面的层中的代码。而“应用程序公共代码”中的类可以访问“应用程序数据库访问”和“实用例程”库中的类。
可以在解决方案中创建任意数量的类库。例如,本书将创建一个名为Common的类库。注意,创建太多的类库会增加它们的管理成本,所以应找出最合适的类库数量。使用类库中的文件夹常常就足够了,不需要其他库。
注意:
解决方案中的所有项目不一定要用同一种语言编写。在Visual Studio 2005中,现在可以在同一个项目中混用不同的语言文件。
我们要考虑用于项目的命名空间。一种方法是为Web应用程序选择像Company Name.ApplicationName这样的命名空间,例如Wiley.MVPHacks。还可以给可重用的库指定类似的命名空间,但要确保它是比较一般的,而不是包含某个应用程序名。例如,如果库是用于完成会计任务的,就可以命名为Wiley.Accounting。
另一种常见的方法是扩展命名空间,使项目中的每个文件夹都将其名称添加到项目级的命名空间中。这将增强文件夹结构的直观性,有助于引用其他项目中的类。
项目文件包含默认的命名空间设置。Web项目没有项目文件,所以不能设置默认命名空间。但是,如果在解决方案中添加一个类库,就可以为该类库设置默认命名空间。具体方法是:在Solution Explorer中选择Class Library项目节点的Properties,再选择Application,最后设置Default Namespace字段。
在类库上设置命名空间时,还要考虑给程序集指定的名称。把项目命名为Common是有意义的,因为其代码在应用程序中是公共的,但这会把程序集命名为common.dll。比较好的方法是使程序集的名称匹配项目的根命名空间。例如,Wiley.MVPHacks.Common会使程序集命名为Wiley.MVPHacks.Common.dll,降低了自己所选名称与第三方或组织中其他项目冲突的可能性。
如前所述,在Visual Studio 2005中新建网站时,默认为不指定命名空间。如果转换一个已有的站点,所有已有的页面将保留其以前使用的命名空间,而新添加的页面在默认情况下不会添加命名空间。当然,可以给文件手动添加一个命名空间。另外,如果选择使用app_Code存储一些类,并可能把它们移到一个单独的类库中,最好把某种形式的命名空间放在这些类上。以后在进行这类决策时,就不必做大量的修改了。
数据库项目用于保存SQL脚本、查询和存储过程。数据库项目常常是Visual Studio中开发人员不太知道的一个特性,除非有人告诉他们。不要混淆数据库项目与SQL Server项目(详见本章下一节),数据库项目用于保存SQL脚本,而SQL Server项目用于保存在Microsoft SQL Server上运行的CLR托管代码。
如果当前没有使用数据库项目保存脚本和存储过程,就将它们保存在文件中,再将它们应用于SQL Server。如果这么做很危险,可以只把最初的副本保存在SQL Server中。
为保存存储过程和其他SQL脚本而使用数据库项目,一个很显著的优点是项目可以包含在Visual Studio解决方案中。这样,就很容易在主项目文件和数据库项目之间切换,来访问存储过程了。另外,在搜索时,项目很容易包含在搜索结果中。最后一个优点是,可以集成Visual Studio支持的源代码控制,在存储过程和SQL脚本上进行版本控制。
很容易在本章前面创建的解决方案中添加一个数据库项目。使用File | New Project,扩展Other Project Types部分。然后选择Database类别。这样Database Project模板就会显示在模板列表中,如图2-3所示。
如果使用默认模板,在建立项目时就会创建几个初始文件夹。这些文件夹非常基本,其名称是Queries、Create Scripts等。这些都是初始名称,不需要使用它们。实际上,使用对应用程序有意义的名称,会使项目更有用,更便于维护。
在使用存储过程时,一种方法是把存储过程放在与数据库域对象相关的文件夹中。例如,如果有几个与地址相关的存储过程,就可以创建一个Address文件夹,把这些存储过程存放在该文件夹中。一定要注意,项目提供了一个文件夹层次结构,可用于给脚本分类,一旦脚本运行起来,应用于数据库,这种分类特性就会丧失。在开发过程中,无论存储过程是否位于有意义的文件夹层次结构中,SQL Server都没有文件夹的概念,所以给SQL Server添加了内容后,都只会看到一个大大的列表。
刚才讨论的是把什么代码放在哪个文件夹中,另一个要考虑的重要问题是:是否有非存储的过程脚本也存放在项目中,例如,创建/删除表的脚本或刷新表中数据的脚本。如果决定把它们也存储在数据库项目中,而且将它们存放在存储过程所在的文件夹下,它们就有可能在不正确的时间运行。这是以前在晚上测试备份的一个绝妙方式。这是如何发生的?数据库项目的一个特性是,它可以选择文件夹中的一项,单击“run”或“run on”,在目标数据库上运行脚本。如果把删除/创建脚本混入文件夹,而不知情的团队成员选择所有的项,在数据库上运行它们时,会认为自己只是把存储过程添加到数据库中。他们在发现备份时会非常惊讶!
1. 添加数据库引用
把数据库项目添加到解决方案中时,向导会启动Add Database Reference对话框,如图2-4所示,提示选择一个数据库引用。在数据库项目的一项上执行操作时,要使用这个引用。使用数据库项目不要求必须有引用,所以可以取消这个对话框,现在不选择引用。该引用可以在将来的任何时刻添加或删除。也可以有多个引用,在数据库项目的一项上执行操作时选择一个激活的引用。
图 2-4
如果添加一个新引用,就要提供一些信息,例如服务器名。另外,还要选择验证类型,是否要存储用户和密码。如果可能,在团队环境下工作时,Windows验证提供了一种互不干扰的方式。如果选择使用用户和密码,但不允许存储它们,则每次打开项目时,都要提供用户和密码。
右击一个文件夹,选择Add Existing,就可以把已有的脚本添加到数据库项目上。如果脚本位于另一个位置,就把它们的副本放在目标文件夹中。
如果已有的脚本不在文件中,就可以利用SQL Server管理工具的Generate Script功能。确保选择把每个对象放在单独文件中的选项。如果所有的对象都在一个文件中生成,数据库项目提供的许多优势就会丧失。一定要把文件生成为Windows文本,而不是ANSI,否则,脚本中就会有乱码。
2.1.8 使用SQL Server项目
Microsoft SQL Server 2005中新增的一个特性是可以使用托管代码创建数据库对象,如存储过程、用户定义的函数和用户定义的类型。在托管代码中实现的数据库对象需要添加到SQL Server项目中。我们可能只把在SQL Server中运行的类放在托管代码中,作为本章前面推荐的一个应用程序类库的一部分。而SQL Server项目有特殊的引用和特性,使该项目成为放置这类代码的唯一位置。
完整论述托管数据库对象的创建超出了本书的范围,这里提及它,是因为这类项目可能会应用于我们正在建立的应用程序。在托管代码中编写的数据库对象可以提供很多优势,访问较复杂的语言功能,但它们也不适合所有的应用程序,不是所有的应用程序都需要它们。在使用它们之前,应仔细考虑数据访问的类型和应用程序进行的处理。
只转换运行一个简单查询的Transact-SQL (T-SQL)存储过程,并不适合使用托管的数据库对象功能。相反,对结果中的每一行都要进行大量数学函数计算,或限定结果集时,使用它们比较好。使用托管的数据库对象将允许利用非常丰富的语言语法,在许多情况下,与使用T-SQL开发的相同数据库对象相比,代码更容易维护。
与部署Transact-SQL数据库对象相比,部署托管代码数据库对象会增加应用程序的复杂性。如果在应用程序中使用用户定义的类型,并在查询中返回它们,要特别注意版本策略和到数据库服务器的部署,并调用应用程序安装程序,以确保版本兼容。
2.1.9 使用Web安装和部署项目
第12章将解释网站部署的各个选项。在安装过程中,要考虑创建一个Web安装项目或Web部署项目。在早期建立部署模型,就可以事先考虑最佳实践方式,而不是等到要部署时再考虑。
建立了解决方案,添加了最初的项目后,就要创建引用了,以便Web项目可以查看和利用公共库中的类。如果在公司范围内添加了多个库或使用第三方的库,现在也应建立这些引用。
表2-2描述了可以建立的4类引用:
表 2-2
类 型 | 说 明 |
项目到项目的引用 | 在解决方案包含多个项目,一个项目需要引用同一个解决方案中的另一个项目时使用。在解决方案上进行构建时,这类引用会自动更新。项目包含在解决方案中时,建议使用这类引用。Visual Studio在解决方案文件中添加ProjectReferences标记项,来跟踪这类引用。也就是说,如果在另一个解决方案中也使用了这个项目,而它有项目到项目的引用,就必须重新建立它们 |
Bin | 这表示引用的是应用程序的bin目录外部的一个共享组件,该组件没有在全局程序集缓存(Global Assembly Cache,GAC)中注册。在引用时,这些项会复制到bin目录下,在默认情况下仅在构建过程中更新。如果查看一下该文件夹,除了程序集之外,还创建了一个<assemblyname>.dll.refresh文件。在该文件中包含了位置的引用,生成一个新副本。如果删除了这个文件,在构建过程中就不生成新副本 |
GAC | 用全局程序集缓存(GAC)注册的项不复制到项目的bin文件夹下。在web.config文件的编译部分,会添加一个新项,以跟踪对GAC组件的引用。这不是一个副本文件,所以会自动更新,选择安装到GAC中的当前版本 |
Web引用 | 这类引用用于跟踪Web服务的注册。在添加一个新的Web引用时,会创建一个新的app_WebReferences文件夹,其中包含所生成的客户代理文件。只有在Visual Studio Solution Explorer的app_WebReferences文件夹中选择Update Web References,才会更新这些引用 |
Visual Studio 2005改变了查看和修改Web项目上引用的方式。在以前的版本中,可以使用Solution Explorer中的References树型节点添加和查看引用。这个功能移动到Web项目的Property Pages上。图2-5显示了添加到Property Pages上的新页面,它允许查看和修改引用。要进入Property Pages,可以在Solution Explorer中右击网站,选择Properties。
一个可能混淆的地方是,为什么像System.Web这样的程序集不需要手动引用,而其他System.*程序集需要。默认情况下,在位于框架安装目录下的系统web.config文件中,下面是默认添加为引用的部分程序集:
<compilation defaultLanguage="c#" debug="true">
<assemblies>
<add assembly="System.Data.OracleClient, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Design, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=B03F5F7F11D50A3A"/>
</assemblies>
</compilation>
考虑一下类库在部署过程中如何使用。如果它们仅在一个应用程序中部署,通常应把它们用作私有程序集。在共享情况或不能控制部署机器的其他环境下,必须把类库部署为公共程序集。
如果类库部署到有许多应用程序使用它的机器上,就可以使用全局程序集缓存 (GAC)。这个方法会注册程序集和程序集的特定版本,这样应用程序就可以引用一个公共的版本。其优点是,所有的应用程序都引用相同的版本;如果更新该版本(且版本号不变),所有的应用程序就会同时全部更新。然而,这种方法的缺陷常常超过其减少重复所带来的效益。例如,在生产过程中,我们常常要快速修补一个应用程序,而不是修补使用该模块的所有应用程序。使用GAC将不具备这个功能,除非拥有版本控制能力,可以创建一个新的不同版本,只让指定的应用程序修补引用。在为其他应用程序和将来的部署引用该程序集时,这个版本问题会提高复杂性。如果选择使用GAC,程序集就必须有一个强名,在GAC中注册时使用它。所以必须仔细考虑,确保GAC适合于放置共享程序集。
对项目使用某种形式的源代码控制有几个优点,最大的优点是可以使团队更好地工作。使用源代码控制的人常常会说,他们无数次地查看文件的历史,找出谁或何时进行了修改,而这个修改导致了错误。这还提供了一种好方法,可以使用贴标签的方式或类似的功能重新创建特定的版本,确保在将来的某一刻获得组成某个版本的所有文件。
在完成新项目的建立后,通常就应考虑使用源代码控制了。在建立了所有的初始项目后,就应使解决方案和相关的项目处于源代码控制之下。
有几个源代码控制提供程序供我们选择,本书不打算介绍所有这些提供程序。过去,最常见的提供程序是Visual Studio及其以前产品附带的Visual Source Safe。在Visual Studio 2005中是新的Team System,它提供了一种新的源代码控制功能,可用于5人以上的团队。还有许多第三方供应商的解决方案和开放源代码的解决方案。
源代码控制的完整讨论超出了本书的范围,但即使是在小型项目上,也总是要使用某种源代码控制,来确保项目源代码的安全。
2.4 使用Page基类
默认情况下,ASP.NET应用程序中的所有Wen窗体页面都继承自System.Web.UI.Page。这个类实现了作为HttpHandler的必要功能,ASP.NET运行库在检测到调用某个页面的请求时,就会调用这个类。简单地说,HttpHandler是处理请求的终端。Page类是HttpHandler用于处理和显示Web窗体的一种特定实现方式。第17章将举例说明执行HttpHandler来完成其他任务的方式。
为构建应用程序而建立体系结构模式时,找出减少冗余和灵活构建的方式总是首要的任务。一种可以采用的模式是定义一个页面基类,让所有的页面都继承这个基类,而不是System.Web.UI.Page。这样,即使最初没有使用它,也可以建立一个基础,以便添加所有页面都可见的属性和功能。这个新的基类继承自System.Web.UI.Page,这样就不必在新的页面基类中重复实现所有的功能了。这么做会使新的页面基类建立在System.Web.UI.Page类功能的基础之上。另外,这还将允许新的基类访问系统类的所有受保护的属性、方法和事件。
下面的例子列出了BaseUIPage类的基本实现代码:
/// <summary>
/// Base UI Page
/// </summary>
public class BaseUIPage : System.Web.UI.Page
{
public BaseUIPage()
{
}
}
即使这就是目前添加到该类中的所有代码,也为应用程序建立了一个可扩展的基础。一旦将它付诸实践,就可以在将来给基本页面添加属性、方法或事件处理程序,让它在使用这个基类的所有页面上都起作用。
下一步是给应用程序添加一个Web窗体,修改该Web窗体的代码文件,以继承新的BaseUIPage类。下面的例子列出了使用新类BaseUIPage之前的代码后置文件:
'VB.Net example
Partial Class _Default
Inherits System.Web.UI.Page
End Class
//C# Example
public partial class Default2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
下面是修改代码,以使用新类BaseUIPage的情形:
'VB.Net example
Partial Class _Default
Inherits BaseUIPage
End Class
//C# Example
public partial class Default2 : BaseUIPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
这看起来不错,但似乎是多余的工作。本章的后面将探讨如何使用Visual Studio 2005中的导出模板功能,创建一个可重复使用的页面模板,以避免重复的修改。明确地说,可以避免的重复修改是,把继承自System.Web.UI.Page改为继承自BaseUIPage。
根据所建立的应用程序的规模和复杂性,可以考虑实现多层基本页面。例如,最底层的基本页面是一个实用类库的一部分,该类库不是本应用程序专用的。但继承这个类库是当前建立的应用程序所特定的。它可以提供在这个应用程序环境下比较有用的属性和方法,例如提供ActiveProductID。最后,如果有一组完成类似任务的页面,例如DataInput或SearchResults,就可以继承应用程序专用的基本页面,以创建一个与这组页面相关的页面。这些层都提供了扩展点,有助于减少页面中的冗余。
2.5 使用Master Page
Master Page是ASP.NET 2.0中的一个新增特性,也是窍门程序变成产品特性的一个典型例子。Master Page的目的是定义一个由页面使用的布局模板。在应用程序中,并不是所有的页面都必须使用Master Page。可以让应用程序在不同的页面上使用多个Master Page,有时这是很理想的。Master Page可以定义一个或多个ContentPlaceHolder区域,这样内容页面就可以使用Master Page上提供的默认内容,或者覆盖它们,提供自己的内容。Web窗体页面使用Master Page特性,就变成了内容页面。内容页面仅提供在Master Page定义的ContentPlaceHolder中填充的内容。图2-6是一个定义了三个内容区域的Master Page示例。
在建立新的应用程序时,确定是否要使用Master Page的概念是很重要的。如果在开发循环的后期再决定,就必须修改每个页面,使之变成内容页面。
创建Master Page时,需要考虑由谁处理应用程序的外观和操作方式。我们定义的ContentPlaceHolder会影响布局的灵活性,所以必须仔细考虑站点可能使用的各种配置。
看看图2-6中显示的示例的HTML标记,注意这三个PlaceHolder控件都使用标准的HTML表,在页面上组织它们。图2-6显示了上一个范例的HTML标记。注意ContentPlaceHolder的名称分别是ContentPlaceHolder1、2和3。使用默认名称并没有什么错,但建议选择其他名称,以便更清晰地描述内容区域的用途。这将有助于查看内容页面的开发人员更好地理解该区域的用法。例如,上面的区域叫作ContentTopNav,在使用时会更清楚其用途。
<form id="form1" runat="server">
<div>
<table width="100%">
<tr>
<td colspan="2">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1"
runat="server" />
</td>
</tr>
<tr>
<td width="200px">
<asp:ContentPlaceHolder ID="ContentPlaceHolder2"
runat="server" />
</td>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder3"
runat="server" />
</td>
</tr>
</table>
</div>
</form>
还可以在运行期间动态修改Master Page,进而动态修改站点的布局。详见第17章。
在使用Master Page时,我们的页面并没有继承Master Page,而是合并了Master Page,生成复合的输出页面。实际上,在页面类中,可以使用Page.Master属性访问活动的Master Page。默认情况下,这个属性是System.Web.UI.MasterPage类型。
可以在Master Page上添加定制的属性、方法和事件。使用下面的页面指令,可以修改属性的类型。下面的例子把Page.Master属性改为MVPHacks.BaseClasses.BaseMasterPage:
<%@ MasterType TypeName="MVPHacks.BaseClasses.BaseMasterPage" %>
或者
<%@ MasterType VirtualPath="~/Masters/SiteMaster.master" %>
如果Master Page的类型与MasterType指令上指定的类型不符,就会产生一个错误。例如,如果Master Page上有一个属性ActiveProductID,就可以使用强类型化的名称访问它,如下面的例子所示:
this.Master.ActiveProductID=50;
2.6 建立第一个内容页面
建立了BasePage类,定义了Master Page,指定内容页面的布局后,就该使用它们了。内容页面与一般的Web窗体不同,它们没有自己的窗体元素或其他标记,例如<head />或<body />,而只是用Content控件引用Master Page上的ContentPlaceHolder控件。下面的示例列出了一个页面的HTML标记,该页面使用了在图2-6中定义的Master Page:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master"
AutoEventWireup="true"
CodeFile="Default2.aspx.cs" Inherits="Default2" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1"
Runat="Server" >
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder2"
Runat="Server" >
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder3"
Runat="Server" >
</asp:Content>
可以看出,在内容页面中并没有定义什么实际的东西。在上面的例子中,Visual Studio默认为Master Page中的每个ContentPlaceHolder创建一个Content控件。如果不打算提供定制内容,而是继承或使用Master Page提供的默认内容,只需在内容页面中删除Content控件,内容页面就会自动使用默认内容。
如前面介绍ContentPlaceHolder控件的名称时所述,所使用的ID值会显示在设计器中,允许开发人员确定他们在修改哪个区域。它们的默认名称是Content1、Content2和Content3。当然,这没有什么用,最好把它们改为有意义的名称,例如TopNav、LeftNav和MainContent。何时修改这些值是比较灵活的;如果在开始创建内容页面之前,没有在Master Page中修改ContentPlaceHolder的名称,就必须更新所有的内容页面。ID值可以随时修改,且不会影响Master Page或其他内容页面。
获得内容页面基本上有4种方式。首先,可以添加一个新的Web窗体,在显示Add New Webform对话框时,要求指定页面是否使用Master Page。如果选中了Master Page,下一个显示的对话框就允许选择要引用的Master Page,并在Page标记的MasterPageFile特性上引用它,将它添加到页面上,如前面的示例代码所示。其次,在页面的OnPreInit事件处理程序中,可以在代码中设置Master Page。这种方式将在第16章中使用,第16章描述了如何在运行期间动态切换Master Page。再次,可以在web.config文件中建立Master Page,但其缺点是,它将迫使项目中的所有页面都变成内容页面。最后,总是有一种能完成所有任务的方式,即把已有的页面转换为内容页面。
建立好第一个内容页面后,就可以把相同的修改应用于添加到应用程序中的所有页面。如果喜欢自动方式,可参阅本章后面讨论的导出模板一节。
应用程序的外观和操作方式常常与其功能一样重要。Master Page解决了页面的布局问题,而主题给应用程序的外观和操作方式提供了灵活性。简单地说,它们提供了一种方式,可以从页面及其控件定义中删除样式。
使用主题可以定义所有的样式,包括GridView如何显示,以及Logon控件的外观等。主题由一个或多个样式表、skin文件和相关的图像组成。使用一个或多个CSS样式表的概念在主题中仍旧存在,现在还可以使用skin文件定义控件的样式属性。
skin文件可以包含控件的定义,但没有功能/操作属性,例如ProductID=5。这个控件定义类似于模板,在引用该主题的页面上使用这个控件时,该skin就会应用于控件。
主题是相对于项目的,它们存储在特定的ASP.NET文件夹App_Themes中,也可以存储在全局主题目录下。在App_Themes文件夹中,可以定义一个或多个主题文件夹。在OnPreInit事件处理程序或web.config文件中,可以使用Theme特性在Page标记上设置主题。
主题不是必须使用的,但使用它们将便于将样式应用于每个页面,而且肯定比在页面上在线包含样式好一些。在ASP.NET 1.1中,我们常常使用CSS文件,以便在每个页面上都包含一个CSS的引用。即使只使用CSS,通过主题基础体系来使用它,该CSS文件就会自动包含进来。
2.7.1 使用Skin还是CSS
何时应使用CSS,何时应使用skin文件?这个问题没有唯一正确的答案,但用skin文件代替表有一些优点。在使用CSS时,只能根据控件的显示版本定制样式。CSS构造无法理解一些控件的定制属性。而skin文件可以提取标记上指定的属性,在显示页面时,这些属性都会在页面的对应控件上进行设置。这解决了两个问题:第一,便于修改所有的GridView控件,而无需在使用该控件的地方添加指定的类名。在默认情况下,除非使用SkinID指定某个skin,否则GridView skin规范就会应用于页面上所有支持主题(默认设置)的GridView控件。第二,如果使用skin,在控件上设置什么属性就更清楚,也就更容易确定输出的结果。
2.7.2 建立Skin文件
开始建立skin文件时,一个技巧是利用Visual Studio 2005内置的Auto Format功能。在许多控件上,右击控件,选择Auto Format,就可以从几个样式中选择,再在控件上设置这些属性。然后,把它们复制到一个skin文件中,删除与操作相关的属性,就得到了一个skin文件。
2.7.3 给Skin文件命名
我们不仅仅能创建名为myskins.skin或其他名称的skin文件。快速定位skin的一种有效技术是给skin文件指定与使用该skin的控件名类似的名称。例如,GridView的skin文件是GridView.skin。
主题提供了一种强大的方式,允许进一步定制站点的外观和操作方式。主题与Master Page一起使用时,如果允许用户选择这两个选项并随时修改它们,就为用户提供了他们以往通常不具备的定制功能。
从1.x转换为2.0的提示:
Visual Web Developer 2005有一个自动向导,可以把原来的ASP.NET 1.1应用程序转换为ASP.NET 2.0。对于最简单的情形,这个向导的效果很好。但是,仍需要注意几个事项,以避免麻烦。ASP.NET 2.0有特别指定的目录,在迁移文件的过程中,会进行其他转换。为了使应用程序仍有条理,请参阅第12章中有关转换过程及各个版本之间的区别等内容。
在进行团队开发时,可能出现冲突的一个领域是在web.config文件中的设置。该文件中的许多项都很基本,所有的团队成员都使用它们,例如注册控件,或所使用的提供程序,甚至是HttpHandler或HttpModule。不同的团队成员有时可能有自己的web.config设置,而应用程序设置或连接字符串常常专用于某个团队成员。
2.8.1 简单方式
appSettings和connectionStrings部分一般是团队成员争论最激烈的部分。避免这个问题的最简单方式是利用web.config文件进行源代码控制,使其中的设置适合于大多数团队成员。需要特别设置的团队成员可以创建该文件的本地可写副本,来修改其中的设置。这种方式的最大缺陷是,用户第一次从源代码控制中获得文件的最新集合时,需要替换文件。一次无意的单击,就会丧失所有的定制功能。
2.8.2 外部文件方式
另一个常见的选项是把这些设置存储在一个外部文件中,用户把这个外部文件保存在本地。在web.config中使用适当的引用来包含这些文件(稍后讨论)。在Visual Studio 2005之前,这些文件可以存储在网站文件夹中。但如果在Visual Studio 2005中这么做,文件就会进入源代码控制,达不到让团队成员建立自己的设置的目的。为了避免这种情况,文件可以存储在父目录下。把一组范例文件存储在主控项目中,让团队成员复制它们,通常是一个好办法。在接纳新的团队成员或重新设置环境时,这种方式尤其有效。
在引用文件以包含它们时,appSettings部分的处理与connectionStrings部分略有不同。下面的示例演示了如何指定包含父目录下的文件localhost.config:
<appSettings file="..\localhost.config">
查看localhost.config文件的内容,会发现它与appSettings部分很类似,如下所示:
<appSettings>
<add key="DefaultDaysLate" value="7" />
</appSettings>
这里给文件选择localhost.config这个名称,但该名称可以是任意名称。
connectionStrings部分的包含略有不同。它不是使用file特性指定文件名,而是使用configSource特性,如下所示:
<connectionStrings configSource="..\localhostConnectionStrings.config"/>
查看localhostConnectionStrings.config的内容,如下所示:
<connectionStrings >
<add name="MyDB" connectionString="server=myServer;uid=myuser;
password=***;database=MyDB" />
</connectionStrings>
这里只介绍这两个方法,显然,管理项目设置有许多方式。这些方式各有优缺点。例如,让所有的团队成员共享相同的注册文件(这是默认的),可以确保所有的成员都得到最新设置。也就是说,一个成员修改了设置,但没有告诉其他人的情况是很常见的。这个问题也常常倒过来:每个成员都有自己的本地副本,但他们可能都不知道另一个团队成员修改了某个重要的设置。正如生活中的许多事情一样,必须确定哪种方式最适合于项目,并完成它们。
Visual Studio 2005中新增的一个功能是,导出模板,在以后创建新项目或已有项目中的新项时使用。使用这个向导可以创建模板,用于整个项目或项目中的某个项,例如新页面或新类。
本章进行的一些修改示例非常清楚地说明了这类自动向导可以提供什么帮助。例如,利用BaseUIPage和Master Page,可以创建一个继承自BaseUIPage的示例Web窗体,并引用Site.Master文件。接着,使用Visual Studio 2005中的File | Export Template菜单项,创建一个可重用的模板,团队的所有成员都使用它创建外观类似示例的新Web窗体页面。使用这个技术可以节省几个小时,同时确保页面的一致性。第13章将深入讨论模板的工作原理和用法。
在每个新项目的开头,都有一个机会从某个坚实的基础开始。本章的目的是阐述一些开始新项目时可以作为最佳实践方式来采用的技术。
理想情况下,开发团队要确定为项目做哪些工作,建立自己的最佳实践方式,并修改它们,使它们适应自己的环境。