<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_dax.net</title><subtitle type="text">Software on Dynamics AX, .NET and DDD ......</subtitle><id>http://feed.cnblogs.com/blog/u/67775/rss</id><updated>2012-05-06T00:36:06Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><generator>feed.cnblogs.com</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/67775/rss"/><entry><id>http://www.cnblogs.com/daxnet/archive/2012/04/16/2452660.html</id><title type="text">EntityFramework之领域驱动设计实践【后续篇】：基于EF 4.3.1 Code First的领域驱动设计实践案例</title><summary type="text">两年前我在博客中发布了《EntityFramework之领域驱动设计实践》系列文章，也得到了广大读者朋友的关注，在完成了系列文章的总结之后，也一直没有这部分内容的更新了。现在，Entity Framework的稳定版（就是那个Stable的版本，不是Entity Framework 5的beta版本）4.3.1已经逐步应用到各种.NET项目中，为了演示Entity Framework 4.3.1 ...</summary><published>2012-04-16T13:07:00Z</published><updated>2012-04-16T13:07:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/04/16/2452660.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/04/16/2452660.html"/><content type="html">&lt;p&gt;两年前我在博客中发布了《EntityFramework之领域驱动设计实践》系列文章，也得到了广大读者朋友的关注，在完成了系列文章的总结之后，也一直没有这部分内容的更新了。现在，Entity Framework的稳定版（就是那个Stable的版本，不是Entity Framework 5的beta版本）4.3.1已经逐步应用到各种.NET项目中，为了演示Entity Framework 4.3.1 Code First编程模式以及其它的一些.NET技术在领域驱动设计实践上的应用，我重新采用经典的分层架构（也就是类似Microsoft NLayerApp的区别于CQRS的架构）实现了一个案例程序：Byteart Retail。在这个案例中，仓储的实现不再采用NHibernate，而是使用的Entity Framework 4.3.1 Code First。虽然这个案例较为完整地从各方面展示了.NET技术在企业级程序中的应用，但Entity Framework确实是其亮点之一，因此，我将这篇介绍这个案例的文章也编排在原来的EF系列文章中。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;案例概述&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Byteart Retail以笔记本电脑在线销售业务为背景，展示了Microsoft.NET技术以及领域驱动设计理念在软件设计、架构与实践中&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201204/20120416210611300.png"&gt;&lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" align="right" src="http://images.cnblogs.com/cnblogs_com/daxnet/201204/201204162106144501.png" width="572" height="382"&gt;&lt;/a&gt;的应用。在Byteart Retail之前，开源社区也有一些领域驱动设计的实践案例，比如Microsoft Domain-Oriented NLayered Application Architecture、面向CQRS体系结构模式的Tiny Library CQRS等。与这些案例相比，Byteart Retail在领域驱动的实践指导方面有一定的相似性和可比性，但它更注重Microsoft.NET技术与领域驱动设计相结合。比如，Byteart Retail案例对基于Entity Framework 4.3.1版本中Code First模式的仓储实现进行了全方位的演示，这样的Entity Framework仓储设计，使得领域模型对象能够完全设计为POCO对象而不需要依赖任何其它技术框架，因此，在对仓储的选择和使用上，就能够做到“无缝替换”。另一方面，领域模型的设计也更为考究，实体、值对象的设计、聚合的划分等，都与系统业务紧密结合，相对于其它的演示案例更为成熟。从实现的业务逻辑上看，Byteart Retail大致实现了以下功能：&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;笔记本电脑商品的浏览  &lt;li&gt;客户账户注册和基本信息查询与修改  &lt;li&gt;笔记本电脑详细信息查询  &lt;li&gt;客户添加笔记本电脑商品到购物篮  &lt;li&gt;购物篮商品项目管理  &lt;li&gt;从购物篮创建销售订单  &lt;li&gt;销售订单的确认与查询  &lt;li&gt;销售订单明细查询 &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;span style="font-family: verdana"&gt;案例对以下.NET技术和开发技巧进行了演示：&lt;/span&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;span style="font-family: segoe ui"&gt;Microsoft Entity Framework 4.3.1 Code First &lt;/span&gt; &lt;li&gt;&lt;span style="font-family: segoe ui"&gt;ASP.NET MVC 3 &lt;/span&gt; &lt;li&gt;&lt;span style="font-family: segoe ui"&gt;WCF &lt;/span&gt; &lt;li&gt;&lt;span style="font-family: segoe ui"&gt;Microsoft Patterns &amp;amp; Practices Unity Application Block &lt;/span&gt; &lt;li&gt;&lt;span style="font-family: segoe ui"&gt;使用AutoMapper实现DTO与领域对象映射 &lt;/span&gt; &lt;li&gt;&lt;span style="font-family: segoe ui"&gt;T4自动化代码生成&lt;/span&gt; &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;strong&gt;案例下载&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;请&lt;a href="http://files.cnblogs.com/daxnet/ByteartRetail.rar" target="_blank"&gt;&lt;strong&gt;【单击此处】&lt;/strong&gt;&lt;/a&gt;下载本案例的所有源代码和Visual Studio 2010的解决方案文件。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;系统需求&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;开发环境：Visual Studio 2010 Professional/Ultimate with SP1，ASP.NET MVC3。其它的程序集引用都在压缩包的packages目录下，因此读者无需上网下载安装其它组件。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;安装部署&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;数据库&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Byteart Retail采用Microsoft SQL Server 2008作为后台数据库。首先，修改ByteartRetail.Services项目下的web.config文件，对数据库链接字符串进行配置：&lt;/p&gt; &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201204/201204162106156269.png"&gt;&lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201204/201204162106161875.png" width="890" height="116"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;然后，按照下面“&lt;strong&gt;程序启动&lt;/strong&gt;”部分的描述，启动ByteartRetail.Web项目，此时Entity Framework会根据上面的连接字符串创建一个名为ByteartRetail的数据库。&lt;/p&gt; &lt;p&gt;最后，打开SQL Server Management Studio，执行压缩包中SQL目录下的ByteartRetailData.sql文件即可将所需的测试数据导入ByteartRetail数据库中。&lt;/p&gt; &lt;p&gt;说明：这种数据库的部署和初始化方式虽然能够规避“Model compatibility cannot be checked because the database does not contain model metadata.”的错误，但仍然不是一个很理想的部署方式。在这里我们暂时采用这种方式让案例先运行起来，以后我会找出一个更合理的办法并对这部分内容进行更新。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;span style="font-family: verdana"&gt;程序启动&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;使用Visual Studio打开解决方案并完成编译，然后在ByteartRetail.Services项目中任选一个.svc文件点击右键，并选择View in Browser选项以启动WCF Service；之后，直接运行ByteartRetail.Web项目，即可出现主界面。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;设计概要&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在此我先将部分设计的类图贴出，以方便读者朋友在查看源代码的过程中参阅。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;领域模型&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="http://pic002.cnblogs.com/images/2012/119825/2012041621123476.jpg" target="_blank"&gt;&lt;img alt="" src="http://pic002.cnblogs.com/images/2012/119825/2012041621123476.jpg" width="900"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;基于Entity Framework的仓储设计（省略属性与方法）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="http://pic002.cnblogs.com/images/2012/119825/2012041621134723.jpg" target="_blank"&gt;&lt;img alt="" src="http://pic002.cnblogs.com/images/2012/119825/2012041621134723.jpg" width="900"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;规约设计&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="http://pic002.cnblogs.com/images/2012/119825/2012041621141442.jpg" target="_blank"&gt;&lt;img alt="" src="http://pic002.cnblogs.com/images/2012/119825/2012041621141442.jpg" width="900"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;企业应用架构模式参考&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;本案例大致涉及到了以下企业级应用架构模式，也一并列举于此，供读者朋友们参考，也可以作为学习《Patterns of Enterprise Application Architecture》、《Core J2EE Patterns》等书籍的参考。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/domainModel.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Domain Model&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/unitOfWork.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Unit Of Work&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/repository.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Repository&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/dataTransferObject.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Data Transfer Object&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/clientSessionState.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Client Session State&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/layerSupertype.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Layer Supertype&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/separatedInterface.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Separated Interface&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/valueObject.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Value Object&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/BusinessDelegate.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Business Delegate&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Service Locator&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;li&gt;&lt;strong&gt;&lt;a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObjectAssembler.html" target="_blank"&gt;&lt;span style="font-family: segoe ui; font-size: small"&gt;&lt;font size="2"&gt;Transfer Object Assembler&lt;/font&gt;&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt; &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;热烈欢迎爱好Microsoft.NET技术以及领域驱动设计的读者朋友对本案例进行深入讨论。有疑问或建议请直接留言回复。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2452660.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/04/16/2452660.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2012/03/23/2413260.html</id><title type="text">ASP.NET MVC实用技术：自定义AuthorizeAttribute与ActionLink的隐藏</title><summary type="text">在有些情况下，我们希望界面上的Action Link不仅仅是限制未授权用户的进一步访问，而是对于这些用户直接隐藏。比如，以普通用户登录时，只能在页面上看到一些常规的链接，而以管理员身份登录时，除了能看到这些常规链接外，还能够看到网站管理的链接。本文将介绍如何使用自定义的AuthorizeAttribute来实现这样的功能。为了方便介绍，在这里不打算使用那些复杂的权限管理子系统或者权限验证机制，我们就做一个非常简单的假设：如果输入的用户名是“daxnet”，则表示这个账户是一个管理员账户，否则，它就是一个普通用户账户。在实际应用过程中，读者朋友可以采用自己的一套权限验证逻辑来判断某个账户是否为管</summary><published>2012-03-23T02:39:00Z</published><updated>2012-03-23T02:39:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/03/23/2413260.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/03/23/2413260.html"/><content type="html">&lt;p&gt;在有些情况下，我们希望界面上的Action Link不仅仅是限制未授权用户的进一步访问，而是对于这些用户直接隐藏。比如，以普通用户登录时，只能在页面上看到一些常规的链接，而以管理员身份登录时，除了能看到这些常规链接外，还能够看到网站管理的链接。本文将介绍如何使用自定义的AuthorizeAttribute来实现这样的功能。&lt;/p&gt;&lt;p&gt;为了方便介绍，在这里不打算使用那些复杂的权限管理子系统或者权限验证机制，我们就做一个非常简单的假设：如果输入的用户名是&amp;ldquo;daxnet&amp;rdquo;，则表示这个账户是一个管理员账户，否则，它就是一个普通用户账户。在实际应用过程中，读者朋友可以采用自己的一套权限验证逻辑来判断某个账户是否为管理员。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;WCF Service&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在《&lt;strong&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/03/16/2400418.html" target="_blank"&gt;ASP.NET MVC实用技术：开篇&lt;/a&gt;&lt;/strong&gt;》一文，我已经介绍过了三种不同的ASP.NET MVC应用模式，其中第二种&amp;ldquo;Data Transfer Object as View Model&amp;rdquo;是在企业级应用中最为常见的一种方式，本文（及以后的系列文章）都会使用这种应用模式进行介绍。所以，在这里我们也还是需要建立一个WCF Service，用来向客户端返回登录用户权限认证的结果。&lt;/p&gt;&lt;p&gt;新建一个WCF Service，在其中定义一个enum类型，用来表示登录用户的账户类型。在这里我们只讨论两种类型：RegularUser，表示普通用户账户；SiteAdmin，表示网站管理员账户。这个enum类型定义如下：&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6b7bc3fb-fa65-4a6b-a55a-28a1e1d74122" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;/// &amp;lt;summary&amp;gt;&lt;br/&gt;/// Represents the type of the account.&lt;br/&gt;/// &amp;lt;/summary&amp;gt;&lt;br/&gt;[Flags]&lt;br/&gt;public enum AccountType&lt;br/&gt;{&lt;br/&gt;    /// &amp;lt;summary&amp;gt;&lt;br/&gt;    /// Indicates that the account is a regular user.&lt;br/&gt;    /// &amp;lt;/summary&amp;gt;&lt;br/&gt;    [EnumMember]&lt;br/&gt;    RegularUser = 1,&lt;br/&gt;    /// &amp;lt;summary&amp;gt;&lt;br/&gt;    /// Indicates that the account is the site administrator.&lt;br/&gt;    /// &amp;lt;/summary&amp;gt;&lt;br/&gt;    [EnumMember]&lt;br/&gt;    SiteAdmin = 2&lt;br/&gt;}&lt;/div&gt;&lt;p&gt;使用FlagsAttribute来标记这个AccountType枚举，是为了今后能够更方便地处理更多类型的账户，事实上在我们这个案例中，并没有太大的实际意义。&lt;/p&gt;&lt;p&gt;然后，新建一个Service Contract，为了简化案例，这个Service Contract只包含一个操作，就是根据传入的用户账户名称，返回AccountType。&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d7fad05c-93a3-4d03-b238-d90aee674fa5" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;[ServiceContract(Namespace="http://aspnetmvcpractice.com")]&lt;br/&gt;public interface IAccountService&lt;br/&gt;{&lt;br/&gt;    [OperationContract]&lt;br/&gt;    AccountType GetAccountType(string userName);&lt;br/&gt;}&lt;/div&gt;&lt;p&gt;之后在WCF Service中实现这个接口，根据我们上面的约定，当用户名为&amp;ldquo;daxnet&amp;rdquo;的时候，就返回SiteAdmin，否则就返回RegularUser，因此这个实现类还是非常简单的：&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:dc1cbc89-02f5-4432-ab02-f1461ee34f96" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;public class AccountService : IAccountService&lt;br/&gt;{&lt;br/&gt;    #region IAccountService Members&lt;br/&gt;    public AccountType GetAccountType(string userName)&lt;br/&gt;    {&lt;br/&gt;        if (userName == "daxnet")&lt;br/&gt;            return AccountType.SiteAdmin;&lt;br/&gt;        else &lt;br/&gt;            return AccountType.RegularUser;&lt;br/&gt;    }&lt;br/&gt;    #endregion&lt;br/&gt;}&lt;/div&gt;&lt;p&gt;至此，我们完成了WCF Service部分的开发，接下来，需要在ASP.NET MVC中使用这个WCF Service来完成用户的验证操作。在通常情况下，我们会在ASP.NET MVC的应用程序上直接添加WCF Service的引用，这样做其实也没有什么太大的问题，不过我还是比较习惯另外新建一个Class Library，然后将WCF Service Reference添加到这个Class Library上，这样做的好处是，可以把所有与ASP.NET MVC扩展相关的内容都集中起来，而且这种扩展相关的类型和方法都有可能需要用到WCF Service提供的服务，这样也不至于将ASP.NET MVC应用程序的结构弄得很乱。在这个案例中，我们新建一个名为WebExtensions的Class Library，在这个Library中使用刚刚创建好的WCF Service来实现我们的自定义授权特性。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Web Extensions&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;CustomAuthorizeAttribute&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在新建的这个Class Library中直接添加WCF Service Reference，这将在这个Library中产生一系列的代理类型，以及一个app.config文件。不要去关注这个app.config文件，因为它在这个Class Library中并不起什么作用；但是也不要去删除这个文件，因为后面我们还是需要用到它里面的内容的。&lt;/p&gt;&lt;p&gt;在Class Library中，新建一个CustomAuthorizeAttribute类，使这个类继承于AuthorizeAttribute。我们会在后面将这个Attribute用在action上，以限制未授权用户对页面的访问。在这个类中，重载AuthorizeCore方法，它的处理逻辑如下：首先判断当前账户是否被认证，如果没有，则返回false；然后调用WCF Service来获取当前账户的类型，并跟给定的类型进行比较，如果类型相同，则返回true，否则返回false。假设这个给定的账户类型是通过CustomAuthorizeAttribute类的构造函数传入的，那么，当我们在某个action上应用&lt;em&gt;[CustomAuthorizeAttribute(AccountType.SiteAdmin)]&lt;/em&gt;这个特性的时候，只要访问这个action的用户账户不是SiteAdmin，程序就会自动跳转到登录页面，请求用户以网站管理员的身份登录。CustomAuthorizeAttribute类的代码如下：&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e6a00243-2cd6-4fe0-a2f7-73666ad72f4f" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;public class CustomAuthorizeAttribute : AuthorizeAttribute&lt;br/&gt;{&lt;br/&gt;    private readonly AccountType requiredType;&lt;br/&gt;&lt;br/&gt;    public CustomAuthorizeAttribute(AccountType comparedWithType)&lt;br/&gt;    {&lt;br/&gt;        this.requiredType = comparedWithType;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    internal bool PerformAuthorizeCore(System.Web.HttpContextBase httpContext) { return this.AuthorizeCore(httpContext); }&lt;br/&gt;&lt;br/&gt;    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)&lt;br/&gt;    {&lt;br/&gt;        if (httpContext == null)&lt;br/&gt;            throw new ArgumentNullException("httpContext");&lt;br/&gt;&lt;br/&gt;        if (!httpContext.User.Identity.IsAuthenticated)&lt;br/&gt;            return false;&lt;br/&gt;&lt;br/&gt;        if (this.requiredType == (AccountType.SiteAdmin | AccountType.RegularUser))&lt;br/&gt;            return true;&lt;br/&gt;&lt;br/&gt;        using (AccountServiceClient client = new AccountServiceClient())&lt;br/&gt;        {&lt;br/&gt;            var calculatedAccountType = client.GetAccountType(httpContext.User.Identity.Name);&lt;br/&gt;&lt;br/&gt;            switch(this.requiredType)&lt;br/&gt;            {&lt;br/&gt;                case AccountType.RegularUser:&lt;br/&gt;                    if ((calculatedAccountType &amp;amp; AccountType.RegularUser) == AccountType.RegularUser)&lt;br/&gt;                        return true;&lt;br/&gt;                    else&lt;br/&gt;                        return false;&lt;br/&gt;                case AccountType.SiteAdmin:&lt;br/&gt;                    if ((calculatedAccountType &amp;amp; AccountType.SiteAdmin) == AccountType.SiteAdmin)&lt;br/&gt;                        return true;&lt;br/&gt;                    else&lt;br/&gt;                        return false;&lt;br/&gt;                default:&lt;br/&gt;                    return base.AuthorizeCore(httpContext);&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;/div&gt;&lt;p&gt;在这个类中有一个internal的方法：PerformAuthorizeCore，它的作用就是向程序集的其它方法暴露AuthorizeCore的执行逻辑，以避免相同的逻辑需要在程序集内部的其它类型中重复实现。这个PerformAuthorizeCore的方法会在自定义的HtmlHelper扩展方法中使用，目的就是为了能够对未授权的账户隐藏Action Link。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;HtmlHelper Extension&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在我们来扩展HtmlHelper类，使得其中的ActionLink方法能够支持对未授权账户的隐藏。同样也是在当前这个Class Library中，新建一个静态类，命名为MvcExtensions，然后使用下面的代码实现这个类：&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:f2e9012f-959f-4818-878e-d696a0360252" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;public static class MvcExtensions&lt;br/&gt;{&lt;br/&gt;    private static bool Visible(HtmlHelper helper, AccountType accountType)&lt;br/&gt;    {&lt;br/&gt;        return new CustomAuthorizeAttribute(accountType).PerformAuthorizeCore(helper.ViewContext.HttpContext);&lt;br/&gt;    }&lt;br/&gt;    /// &amp;lt;summary&amp;gt;&lt;br/&gt;    /// Returns an anchor element (a element) that contains the virtual path of the specified action.&lt;br/&gt;    /// &amp;lt;/summary&amp;gt;&lt;br/&gt;    /// &amp;lt;param name="htmlHelper"&amp;gt;The HTML helper instance that this method extends.&amp;lt;/param&amp;gt;&lt;br/&gt;    /// &amp;lt;param name="linkText"&amp;gt;The inner text of the anchor element.&amp;lt;/param&amp;gt;&lt;br/&gt;    /// &amp;lt;param name="actionName"&amp;gt;The name of the action.&amp;lt;/param&amp;gt;&lt;br/&gt;    /// &amp;lt;param name="controllerName"&amp;gt;The name of the controller.&amp;lt;/param&amp;gt;&lt;br/&gt;    /// &amp;lt;param name="accountTypeRequired"&amp;gt;The required account type.&amp;lt;/param&amp;gt;&lt;br/&gt;    /// &amp;lt;returns&amp;gt;The anchor element (a element) that contains the virtual path of the specified action.&amp;lt;/returns&amp;gt;&lt;br/&gt;    public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, &lt;br/&gt;        string linkText, &lt;br/&gt;        string actionName, &lt;br/&gt;        string controllerName, &lt;br/&gt;        AccountType accountTypeRequired)&lt;br/&gt;    {&lt;br/&gt;        MvcHtmlString link = MvcHtmlString.Empty;&lt;br/&gt;        if (Visible(htmlHelper, accountTypeRequired))&lt;br/&gt;            link = htmlHelper.ActionLink(linkText, actionName, controllerName);&lt;br/&gt;        return link;&lt;br/&gt;    }&lt;br/&gt;}&lt;/div&gt;&lt;p&gt;这个ActionLink方法首先将link设置为MvcHtmlString.Empty，表示为一个空的string，然后调用私用静态方法Visible，来判断当前用户是否应该看到这个ActionLink，如果Visible返回的是true，则直接调用HtmlHelper中已有的ActionLink重载方法，否则直接返回MvcHtmlString.Empty。在Visible方法中，我们可以看到，所执行的逻辑正是CustomAuthorizeAttribute中的AuthorizeCore方法。&lt;/p&gt;&lt;p&gt;接下来要做的，就是在ASP.NET MVC应用程序中使用这些扩展方法和自定义特性。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;ASP.NET MVC应用程序&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在ASP.NET MVC应用程序上添加对上述Class Library的引用，然后我们打开Views\Shared\_Layout.cshtml文件，在这个Razor View中添加对所需命名空间的引用：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037097314.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037128620.png" alt="image" width="534" height="34" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;然后，根据需要，我们向主菜单中添加两个ActionLink：Regular Users Only和Site Admins Only，前者仅允许普通用户访问，后者仅允许站点管理员访问。在此所使用的ActionLink就是在上文中我们自定义的那个重载：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037172062.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037248794.png" alt="image" width="782" height="130" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;接下来在HomeController中定义两个action：RegularUserVisible和SiteAdminVisible，并将CustomAuthorizeAttribute应用在这两个action上。事实上这个步骤与隐藏Action Link并没有太大关系，只是确保用户无法通过在浏览器中输入URL而直接访问到这两个页面。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037288398.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/20120323103732967.png" alt="image" width="387" height="183" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;最后别忘了把Class Library下app.config中有关system.serviceModel的配置复制到ASP.NET MVC应用程序的web.config中。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;运行程序&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在让我们来启动程序，看看会产生什么效果。首先启动WCF Service，然后直接运行ASP.NET MVC应用程序，得到如下界面：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037384767.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037489840.png" alt="image" width="723" height="446" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;现在点击&amp;ldquo;Log On&amp;rdquo;链接，以daxnet账户登录，我们得到了如下的效果，可以看到页面上显示了&amp;ldquo;Site Admins Only&amp;rdquo;的链接选项：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231037556015.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231038034384.png" alt="image" width="723" height="446" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;退出登录，再以&amp;ldquo;acqy&amp;rdquo;账户登录，我们又得到了如下效果，看到页面上显示了&amp;ldquo;Regular Users Only&amp;rdquo;的选项：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231038105576.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231038173704.png" alt="image" width="723" height="446" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;本文案例源代码下载&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;下载链接&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;请&lt;strong&gt;&lt;a href="http://files.cnblogs.com/daxnet/AspNetMVCPractice.rar" target="_blank"&gt;单击此处&lt;/a&gt;&lt;/strong&gt;下载本文案例源代码&lt;/p&gt;&lt;p&gt;&lt;strong&gt;有关数据库配置&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;本文使用的是SQL Server Enterprise Edition作为ASP.NET MVC的后台数据库，如果你打算选用SQL Server Express作为数据库，请修改本文案例中web.config里的连接字符串，并使用《&lt;strong&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/03/16/2400418.html" target="_blank"&gt;ASP.NET MVC实用技术：开篇&lt;/a&gt;&lt;/strong&gt;》一文中所介绍的方法重建你的数据库结构。根据本文案例需要，你需要在ASP.NET MVC应用程序启动以后，新建两个用户账户：daxnet以及另一个任意名称的账户。当你正确地配置好了ASP.NET MVC的数据库以后，你可以在Solution Explorer中单击ASP.NET Configuration按钮来配置你的ASP.NET MVC站点，以添加所需的用户账户：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231038267796.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203231038367819.png" alt="image" width="843" height="637" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2413260.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/03/23/2413260.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2012/03/16/2400418.html</id><title type="text">ASP.NET MVC实用技术：开篇</title><summary type="text">ASP.NET MVC是一款基于ASP.NET的MVC模式的实现框架。通过使用ASP.NET MVC框架，开发人员能够非常方便地完成应用程序前台页面的开发工作。优秀的前台展示，对于大型企业级应用而言，是非常重要的组成部分，而ASP.NET MVC则为实现这一重要组成部分提供了技术和平台支持。目前，ASP.NET MVC已经到了4.0 Beta的版本，但我仍然打算以ASP.NET MVC 3为基础，通过几篇文章的篇幅，介绍一些ASP.NET MVC的实用技术，比如：如何实现自定义的认证机制、如何实现多主题效果的支持等。这些内容在网上也或多或少地提供了一些解决方案，但有些也不算太完整，有些又写的很</summary><published>2012-03-16T07:47:00Z</published><updated>2012-03-16T07:47:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/03/16/2400418.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/03/16/2400418.html"/><content type="html">&lt;p&gt;ASP.NET MVC是一款基于ASP.NET的MVC模式的实现框架。通过使用ASP.NET MVC框架，开发人员能够非常方便地完成应用程序前台页面的开发工作。优秀的前台展示，对于大型企业级应用而言，是非常重要的组成部分，而ASP.NET MVC则为实现这一重要组成部分提供了技术和平台支持。目前，ASP.NET MVC已经到了4.0 Beta的版本，但我仍然打算以ASP.NET MVC 3为基础，通过几篇文章的篇幅，介绍一些ASP.NET MVC的实用技术，比如：如何实现自定义的认证机制、如何实现多主题效果的支持等。这些内容在网上也或多或少地提供了一些解决方案，但有些也不算太完整，有些又写的很含糊。因此，这几篇连载的文章会对每个实用技术进行完整的介绍，并在每篇文章结尾部分给出案例源代码及其使用方式，以供读者朋友们参考。&lt;/p&gt;&lt;p&gt;首先说明一下，这几篇文章不会对ASP.NET MVC中的概念进行描述，也不会太多地去深究某些技术实现细节，这些文章应该可以看成是几篇连载的&amp;ldquo;菜谱（Cook Book）&amp;rdquo;，读者朋友通过阅读并实践文中所述的内容，就能够获得所需的解决方案。虽然不能从根本上&amp;ldquo;解惑&amp;rdquo;，但也能在实践上为读者指明方向。此外，本人并不擅长.NET的前台技术，所以这几篇文章中提供的解决方案也不一定是最好的，如果读者朋友们有更好的想法或者思路，欢迎大家踊跃留言。&lt;/p&gt;&lt;p&gt;在介绍这些实用技术以前，首先让我们了解一下在企业级应用程序架构中，ASP.NET MVC的应用模式，这对我们今后介绍各种技术有一定的帮助，也算是能够让我们在架构的这个问题上达成一致。之后，我们再看看标准的ASP.NET MVC应用程序的数据库问题。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;ASP.NET MVC的应用模式&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在讨论ASP.NET MVC的应用模式时，对于MVC中的View和Controller的理解，我想应该不会存在什么太大的异议：View主要负责管理页面的显示布局和效果，并将Model中的数据展现在页面上；Controller则负责Model数据的获取与组织，并将数据以某种形式绑定到View上，或者从View上获取数据并对其进行必要的操作。现在需要讨论的是MVC中的这个&amp;ldquo;M&amp;rdquo;，从概念上讲，它应该是指View Model，因为它是View的数据源，但事实上在不同的应用程序中，它可能会被赋予不同的含义，于是也就产生了三种常见的ASP.NET MVC应用模式。比如，在实际项目中，我们有可能直接将Domain Model用作View Model，以便Controller能够直接操作Domain Model以完成业务逻辑处理；我们有可能把用于与分布式服务进行通信的数据传输对象（&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/dataTransferObject.html" target="_blank"&gt;Data Transfer Objects&lt;/a&gt;&lt;/strong&gt;）用作View Model，以便Controller能够通过分布式服务来获取或者提交数据，进而在分布式服务中对这些数据进行处理；我们还有可能在数据传输对象（Data Transfer Objects）与View Model之间再做一层映射，以解耦View Model与数据传输对象，因为在某些情况下View Model与数据传输对象仍然存在&amp;ldquo;阻抗失衡。针对这三种不同的应用模式，应用程序也会有着架构设计和部署上的差异。下面我们对这三种不同的应用模式进行详细讨论。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Domain Model as View Model&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;对于中小型应用程序而言，这种应用模式比较常见，在这种模式中，Domain Model被用作View Model，直接成为View的数据源，被显示在页面上。这有点像&amp;ldquo;数据源架构模式（&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/" target="_blank"&gt;Data Source Architectural Patterns&lt;/a&gt;&lt;/strong&gt;）&amp;rdquo;中的&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/activeRecord.html" target="_blank"&gt;Active Record&lt;/a&gt;&lt;/strong&gt;模式。在Active Record模式中，对象用来表示数据库中的一张表或者一个视图，同时它又包含一些业务逻辑，于是，它混合了领域对象和数据访问对象的职责。在ASP.NET MVC中，如果采用Domain Model as View Model的模式，那么情况也就非常相似，因为这里的Model同时包含了领域模型和视图模型的职责。当然，除非是特殊情况，比如应用程序本身规模比较小，否则Domain Model as View Model并非一个合理的应用模式，它违背了软件设计的SRP（Single Responsibility Principle）的原则。&lt;/p&gt;&lt;p&gt;Domain Model as View Model的实现比较简单，只需要将Domain Model写在ASP.NET MVC Web应用程序中就可以了。为了能够参与View Model的职责，我们通常会在Domain Model上加入一些验证特性，比如使用RequiredAttribute来标注当前的属性是一个必填字段，以便基于javascript的客户端验证机制能够在必要的时候将错误信息提示给用户。在数据存储部分，通常可以选用ORM，将Domain Model直接持久化到数据库；也可以使用数据访问对象（Data Access Objects），配合使用ADO.NET来实现数据访问。常见的数据访问模式可以参考：&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/tableDataGateway.html" target="_blank"&gt;Table Data Gateway&lt;/a&gt;&lt;/strong&gt;、&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/rowDataGateway.html" target="_blank"&gt;Row Data Gateway&lt;/a&gt;&lt;/strong&gt;、&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/activeRecord.html" target="_blank"&gt;Active Record&lt;/a&gt;&lt;/strong&gt;以及&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/dataMapper.html" target="_blank"&gt;Data Mapper&lt;/a&gt;&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;从物理部署的角度看，首先需要一台数据库服务器作为数据持久化基础架构，然后是一台IIS Web服务器，用来运行ASP.NET MVC Web应用程序，客户端浏览器通过访问这个IIS站点来完成系统交互。这是一个典型的三层结构，我用下图简要地描述了Domain Model as View Model的应用模式（说明：虽然将物理机器的部署和应用程序的逻辑结构画在一起并不是那么自然，毕竟逻辑视图和部署视图是两个完全不同的表述，但为了让读者能够看到在不同的物理部署节点上，运行着哪些逻辑组件，我还是勉强将它们画在了一张图中，因此如果在该图中如果有什么不合理的地方，还请读者指正）：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161544559074.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161545106410.png" alt="image" width="744" height="499" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Data Transfer Object as View Model&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这种应用模式往往可以跟面向领域的架构模式相结合，提供比较合理的企业级应用程序解决方案。在这种应用模式中，领域模型不充当View Model的角色，它甚至与View Model毫无关系，只是作为一个业务组件运行在另一台物理服务器上。根据领域驱动设计（DDD）的实践指导，应用层通过仓储来管理领域对象的生命周期、使用这些领域对象和领域服务来完成所需的业务逻辑、并在领域对象与数据传输对象之间进行映射操作。当我们使用WCF作为服务供应者（Service Provider）时，通常会将WCF的Data Contract作为数据传输对象（Data Transfer Object），因此，应用层会完成Data Contract和领域对象之间的转换，然后通过WCF服务向外部提供服务接口。&lt;/p&gt;&lt;p&gt;作为向最终用户提供界面服务的ASP.NET MVC Web应用程序，它会通过服务引用（Service Reference）来使用由WCF公布的服务。在这个过程中，.NET会在ASP.NET MVC Web应用程序中创建一系列的WCF客户端代理类型，其中包括Client Channel、Data Contracts等。于是，MVC中的Controller会调用WCF服务以获得Data Contract对象，之后将这些Data Contract对象作为View的数据源绑定到View上。因此，整个过程事实上是使用数据传输对象作为View Model。类似地，我也针对这种应用模式画出了整个应用程序的结构细节，读者可以与上图进行比较：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161545318625.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161545489524.png" alt="image" width="865" height="387" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在这种应用模式中，ASP.NET MVC不再参与任何业务逻辑，它仅仅是为用户界面提供服务，这样就完全解耦了用户界面层与业务层之间的关联关系。也就是说，我们可以不选用ASP.NET MVC技术，而选择其它的展现层技术（比如WPF或者Windows Forms）来设计和开发应用程序的展现部分。而在业务逻辑部分，基本上可以以DDD为指导进行设计与开发，这样做不仅可以将大部分分析与设计的精力放在领域模型上，而且还能够很自然地（或者说更合理地）应用一些企业架构模式。&lt;/p&gt;&lt;p&gt;这里其实会有一个技术问题，就是在ASP.NET MVC部分，Data Contract代理类型都是在Service Reference的过程中自动产生的，如果将这些Data Contract，也就是数据传输对象，直接作为View Model绑定到View上，那么似乎那些用于客户端验证的Attributes就无法添加到这个View Model上。事实上我觉得MS应该已经意识到了这种应用的可行性，所以已经有现成的解决方案了。这个解决方案得益于两件事情：C#的partial class，以及MetadataTypeAttribute。&lt;/p&gt;&lt;p&gt;比如，在自动生成的代理类中，有如下这个Data Contract：&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:43c67e59-2dc6-4fdb-ae6b-6fed9b232a15" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;[DataContract]&lt;br/&gt;public partial class Customer {&lt;br/&gt;  [DataMember]&lt;br/&gt;public string Name { get; set; }&lt;br/&gt;}&lt;/div&gt;&lt;p&gt;那么我们可以使用下面的方法为Customer的Name属性添加验证规则：首先，针对Customer定义一个Metadata类，使其包含与Customer相同名称的属性（当然，只需要包含那些你需要添加验证特性的属性即可），然后在这个Metadata类的属性上，添加验证特性，如下：&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bebad981-3631-41ec-9c61-14e543792769" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;public class CustomerMetadata {&lt;br/&gt;[Required(ErrorMessage="Name must be specified.")]&lt;br/&gt;public string Name { get; set; }&lt;br/&gt;}&lt;/div&gt;&lt;p&gt;之后，使用partial关键字重定义Customer类，并在这个partial类上使用MetadataTypeAttribute，将CustomerMetadata&amp;ldquo;绑定&amp;rdquo;到Customer类即可：&lt;/p&gt;&lt;div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:5d92ae00-c2dd-4be7-8fe7-6cf1142e78d9" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;"&gt;[System.ComponentModel.DataAnnotations.MetadataTypeAttribute(typeof(CustomerMetadata))]&lt;br/&gt;partial class Customer { }&lt;/div&gt;&lt;p&gt;Data Transfer Object as View Model的应用模式同时也向我们展示了.NET技术在领域驱动设计实践中的应用。接下来的几篇文章，都会以这种应用模式为背景，对ASP.NET MVC的一些实用技术进行介绍。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Mapped DTO as View Model&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Mapped DTO，也就是根据View的需求，通过映射技术，将数据传输对象组装成View所需的View Model。其实与Data Transfer Object as View Model相比，它只是在View Model和Data Transfer Object之间又增加了一层映射处理，应用程序的其它部分与DTO as View Model完全相同。这种映射处理可以使用一些辅助的第三方框架（比如&lt;strong&gt;&lt;a href="https://github.com/AutoMapper/AutoMapper" target="_blank"&gt;AutoMapper&lt;/a&gt;&lt;/strong&gt;等）完成。在这里，我也就不打算对这种应用模式进行详细介绍了。&lt;/p&gt;&lt;p&gt;ASP.NET MVC应用模式大致也就是上述几种，这些讨论都是以MVC中的&amp;ldquo;M&amp;rdquo;为中心展开的。为了能够开始接下来的实用技术介绍，在此还需要简单地讨论一下ASP.NET MVC应用程序的数据库部分。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;ASP.NET MVC应用程序的数据库&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;每当我们新建一个ASP.NET MVC应用程序的时候，Visual Studio模板都会帮我们把整个项目都建好，并会默认地使用SQL Server Express作为这个应用程序的后台数据库。如果你的机器上装有SQL Server Express，那么在你第一次运行ASP.NET MVC应用程序后，你应该就会在App_Data下找到一个aspnetdb.mdf的数据库文件，所有与ASP.NET MVC应用相关的数据表都会保存在这个数据库文件中。&lt;/p&gt;&lt;p&gt;当然，在大多数情况下，你有可能不会将SQL Server Express用作上线系统的数据库，或许你更倾向于使用SQL Server 2008 Enterprise Edition作为你的后台数据库，那么你就需要定制ASP.NET MVC应用程序。首先，打开Visual Studio 2010 Command Prompt，在命令行输入aspnet_regsql然后回车，这将打开ASP.NET SQL Server Setup Wizard：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161545559637.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161546023088.png" alt="image" width="592" height="458" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在这个界面上单击Next按钮，在Select a Setup Option选项卡中，选择Configure SQL Server for application services选项，然后单击Next：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/20120316154606466.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161546141767.png" alt="image" width="592" height="458" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在Select Server and Database选项卡中，选择你要使用的数据库名称，然后单击Next：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161546208665.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161546247646.png" alt="image" width="592" height="458" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在Confirm Your Settings选项卡中直接单击Next按钮，这样，向导就会将所需的数据库对象同步到你所选择的数据库中，然后在完成页面单击Finish按钮：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161546305689.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/20120316154632475.png" alt="image" width="592" height="458" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;回到SQL Server管理控制台，展开我们刚刚创建的数据库，我们可以看到，所有所需的数据库对象都已经创建好了：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161546356000.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203161546402407.png" alt="image" width="310" height="383" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;现在，回到ASP.NET MVC应用程序，在web.config中把数据库连接字符串直接改掉就可以了。&lt;/p&gt;&lt;p&gt;如果你的应用程序采用了上述&amp;ldquo;Data Transfer Object as View Model&amp;rdquo;的模式，那么你的后台数据库又将是另外一个景象：它或许连这些ASP.NET MVC所需的数据库对象都没有，于是你只能够自定义Membership Provider，然后在ASP.NET MVC中使用这个自定义的Membership Provider。在&lt;strong&gt;&lt;a href="http://tlibcqrs.codeplex.com" target="_blank"&gt;Tiny Library CQRS&lt;/a&gt;&lt;/strong&gt;案例中已经采用了自定义的Membership Provider，有兴趣的读者可以先去了解一下，不过这部分内容也会在后续的篇章中详细介绍。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;本文为《ASP.NET MVC实用技术》系列文章的开篇，首先对ASP.NET MVC的应用模式进行了简单介绍，然后浏览了一下与ASP.NET MVC程序数据库相关的一些内容。在接下来的文章中，我会以ASP.NET MVC 3为基础，详细介绍一些实用技术，并会在每篇文章末尾给出完整源代码案例，供读者参考使用。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;扩展阅读&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;有兴趣的读者可以参考以下资料，以对ASP.NET MVC的应用模式作进一步了解：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://weblogs.asp.net/shijuvarghese/archive/2010/02/01/view-model-pattern-and-automapper-in-asp-net-mvc-applications.aspx" target="_blank"&gt;View Model pattern and AutoMapper in ASP.NET MVC Applications&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx" target="_blank"&gt;ASP.NET MVC View Model Patterns&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://blogs.msdn.com/b/simonince/archive/2010/01/26/view-models-in-asp-net-mvc.aspx" target="_blank"&gt;View Models in ASP.NET MVC&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/" target="_blank"&gt;How we do MVC &amp;ndash; View models&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://blog.aquabirdconsulting.com/?p=317" target="_blank"&gt;Asp.Net MVC ViewModel with AutoMapper&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2400418.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/03/16/2400418.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2012/03/01/2375181.html</id><title type="text">在Apworks框架中解除NHibernateContext与NHibernateRepository的依赖关系</title><summary type="text">在以前的Apworks框架中，Apworks的核心组件（Apworks.dll）定义了所有与仓储/仓储上下文相关的接口，而在另外的程序集中，实现了这些接口并提供了针对某个ORM框架的仓储/仓储上下文的具体实现。当然，目前我也只是开发了针对NHibernate的仓储实现，也就是那个Apworks.Repositories.NHibernate程序集。这样做的目的，就是为了使得Apworks的核心组件能够脱离具体的第三方组件而独立存在，避免由于第三方组件存在的缺陷而导致核心组件需要频繁更新。这种做法参考了Martin Fowler在其PoEAA一书中描述的Separated Interface模式</summary><published>2012-03-01T03:03:00Z</published><updated>2012-03-01T03:03:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/03/01/2375181.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/03/01/2375181.html"/><content type="html">&lt;p&gt;在以前的&lt;strong&gt;&lt;a href="http://apworks.codeplex.com" target="_blank"&gt;Apworks&lt;/a&gt;&lt;/strong&gt;框架中，Apworks的核心组件（Apworks.dll）定义了所有与仓储/仓储上下文相关的接口，而在另外的程序集中，实现了这些接口并提供了针对某个ORM框架的仓储/仓储上下文的具体实现。当然，目前我也只是开发了针对NHibernate的仓储实现，也就是那个Apworks.Repositories.NHibernate程序集。这样做的目的，就是为了使得Apworks的核心组件能够脱离具体的第三方组件而独立存在，避免由于第三方组件存在的缺陷而导致核心组件需要频繁更新。这种做法参考了Martin Fowler在其PoEAA一书中描述的&lt;strong&gt;&lt;a href="http://martinfowler.com/eaaCatalog/separatedInterface.html" target="_blank"&gt;Separated Interface&lt;/a&gt;&lt;/strong&gt;模式。当然，本文的意图不在于讨论如何将这个模式应用到实际框架的设计和开发过程中，这个内容我会在后续的博客中详细讨论。现在我们来讨论一下这个Apworks.Repositories.NHibernate程序集的设计问题。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;早在Apworks 2.0发布之前，我就意识到这个问题了，之后也有网友针对这个问题发表过&lt;strong&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2011/09/15/2177323.html#2307893" target="_blank"&gt;评论&lt;/a&gt;&lt;/strong&gt;。在现有的设计中，NHibernateContext在通过GetRepository方法返回仓储实例的时候，是直接新建了一个NHibernateRepository的对象然后返回的，这对普通的仓储应用并不会带来太多的影响，比如通过仓储获取一个聚合，或者将某个聚合保存到仓储中等。然而对于那些需要扩展仓储的情形而言，这种设计就是致命的：除了修改Apworks.Repositories.NHibernate程序集的源代码以外，我想，应该没有别的办法来通过NHibernateContext以获得一个定制的仓储实例。以下的代码充分证明了这一点：&lt;/p&gt;public IRepository&amp;lt;TAggregateRoot&amp;gt; GetRepository&amp;lt;TAggregateRoot&amp;gt;() &lt;br/&gt;    where TAggregateRoot : class, IAggregateRoot&lt;br/&gt;{&lt;br/&gt;    string key = typeof(TAggregateRoot).AssemblyQualifiedName;&lt;br/&gt;    if (repositories.ContainsKey(key))&lt;br/&gt;    {&lt;br/&gt;        return repositories[key] as IRepository&amp;lt;TAggregateRoot&amp;gt;;&lt;br/&gt;    }&lt;br/&gt;    else&lt;br/&gt;    {&lt;br/&gt;        var repository = new NHibernateRepository&amp;lt;TAggregateRoot&amp;gt;(this);&lt;br/&gt;        lock (sync)&lt;br/&gt;        {&lt;br/&gt;            repositories.Add(key, repository);&lt;br/&gt;        }&lt;br/&gt;        return repository;&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;现在，我们需要对这部分实现进行修改，使得NHibernateRepository的实现能够像NHibernateContext那样，能够方便地在配置文件中进行配置，说得具体一些，能够通过依赖注入来消除NHibernateContext和NHibernateRepository之间的耦合。例如，在后续的应用程序开发过程中，或许我们会要对仓储进行扩展，比如加入一些分页的功能或者一些特定的查询等。在这种情况下，NHibernateContext也同样能够满足我们的需求。假设我们需要将一个FooRepository应用到应用程序中，我们或许会这样写代码：&lt;/p&gt;public interface IFooRepository&amp;lt;T&amp;gt; : IRepository&amp;lt;T&amp;gt;&lt;br/&gt;    where T : class, IAggregateRoot&lt;br/&gt;{&lt;br/&gt;    IEnumerable&amp;lt;T&amp;gt; GetWithPaging(ISpecification&amp;lt;T&amp;gt; spec, int pageNumber, int pageSize);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;public class FooRepository&amp;lt;T&amp;gt; : NHibernateRepository&amp;lt;T&amp;gt;, IFooRepository&amp;lt;T&amp;gt;&lt;br/&gt;    where T : class, IAggregateRoot&lt;br/&gt;{&lt;br/&gt;    public IEnumerable&amp;lt;T&amp;gt; GetWithPaging(ISpecification&amp;lt;T&amp;gt; spec, int pageNumber, int pageSize)&lt;br/&gt;    {&lt;br/&gt;        //...&lt;br/&gt;    }&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;在使用FooRepository的时候，则有可能是这样写代码：&lt;/p&gt;IFooRepository&amp;lt;Customer&amp;gt; repository = context.GetRepository&amp;lt;Customer&amp;gt;() as IFooRepository&amp;lt;Customer&amp;gt;;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;最后，只需要在IoC容器（在此以Microsoft Unity为例）中注册一下这个仓储，即可实现仓储的替换：&lt;/p&gt;&amp;lt;register type="Apworks.Repositories.IRepository`1[[MyNamespace.Domain.Customer, MyNamespace.Domain]], Apworks"&lt;br/&gt;  mapTo="MyNamespace.Repositories.FooRepository`1[[MyNamespace.Domain.Customer, MyNamepsace.Domain]], MyNamespace.Repositories" /&amp;gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;事实上，我们需要解决的问题，并不是如何去调整仓储的设计，因为从接口的层面上看，这部分内容并没有什么问题。我们需要解决的问题是，如果通过IoC容器将仓储实例注入应用程序后，如何保证这些实例是在同一个Repository Context中进行工作的。这很重要，因为Repository Context担当了Unit Of Work的任务，它需要保证在其管辖的范围内，所有的仓储操作都是在同一个事务中完成的。不仅如此，它还能够允许多个仓储实例共享同一个数据库连接，减少了数据库连接次数。&lt;/p&gt;&lt;p&gt;上面也已经提到，以前是直接创建NHibernateRepository的实例，并通过构造函数将当前Repository Context的实例传给新创建的NHibernateRepository，这样就保证了所有通过Repository Context创建的仓储，都共享了同一个Context。但根据我们现在的设计，虽然仓储在构造函数上依赖IRepositoryContext接口，但如果通过IoC容器来解析获得仓储实例，就会使得IoC容器在解析IRepositoryContext时，会自动创建一个新的Context实例，而不会重用已有的实例。最终出现的结果就是：各个仓储都使用着自己的Context，各自为政，互不相干，更别提事务性的保证了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;有关IoC容器&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;要解决Context共享的问题，还是得从IoC容器部分入手。比如，研究一下IoC容器是否能够在解析Repository时，将已有的Context实例注入到Repository中，使其共享同一个Context，而不是在每次解析Repository时都重新创建一个Context。Microsoft Unity是具有这样的功能的，它具有一种被称之为Resolver Override的功能，能够在解析某个类型的时候，用已存在的另一个类型的实例来覆盖原本应该由IoC容器解析的类型实例。从最初对Apworks的设计来看，它本身是不会依赖于任何第三方的依赖注入框架的，这点在本文开始的时候就已经说明了，因此，我们还需要考察一些常见的第三方依赖注入框架，看它们是否也像Unity那样，具有Resolver Override的功能。&lt;/p&gt;&lt;p&gt;至少，Castle Windsor是支持的，它可以通过向Resolve方法传入匿名类型对象来实现。于是我猜想，类似Resolver Override这样的功能，应该是大部分依赖注入框架所应该具备的功能，我也没有进一步去研究了。总之，我们需要对Apworks的ObjectContainer接口部分开始进行修改，使其也同样具有Resolver Override的功能，这样我们才能在后续解析NHibernateRepository的时候，将已有的NHibernateContext实例注射进去。&lt;/p&gt;&lt;p&gt;首先需要修改的是Apworks.IObjectContainer接口，向其添加两个方法，这两个方法其实是成对的，其中一个是另一个的泛型版本。这两个方法都会接受一个匿名类型的参数，以获得需要重写的实例：&lt;/p&gt;T GetService&amp;lt;T&amp;gt;(object overridedArguments) where T : class;&lt;br/&gt;object GetService(Type serviceType, object overridedArguments);&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;然后修改ObjectContainer抽象类和Apworks.ObjectContainers.Unity.UnityObjectContainer类的代码，使得它们能够正确地实现接口中新定义的这两个方法。ObjectContainer抽象类中的实现还是很简单直观的，就是针对这两个方法分别定义一个DoGetService的受保护（protected）方法，这么做的理由是因为ObjectContainer需要为AOP拦截提供便利；然后再在UnityObjectContainer中实现所需的受保护方法。在UnityObjectContainer中，我们重载了ObjectContainer抽象类中的DoGetService非泛型方法，并通过反射以实现Unity对Resolver Override功能的支持。代码如下：&lt;/p&gt;protected override object DoGetService(Type serviceType, object overridedArguments)&lt;br/&gt;{&lt;br/&gt;    List&amp;lt;ParameterOverride&amp;gt; overrides = new List&amp;lt;ParameterOverride&amp;gt;();&lt;br/&gt;    Type argumentsType = overridedArguments.GetType();&lt;br/&gt;    argumentsType.GetProperties(BindingFlags.Public | BindingFlags.Instance)&lt;br/&gt;        .ToList()&lt;br/&gt;        .ForEach(property =&amp;gt;&lt;br/&gt;        {&lt;br/&gt;            var propertyValue = property.GetValue(overridedArguments, null);&lt;br/&gt;            var propertyName = property.Name;&lt;br/&gt;            overrides.Add(new ParameterOverride(propertyName, propertyValue));&lt;br/&gt;        });&lt;br/&gt;    return container.Resolve(serviceType, overrides.ToArray());&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;说明一下，就基于Castle Windsor框架的ObjectContainer的实现而言，我们只需要向WindsorContainer.Resolve方法传入这个overridedArguments对象就可以了，而不需要通过反射来做这部分转换。&lt;/p&gt;&lt;p&gt;至此，对IoC容器的修改就完成了。接下来就是使用这个更新了的容器来实现对NHibernateContext和NHibernateRepository的解耦。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;RepositoryContextManager&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在，Apworks.Repositories命名空间下有了一个新成员：RepositoryContextManager，其主要任务就是管理RepositoryContext，并向外界提供仓储的实例。从某种意义上讲，它更像是RepositoryContext的代理。以前，当我们需要获得仓储实例时，我们需要使用IRepositoryContext.GetRepository方法来获得，而现在，由于Resolver Overrides的引入，我们可以直接通过IoC容器来获得仓储实例，只是在做解析的时候，需要把已有的RepositoryContext实例注入到解析的过程中。从这个角度讲，RepositoryContextManager的功能其实也是对这一过程的封装，不仅简化了代码的编写，而且还降低了出错的风险，因为我们很容易忘记在解析仓储实例的时候，忘记把Context的实例也一并传入。&lt;/p&gt;&lt;p&gt;RepositoryContextManager在构造函数中，就通过IoC容器获得了Context的实例，由于它继承了DisposableObject，并实现了IUnitOfWork接口，这就使得RepositoryContextManager的使用更像原有的RepositoryContext。比如，在操作仓储时，以前我们是这样写代码的：&lt;/p&gt;using (IRepositoryContext ctx = IoCFactory.GetService&amp;lt;IRepositoryContext&amp;gt;())&lt;br/&gt;{&lt;br/&gt;    IRepository&amp;lt;Customer&amp;gt; customerRepository = ctx.GetRepository&amp;lt;Customer&amp;gt;();&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;而现在我们可以直接这样写：&lt;/p&gt;using (RepositoryContextManager mgr = new RepositoryContextManager())&lt;br/&gt;{&lt;br/&gt;  IRepository&amp;lt;Customer&amp;gt; customerRepository = mgr.GetRepository&amp;lt;Customer&amp;gt;();&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;代码上虽然看上去不会有太大差别，但后面的实现机制却有了显著的变换：通过使用RepositoryContextManager，我们解耦了Context和Repository（在我们的例子中，确切地说，是NHibernateContext和NHibernateRepository）。在看完RepositoryContextManager.GetRepository方法的实现代码后，我想你一定会恍然大悟的：&lt;/p&gt;public IRepository&amp;lt;T&amp;gt; GetRepository&amp;lt;T&amp;gt;()&lt;br/&gt;    where T : class, IAggregateRoot&lt;br/&gt;{&lt;br/&gt;    IRepository&amp;lt;T&amp;gt; repository = AppRuntime&lt;br/&gt;        .Instance&lt;br/&gt;        .CurrentApplication&lt;br/&gt;        .ObjectContainer&lt;br/&gt;        .GetService&amp;lt;IRepository&amp;lt;T&amp;gt;&amp;gt;(new { context = this.context });&lt;br/&gt;    &lt;br/&gt;    return repository;&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;在这个方法中，使用了我们新定义的IoC容器接口函数，在解析IRepository接口的时候，将当前Context的实例注射到解析过程中。而&amp;ldquo;context = this.context&amp;rdquo;这句话中的第一个&amp;ldquo;context&amp;rdquo;，正是Repository抽象类中构造函数的第一个参数名称。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;有关AOP拦截&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;如果我们不使用AOP拦截，那么至此问题已经得到圆满的解决了。然而，AOP拦截对于构建一个高复用性、高延展性的企业级应用是多么的重要。Apworks是支持AOP拦截的，而且它也不会允许由于一些框架上的变动而导致AOP拦截在某些情况下不起作用。因此，框架中代码的变动，需要去迎合AOP拦截功能。&lt;/p&gt;&lt;p&gt;细心的读者在阅读RepositoryContextManager源代码的时候，就会注意到，如果我们启用了Apworks的AOP拦截功能，那么事实上在GetRepository方法中，通过IoC容器注入的Context实例，已经不再是我们的NHibernateContext实例了，而是由Castle Dynamic Proxy框架（Apworks使用这个框架做AOP拦截）产生的一个实现了IRepositoryContext接口、对NHibernateContext对象进行代理的代理类。结构类似如下：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/20120301110225840.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203011102282528.png" alt="image" width="686" height="312" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;于是，这个动态产生的_Castle_RepositoryContext类被不幸地注射到了NHibernateRepository的构造函数中。而在NHibernateRepository中，显然是无法通过as关键字将_Castle_RepositoryContext转换为NHibernateContext的，因为两者没有继承关系，因此，也就无法获得NHibernateContext中的这个session对象：&lt;/p&gt;public NHibernateRepository(IRepositoryContext context)&lt;br/&gt;            : base(context)&lt;br/&gt;{&lt;br/&gt;    if (context is NHibernateContext)&lt;br/&gt;    {&lt;br/&gt;        NHibernateContext nhContext = context as NHibernateContext;&lt;br/&gt;        this.session = nhContext.Session;&lt;br/&gt;    }&lt;br/&gt;    else&lt;br/&gt;        throw new RepositoryException(Resources.EX_INVALID_CONTEXT_TYPE);&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;此时context is NHibernateContext的判定就是False，直接抛出了异常。&lt;/p&gt;&lt;p&gt;要解决这个问题，我们需要重新设计NHibernateContext，我们需要添加一个新的接口，使得NHibernateContext实现这个新的接口，同时，我们需要将这个接口织入动态代理类中，使得这个类也同样可以通过接口获得我们需要的数据。于是，我们的设计大致如下：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203011102302264.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201203/201203011102334998.png" alt="image" width="756" height="461" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在这种设计下，虽然context is NHibernateContext的判定还是False，但我们已经可以通过将_Castle_RepositoryContext转换为INHibernateContext，进而获得session的实例。当然，我们更希望Apworks不仅仅是针对NHibernateContext这个特例来处理这样的接口织入，而且应该能够处理更通用的场景。因此，我们可以新建一个Attribute，在使用Castle Dynamic Proxy产生代理类之前，判断被代理的类型是否有这个Attribute，并通过Attribute的值来获得需要织入的接口类型，然后将这个接口类型织入代理类即可。在Apworks中，Apworks.Interception.AdditionalInterfaceToProxyAttribute就是这样一种Attribute：&lt;/p&gt;[AttributeUsage(AttributeTargets.Class, AllowMultiple=true, Inherited=false)]&lt;br/&gt;public class AdditionalInterfaceToProxyAttribute : System.Attribute&lt;br/&gt;{&lt;br/&gt;    #region Public Properties&lt;br/&gt;    /// &amp;lt;summary&amp;gt;&lt;br/&gt;    /// Gets or sets the type of the interface that needs to be intercepted&lt;br/&gt;    /// when the proxy object is created.&lt;br/&gt;    /// &amp;lt;/summary&amp;gt;&lt;br/&gt;    public Type InterfaceType { get; set; }&lt;br/&gt;    #endregion&lt;br/&gt;&lt;br/&gt;    #region Ctor&lt;br/&gt;    /// &amp;lt;summary&amp;gt;&lt;br/&gt;    /// Initializes a new instance of &amp;lt;c&amp;gt;AdditionalInterfaceToProxyAttribute&amp;lt;/c&amp;gt;.&lt;br/&gt;    /// &amp;lt;/summary&amp;gt;&lt;br/&gt;    /// &amp;lt;param name="intfType"&amp;gt;The type of the interface that needs to be intercepted&lt;br/&gt;    /// when the proxy object is create.&amp;lt;/param&amp;gt;&lt;br/&gt;    public AdditionalInterfaceToProxyAttribute(Type intfType)&lt;br/&gt;    {&lt;br/&gt;        this.InterfaceType = intfType;&lt;br/&gt;    }&lt;br/&gt;    #endregion&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;然后修改ObjectContainer抽象类的GetProxyObject私有方法，使得它能够处理上面所述的这些逻辑：&lt;/p&gt;private object GetProxyObject(Type targetType, object targetObject)&lt;br/&gt;{&lt;br/&gt;    IInterceptor[] interceptors = AppRuntime.Instance.CurrentApplication.Interceptors.ToArray();&lt;br/&gt;&lt;br/&gt;    if (interceptors == null ||&lt;br/&gt;        interceptors.Length == 0)&lt;br/&gt;        return targetObject;&lt;br/&gt;&lt;br/&gt;    if (targetType.IsInterface)&lt;br/&gt;    {&lt;br/&gt;        object obj = null;&lt;br/&gt;        ProxyGenerationOptions proxyGenerationOptionsForInterface = new ProxyGenerationOptions();&lt;br/&gt;        proxyGenerationOptionsForInterface.Selector = interceptorSelector;&lt;br/&gt;        Type targetObjectType = targetObject.GetType();&lt;br/&gt;        if (targetObjectType.IsDefined(typeof(BaseTypeForInterfaceProxyAttribute), false))&lt;br/&gt;        {&lt;br/&gt;            BaseTypeForInterfaceProxyAttribute baseTypeForIPAttribute = targetObjectType&lt;br/&gt;              .GetCustomAttributes(typeof(BaseTypeForInterfaceProxyAttribute), false)[0] &lt;br/&gt;                as BaseTypeForInterfaceProxyAttribute;&lt;br/&gt;            proxyGenerationOptionsForInterface.BaseTypeForInterfaceProxy = baseTypeForIPAttribute.BaseType;&lt;br/&gt;        }&lt;br/&gt;        if (targetObjectType.IsDefined(typeof(AdditionalInterfaceToProxyAttribute), false))&lt;br/&gt;        {&lt;br/&gt;            List&amp;lt;Type&amp;gt; intfTypes = targetObjectType.GetCustomAttributes(typeof(AdditionalInterfaceToProxyAttribute), false)&lt;br/&gt;             .Select(p =&amp;gt;&lt;br/&gt;             {&lt;br/&gt;                 AdditionalInterfaceToProxyAttribute attrib = p as AdditionalInterfaceToProxyAttribute;&lt;br/&gt;                 return attrib.InterfaceType;&lt;br/&gt;             }).ToList();&lt;br/&gt;            obj = proxyGenerator.CreateInterfaceProxyWithTarget(targetType, &lt;br/&gt;              intfTypes.ToArray(), &lt;br/&gt;              targetObject, &lt;br/&gt;              proxyGenerationOptionsForInterface, &lt;br/&gt;              interceptors);&lt;br/&gt;        }&lt;br/&gt;        else&lt;br/&gt;            obj = proxyGenerator.CreateInterfaceProxyWithTarget(targetType, &lt;br/&gt;              targetObject, proxyGenerationOptionsForInterface, interceptors);&lt;br/&gt;        return obj;&lt;br/&gt;    }&lt;br/&gt;    else&lt;br/&gt;        return proxyGenerator.CreateClassProxyWithTarget(targetType, &lt;br/&gt;          targetObject, proxyGenerationOptions, interceptors);&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;本文详细介绍了在Apworks中解耦NHibernateContext与NHibernateRepository的具体方法，并对这个过程中遇到的问题进行了分析。虽然所介绍的内容是基于Apworks这一框架的，而并不是所有的读者朋友对这个框架都比较熟悉，但本文在一定层面上提供了解决实际问题的思路，比如如何在不改变现有框架行为的情况下，使得新的功能能够被集成进来，希望这些思路能够帮助到正在这条道路上进行探索，并遇到实际困难的朋友。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2375181.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/03/01/2375181.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2012/02/16/2354703.html</id><title type="text">在.NET下使用Task Parallel Library提高程序性能</title><summary type="text">.NET 4.0中的Task Parallel Library（TPL）已经不是什么新鲜事了，相信很多朋友也阅读过不少有关TPL的书籍资料。而另一方面，能够将TPL合理地运用在实际项目开发过程中，以提高程序的执行效率，这种情况也并不多见。本文就以实际项目中的一个程序功能为例，简要讨论一下TPL的应用。在此我不打算对TPL的相关基础知识做过多讨论，这些内容在网上应该有不少的文章资料可供参考；同时读者朋友还可以阅读一些有关TPL的经典书籍，以便加深对TPL的理解。文章最后我会推荐几本不错的有关.NET 4.0下TPL的书籍资料。案例：批量对象的XML序列化在某个项目中，需要对一大批相同类型的对象进</summary><published>2012-02-16T10:14:00Z</published><updated>2012-02-16T10:14:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/02/16/2354703.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/02/16/2354703.html"/><content type="html">&lt;p&gt;.NET 4.0中的Task Parallel Library（TPL）已经不是什么新鲜事了，相信很多朋友也阅读过不少有关TPL的书籍资料。而另一方面，能够将TPL合理地运用在实际项目开发过程中，以提高程序的执行效率，这种情况也并不多见。本文就以实际项目中的一个程序功能为例，简要讨论一下TPL的应用。在此我不打算对TPL的相关基础知识做过多讨论，这些内容在网上应该有不少的文章资料可供参考；同时读者朋友还可以阅读一些有关TPL的经典书籍，以便加深对TPL的理解。文章最后我会推荐几本不错的有关.NET 4.0下TPL的书籍资料。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;案例：批量对象的XML序列化&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在某个项目中，需要对一大批相同类型的对象进行XML序列化操作，在序列化工作完成后，程序会把序列化所得的XML字符串根据对象的ID值保存到一个字典（Dictionary）的对象中，以便后续的程序逻辑能够使用这些序列化后的XML。为了简化起见，我定义了一个Customer类来模拟这些对象的类型（实际项目中的对象类型要比这个Customer复杂一些），这个Customer类仅包含两个属性：ID和Name。下图大致描述了这个处理过程：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201202/201202161813282257.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201202/201202161813314539.png" alt="image" width="761" height="342" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;现在让我们先定义这个Customer类，以便为接下来的实验作准备。Customer类的定义如下：&lt;/p&gt;public class Customer&lt;br/&gt;{&lt;br/&gt;    public long ID { get; set; }&lt;br/&gt;    public string Name { get; set; }&lt;br/&gt;&lt;br/&gt;    public override string ToString()&lt;br/&gt;    {&lt;br/&gt;        return Name;&lt;br/&gt;    }&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;下面，我们分别使用传统的方式和基于TPL的并行处理方式来实现这个程序，然后比较一下这两种方式产生的效果差异。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;传统的实现方式&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;传统的实现方式很简单，基本思路就是对每一个Customer对象，使用XmlSerializer对其进行序列化操作，然后把产生的XML字符串保存到字典中。代码如下：&lt;/p&gt;static IEnumerable&amp;lt;KeyValuePair&amp;lt;long, string&amp;gt;&amp;gt; SerializeCustomers(Customer[] customers)&lt;br/&gt;{&lt;br/&gt;    var dict = new Dictionary&amp;lt;long, string&amp;gt;();&lt;br/&gt;    var xmlSerializer = new XmlSerializer(typeof(Customer));&lt;br/&gt;    foreach (var customer in customers)&lt;br/&gt;    {&lt;br/&gt;        using (var ms = new MemoryStream())&lt;br/&gt;        {&lt;br/&gt;            xmlSerializer.Serialize(ms, customer);&lt;br/&gt;            dict.Add(customer.ID, Encoding.ASCII.GetString(ms.ToArray()));&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    return dict;&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;基于TPL的并行处理方式&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在采用这种方式之前，需要对我们的应用场景进行分析。今后在项目中打算使用TPL之前，都应该进行这样的分析。主要目的就是为了讨论目前我们所面对的场景，是否可以使用并行计算。目前我们的应用场景是可以采用TPL的并行处理方式的。因为首先，针对每个Customer对象的序列化操作都相对独立，没有先后顺序之分，即各操作之间是可替换的，比如计算a+b+c，可以先计算a+b（也就是(a+b)+c），也可以先计算b+c（也就是a+(b+c)）；其次，虽然在最后整合结果的时候需要访问跨线程的共享资源，也就是在最后整合结果的时候产生了资源的依赖关系，但对于整个计算的过程，各个任务都是可以互不干扰地执行的。在运用TPL的时候，我觉得应该尽可能地降低各个任务之间的依赖关系，因为TPL中的任务有可能会被分配到不同的线程去执行，如果任务之间有资源的相互依赖的话，线程同步将降低任务执行的效率。&lt;/p&gt;&lt;p&gt;以下是此案例的TPL版本：&lt;/p&gt;static IEnumerable&amp;lt;KeyValuePair&amp;lt;long, string&amp;gt;&amp;gt; ParallelSerializeCustomers(Customer[] customers)&lt;br/&gt;{&lt;br/&gt;    var dict = new Dictionary&amp;lt;long, string&amp;gt;();&lt;br/&gt;    var xmlSerializer = new XmlSerializer(typeof(Customer));&lt;br/&gt;    object lockObj = new object();&lt;br/&gt;    Parallel.ForEach(customers, () =&amp;gt; new Dictionary&amp;lt;long, string&amp;gt;(),&lt;br/&gt;        (customer, loopState, single) =&amp;gt;&lt;br/&gt;            {&lt;br/&gt;                using (var ms = new MemoryStream())&lt;br/&gt;                {&lt;br/&gt;                    xmlSerializer.Serialize(ms, customer);&lt;br/&gt;                    single.Add(customer.ID, Encoding.ASCII.GetString(ms.ToArray()));&lt;br/&gt;                }&lt;br/&gt;                return single;&lt;br/&gt;            }, &lt;br/&gt;        (single) =&amp;gt;&lt;br/&gt;            {&lt;br/&gt;                lock (lockObj)&lt;br/&gt;                {&lt;br/&gt;                    single.ToList().ForEach(p =&amp;gt; dict.Add(p.Key, p.Value));&lt;br/&gt;                }&lt;br/&gt;            });&lt;br/&gt;    return dict;&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;在ParallelSerializeCustomers方法中，采用了foreach循环的并行版本：Parallel.ForEach方法。这个方法与foreach类似，会逐个轮询给定的IEnumerable对象中的没一个值，不过Parallel.ForEach方法会将这个轮询的过程分配到多个Task上执行，因此对于Parallel.ForEach，执行过程的中断（break）以及异常处理都与foreach完全不同。在这个例子中，我们使用的是Parallel.ForEach方法的其中一个重载版本，在这个方法重载中，首先我们将需要轮询的IEnumerable对象（也就是这里的customers数组）传递给该方法；之后有一个Func&amp;lt;TLocal&amp;gt;的委托参数，这个委托参数的作用是为了对Task执行线程范围内的局部变量进行初始化，在这里我们直接使用Lambda表达式返回了一个新建的Dictionary&amp;lt;long, string&amp;gt;对象，表示需要对线程范围内的局部变量（其实就是第三个参数中的那个single变量）初始化成一个新的Dictionary&amp;lt;long, string&amp;gt;实例；第三个参数也是一个委托，用于对当前的枚举对象执行真正的处理逻辑，然后将处理结果返回；第四个参数则是用来整合每个任务的处理结果，以得到最终结果。不难看出，在整合最终结果的时候，多个线程需要同时访问dict变量，因此需要使用lock关键字以保证线程同步。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;执行效果对比&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;以下是在一台具有4核CPU的计算机上，处理十万（100000）个Customer对象的执行效果，可见基于TPL的实现效率要比传统的实现方式高很多。值得一提的是，传统方式所产生的dict是有序的，而基于TPL的方式所产生的dict则是无序的，但这并不影响结果，因为程序并不会关心dict中的值是否有序。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201202/20120216181336641.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201202/201202161813437157.png" alt="image" width="681" height="182" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;以下是传统实现方式下，CPU的利用率。我们可以看到，基本上CPU的利用率只能达到20%-30%左右，大部分CPU资源都没有利用到：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201202/201202161813511603.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201202/201202161814003956.png" alt="image" width="533" height="581" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;以下是基于TPL方式下，CPU的利用率，基本上能达到85%以上（估计剩下的部分由于IO的原因，所以没有达到更高的CPU利用率）：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201202/20120216181407289.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201202/201202161814174410.png" alt="image" width="533" height="581" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考书籍&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;&lt;strong&gt;&lt;a href="http://www.amazon.com/Parallel-Programming-Microsoft-Visual-Studio/dp/0735640602/ref=sr_1_1?ie=UTF8&amp;amp;qid=1329386725&amp;amp;sr=8-1" target="_blank"&gt;《Parallel Programming with Microsoft Visual Studio 2010 Step by Step》&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;&lt;strong&gt;&lt;a href="http://www.amazon.com/Professional-Parallel-Programming-Extensions-Programmer/dp/0470495995/ref=sr_1_1?s=books&amp;amp;ie=UTF8&amp;amp;qid=1329386785&amp;amp;sr=1-1" target="_blank"&gt;《Professional Parallel Programming with C#: Master Parallel Extensions with .NET 4》&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;&lt;strong&gt;&lt;a href="http://www.amazon.com/Parallel-Programming-Microsoft-NET-Decomposition/dp/0735651590/ref=pd_rhf_se_shvl1" target="_blank"&gt;《Parallel Programming with Microsoft .NET: Design Patterns for Decomposition and Coordination on Multicore Architectures》&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;案例代码下载&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://files.cnblogs.com/daxnet/SerializeTest2.rar" target="_blank"&gt;【请单击此处下载本文案例源代码】&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2354703.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/02/16/2354703.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2012/01/19/2326758.html</id><title type="text">在Visual Studio 2010中创建多项目（解决方案）模板【三】</title><summary type="text">前文回顾：在Visual Studio 2010中创建多项目（解决方案）模板【一】：多项目解决方案模板的创建在Visual Studio 2010中创建多项目（解决方案）模板【二】：Template Wizard的使用本文主要讨论多项目（解决方案）模板的部署相关问题，包括：为多项目解决方案模板设置模板名称修改多项目解决方案模板的图标创建Visual Studio 2010扩展的安装包VSIX文件为多项目解决方案模板设置模板名称模板名称的设置非常简单,，只需要修改CMSProjectTemplate.vstemplate文件中的Name XML节点的内容即可。例如，我们可以为我们的模板起名为：C</summary><published>2012-01-19T06:43:00Z</published><updated>2012-01-19T06:43:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/01/19/2326758.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/01/19/2326758.html"/><content type="html">&lt;p&gt;前文回顾：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/01/17/2324969.html" target="_blank"&gt;在Visual Studio 2010中创建多项目（解决方案）模板【一】：多项目解决方案模板的创建&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/01/18/2325928.html" target="_blank"&gt;在Visual Studio 2010中创建多项目（解决方案）模板【二】：Template Wizard的使用&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;本文主要讨论多项目（解决方案）模板的部署相关问题，包括：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;为多项目解决方案模板设置模板名称&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;修改多项目解决方案模板的图标&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;创建Visual Studio 2010扩展的安装包VSIX文件&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;为多项目解决方案模板设置模板名称&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;模板名称的设置非常简单,，只需要修改CMSProjectTemplate.vstemplate文件中的Name XML节点的内容即可。例如，我们可以为我们的模板起名为：Customer Management System Solution：&lt;/p&gt;&amp;lt;Name&amp;gt;Customer Management System Solution&amp;lt;/Name&amp;gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;修改多项目解决方案模板的图标&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;模板图标的修改也非常简单，在文件系统中找一个ICO的图标文件，将CMSProjectTemplate项目目录下的CMSProjectTemplate.ico文件替换掉即可。例如我使用下面的图标作为模板的图标：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442131837.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442149230.png" alt="image" width="55" height="50" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;现在编译CMSProjectTemplate项目，并将产生的ZIP文件拷贝到Visual C#的ProjectTemplate目录下，重新打开New Project对话框，我们可以看到下面的效果：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442187131.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442236603.png" alt="image" width="725" height="276" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;创建Visual Studio 2010扩展的安装包VSIX文件&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在，我们可以使用VSIX来为最终用户提供一个安装项目模板的安装包，到时候用户只需要双击这个VSIX文件即可将所需的项目模板以插件的形式安装到Visual Studio中。&lt;/p&gt;&lt;p&gt;首先，在CMSProjectTemplate解决方案中，新建一个VSIX Project的项目，我们取名为CMSProjectTemplateVSIX：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442312987.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442358413.png" alt="image" width="716" height="438" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在source.extension.vsixmanifest文件的设计界面，设置如下属性：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;Product Name：Customer Management System Project Template&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;Author：&amp;lt;填写你自己的姓名，或者公司名&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;Description：&amp;lt;填写一些描述信息&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;其它内容你可以选填，至于License Terms，你可以找一个txt或者rtf文件，用来描述许可协议。填写完后，设计界面大致如下：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442365249.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442383623.png" alt="image" width="779" height="273" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;然后，在设计界面的Content部分，单击Add Content按钮，此时将弹出Add Content对话框，在Select a content type下拉框中，选择Project Template，在Select a source选项中选择CMSProjectTemplate项目，然后单击OK按钮：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442392934.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442412246.png" alt="image" width="473" height="342" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;用相同的方法，添加Template Wizard：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442421242.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442436093.png" alt="image" width="473" height="342" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;完成这两项内容的添加以后，设计界面的Content部分大致如下：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442445504.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442444914.png" alt="image" width="524" height="131" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;OK，现在保存并编译CMSProjectTemplateVSIX项目，完成编译之后，我们在输出目录中找到了VSIX文件：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442455961.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442451468.png" alt="image" width="592" height="81" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;双击CMSProjectTemplateVSIX.vsix文件，将出现如下对话框：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442462731.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442488139.png" alt="image" width="464" height="348" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;单击Install按钮完成Visual Studio 2010扩展的安装。安装完成后，重新启动Visual Studio 2010，点击Tools &amp;ndash;&amp;gt; Extension Manager菜单，我们可以在打开的Extension Manager对话框中找到刚刚安装的扩展包：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/20120119144251284.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201191442553792.png" alt="image" width="698" height="427" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;用户可以根据自己的需要对其进行禁用或者卸载。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;本系列文章从一个案例解决方案开始，逐步介绍了如何使用Visual Studio 2010 SDK来创建一个多项目的解决方案模板项目，并介绍了其中的一些高级应用。希望这样的文章能够真正地帮助到有这方面需求的读者朋友。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;本文案例下载&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://files.cnblogs.com/daxnet/CMSProjectTemplate.rar" target="_blank"&gt;CMSProjectTemplate&lt;/a&gt;&lt;/strong&gt;（完整版）&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考文献&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Creating and Sharing Project &amp;amp; Item Templates：&lt;a title="http://blogs.msdn.com/b/visualstudio/archive/2010/03/04/creating-and-sharing-project-item-templates.aspx" href="http://blogs.msdn.com/b/visualstudio/archive/2010/03/04/creating-and-sharing-project-item-templates.aspx"&gt;http://blogs.msdn.com/b/visualstudio/archive/2010/03/04/creating-and-sharing-project-item-templates.aspx&lt;/a&gt;&lt;/li&gt;&lt;li&gt;How to: Create Multi-Project Templates: &lt;a title="http://msdn.microsoft.com/en-us/library/ms185308.aspx" href="http://msdn.microsoft.com/en-us/library/ms185308.aspx"&gt;http://msdn.microsoft.com/en-us/library/ms185308.aspx&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Can't Avoid the ProjectGuid from Being Changed in .csproj File：&lt;a title="http://social.msdn.microsoft.com/Forums/en-US/csharpide/thread/1d632940-cc1d-49d5-a64c-d3e999216cbd" href="http://social.msdn.microsoft.com/Forums/en-US/csharpide/thread/1d632940-cc1d-49d5-a64c-d3e999216cbd"&gt;http://social.msdn.microsoft.com/Forums/en-US/csharpide/thread/1d632940-cc1d-49d5-a64c-d3e999216cbd&lt;/a&gt;&lt;/li&gt;&lt;li&gt;VSPackage: Force a project to unload then reload：&lt;a title="http://social.msdn.microsoft.com/Forums/en/vsx/thread/49f69447-951a-4a9c-9c69-9a821f2a367c" href="http://social.msdn.microsoft.com/Forums/en/vsx/thread/49f69447-951a-4a9c-9c69-9a821f2a367c"&gt;http://social.msdn.microsoft.com/Forums/en/vsx/thread/49f69447-951a-4a9c-9c69-9a821f2a367c&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Multi-Project Templates with Wizard: Visual Studio 2010 Sample：&lt;a title="http://vsix.codeplex.com/" href="http://vsix.codeplex.com/"&gt;http://vsix.codeplex.com/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2326758.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/01/19/2326758.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2012/01/18/2325928.html</id><title type="text">在Visual Studio 2010中创建多项目（解决方案）模板【二】</title><summary type="text">在上文中我给大家介绍了多项目解决方案模板的创建，在文章的最后我们遇到了一个问题，就是$safeprojectname$这个模板参数（宏）所指代的意义在各个项目中都不一样，而我们却希望它能够简单地指代用户所输入的项目名称。本文将从这个问题出发，讨论在Visual Studio 2010中是如何使用Template Wizard来设计复杂的多项目解决方案的。Template Wizard的基本应用创建Template Wizard项目在CMSProjectTemplate解决方案下，新建一个C# Class Library，取名为CMSProjectTemplateWizard，在该项目上添加Mi</summary><published>2012-01-18T12:17:00Z</published><updated>2012-01-18T12:17:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/01/18/2325928.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/01/18/2325928.html"/><content type="html">&lt;p&gt;在&lt;strong&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/01/17/2324969.html" target="_blank"&gt;上文&lt;/a&gt;&lt;/strong&gt;中我给大家介绍了多项目解决方案模板的创建，在文章的最后我们遇到了一个问题，就是$safeprojectname$这个模板参数（宏）所指代的意义在各个项目中都不一样，而我们却希望它能够简单地指代用户所输入的项目名称。本文将从这个问题出发，讨论在Visual Studio 2010中是如何使用Template Wizard来设计复杂的多项目解决方案的。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Template Wizard的基本应用&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;创建Template Wizard项目&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在CMSProjectTemplate解决方案下，新建一个C# Class Library，取名为CMSProjectTemplateWizard，在该项目上添加Microsoft.VisualStudio.TemplateWizardInterface以及EnvDTE的引用（注意：此时需要将EnvDTE的Embed Interop Types设置为False），然后新建一个名为RootWizardImpl的类，使其继承于Microsoft.VisualStudio.TemplateWizard.IWizard接口，然后实现该接口中的方法。RootWizardImpl类的代码如下：&lt;/p&gt;public class RootWizardImpl : IWizard&lt;br/&gt;{&lt;br/&gt;    private string safeprojectname;&lt;br/&gt;    private static Dictionary&amp;lt;string, string&amp;gt; globalParameters = new Dictionary&amp;lt;string, string&amp;gt;();&lt;br/&gt;&lt;br/&gt;    public static IEnumerable&amp;lt;KeyValuePair&amp;lt;string, string&amp;gt;&amp;gt; GlobalParameters&lt;br/&gt;    {&lt;br/&gt;        get { return globalParameters; }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #region IWizard Members&lt;br/&gt;&lt;br/&gt;    public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem) { }&lt;br/&gt;&lt;br/&gt;    public void ProjectFinishedGenerating(EnvDTE.Project project) { }&lt;br/&gt;&lt;br/&gt;    public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem) { }&lt;br/&gt;&lt;br/&gt;    public void RunFinished() { }&lt;br/&gt;&lt;br/&gt;    public void RunStarted(object automationObject, &lt;br/&gt;        Dictionary&amp;lt;string, string&amp;gt; replacementsDictionary, &lt;br/&gt;        WizardRunKind runKind, object[] customParams)&lt;br/&gt;    {&lt;br/&gt;        safeprojectname = replacementsDictionary["$safeprojectname$"];&lt;br/&gt;        globalParameters["$safeprojectname$"] = safeprojectname;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    public bool ShouldAddProjectItem(string filePath) { return true; }&lt;br/&gt;&lt;br/&gt;    #endregion&lt;br/&gt;}&lt;p&gt;在上面的代码中，我们仅实现了RunStarted方法，在这个方法中，我们首先通过replacementsDictionary将&amp;ldquo;根项目&amp;rdquo;（也就是对Visual Studio而言的那个单一项目）的$safeprojectname$的值取出，然后将其放到一个静态字典集合globalParameters中，这个globalParameters会在后面子项目的TemplateWizard中使用，以替代子项目中$safeprojectname$的值。&lt;/p&gt;&lt;p&gt;顺便说一下RunStarted方法的几个参数：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;automationObject：DTE的自动化对象，它可以被转换成DTE接口的实例，以便在代码中操作Visual Studio IDE&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;replacementsDictionary：包含了所有内嵌的和自定义的模板参数（宏），这些参数值会在项目完成创建时，替换掉项目各个文件中所出现的与之对应的参数（宏）&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;WizardRunKind：指代Template Wizard的执行类型，比如是创建Item Template、Project Template还是Multiple-Project Template&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;customParams：包含了来自vstemplate文件的自定义参数。在vstemplate文件中，可以在WizardData XML节点下设置这些自定义的值&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;现在，让我们继续在CMSProjectTemplateWizard项目中新建一个名为ChildWizardImpl的类，同样让其继承于Microsoft.VisualStudio.TemplateWizard.IWizard接口，具体代码如下：&lt;/p&gt;public class ChildWizardImpl : IWizard&lt;br/&gt;{&lt;br/&gt;    #region IWizard Members&lt;br/&gt;&lt;br/&gt;    public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem) { }&lt;br/&gt;&lt;br/&gt;    public void ProjectFinishedGenerating(EnvDTE.Project project) { }&lt;br/&gt;&lt;br/&gt;    public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem) { }&lt;br/&gt;&lt;br/&gt;    public void RunFinished() { }&lt;br/&gt;&lt;br/&gt;    public void RunStarted(object automationObject, &lt;br/&gt;        Dictionary&amp;lt;string, string&amp;gt; replacementsDictionary, &lt;br/&gt;        WizardRunKind runKind, object[] customParams)&lt;br/&gt;    {&lt;br/&gt;        string safeprojectname = RootWizardImpl.GlobalParameters.Where(p =&amp;gt; p.Key == "$safeprojectname$").First().Value;&lt;br/&gt;        replacementsDictionary["$safeprojectname$"] = safeprojectname;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    public bool ShouldAddProjectItem(string filePath) { return true; }&lt;br/&gt;&lt;br/&gt;    #endregion&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;接下来，我们需要对CMSProjectTemplateWizard进行数字签名，可以直接在项目上直接单击鼠标右键，选择Properties，在打开的项目属性标签页上选择Signing，并为项目制定一个强名称密钥文件：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016222982.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016237376.png" alt="image" width="728" height="184" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;重新编译CMSProjectTemplateWizard，然后打开Visual Studio 2010 Command Prompt工具，在命令提示符中使用gacutil.exe将编译出来的程序集安装到GAC中：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016245608.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016255476.png" alt="image" width="683" height="216" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;现在我们已经创建了一个Template Wizard项目，接下来，我们需要调整CMSProjectTemplate的设置，使其能够使用已创建的Template Wizard&lt;/p&gt;&lt;p&gt;&lt;strong&gt;在CMSProjectTemplate中使用Template Wizard&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;打开CMSProjectTemplate.vstemplate文件，在文件的底部TemplateContent节点之后加入WizardExtension节点，设置节点的内容如下：&lt;/p&gt;&amp;lt;WizardExtension&amp;gt;&lt;br/&gt;  &amp;lt;Assembly&amp;gt;CMSProjectTemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=52319e57efa35eb8&amp;lt;/Assembly&amp;gt;&lt;br/&gt;  &amp;lt;FullClassName&amp;gt;CMSProjectTemplateWizard.RootWizardImpl&amp;lt;/FullClassName&amp;gt;&lt;br/&gt;&amp;lt;/WizardExtension&amp;gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;逐一打开CMSProjectTemplate\CMSTemplate下的所有子目录，修改每个目录下的MyTemplate.vstemplate文件，在文件的底部TemplateContent节点之后加入WizardExtension节点，设置节点的内容如下：&lt;/p&gt;&amp;lt;WizardExtension&amp;gt;&lt;br/&gt;  &amp;lt;Assembly&amp;gt;CMSProjectTemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=52319e57efa35eb8&amp;lt;/Assembly&amp;gt;&lt;br/&gt;  &amp;lt;FullClassName&amp;gt;CMSProjectTemplateWizard.ChildWizardImpl&amp;lt;/FullClassName&amp;gt;&lt;br/&gt;&amp;lt;/WizardExtension&amp;gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;重新编译CMSProjectTemplate项目，并将编译输出的ZIP文件复制到&lt;em&gt;&amp;lt;User_Documents&amp;gt;&lt;/em&gt;\Visual Studio 2010\Templates\ProjectTemplates\Visual C#目录下。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;重新测试CMSProjectTemplate&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在让我们重新新建一个CMSProjectTemplate的项目，在Visual Studio 2010中单击File &amp;ndash;&amp;gt; New &amp;ndash;&amp;gt; Project菜单，在弹出的对话框中选择CMSProjectTemplate，并输入项目名称然后单击OK按钮：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016282322.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016329450.png" alt="image" width="665" height="407" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在Visual Studio 2010完成了项目的创建后，我们得到如下的解决方案：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016333288.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016337682.png" alt="image" width="235" height="383" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;编译CMSTest1解决方案，我们发现，我们的CMSTest1解决方案已经被成功编译：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016343712.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016342566.png" alt="image" width="589" height="197" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;双击打开IoCFactory.cs文件，我们发现，代码中已经使用了正确的命名空间，整个解决方案的$safeprojectname$已经保持一致：&lt;/p&gt;namespace CMSTest1.Infrastructure&lt;br/&gt;{&lt;br/&gt;    public static class IoCFactory&lt;br/&gt;    {&lt;br/&gt;        public static T GetObject&amp;lt;T&amp;gt;()&lt;br/&gt;        {&lt;br/&gt;            // TODO: Implement the IoC/DI logic here.&lt;br/&gt;            return default(T);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;p&gt;至此，我们事实上已经成功地创建了一个多项目解决方案的模板，用户已经可以开始使用这个模板来新建一个类似RainbowCMS的解决方案了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Template Wizard的高级应用&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在，让我们看看Template Wizard的几个高级应用的例子以及使用中需要注意的问题。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;场景一：通过Template Wizard向CMSProjectTemplate传递自定义参数&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这个应用场景比较简单，假设我们需要通过Template Wizard向CMSProjectTemplate传递一个名为$nowyear$的参数，表示当前日期的年份，基本步骤如下：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;在RootWizardImpl的RunStarted方法中，向replacementsDictionary中添加一个$nowyear$的项，值为DateTime.Now.Year.ToString()&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;在RootWizardImpl的RunStarted方法中，同样向globalParameters中添加一个$nowyear$的项，值为DateTime.Now.Year.ToString()&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;在ChildWizardImpl的RunStarted方法中，通过RootWizardImpl从GlobalParameters中取得$nowyear$的值，并将其赋给replacementsDictionary&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;现在就可以在CMSProjectTemplate的任意地方使用$nowyear$参数，当项目被创建时，该参数会被当前日期的年份替换。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;场景二：为用户提供&amp;ldquo;创建解决方案后编译&amp;rdquo;的选项&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在CMSProjectTemplateWizard中，新建一个Windows Form，然后在这个Form上添加一个复选框，设置其文本为&amp;ldquo;Build the solution after it is created.&amp;rdquo;，表示当用户选中这个复选框时，在完成解决方案创建之后，需要Visual Studio 2010立即对该解决方案进行编译。这个Form的布局大致如下：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016358630.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016351105.png" alt="image" width="386" height="129" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;修改窗体的后台代码，添加一个BuildSolutionRequired属性，代码如下：&lt;/p&gt;public bool BuildSolutionRequired&lt;br/&gt;{&lt;br/&gt;    get { return this.chkBuild.Checked; }&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;向CMSProjectTemplateWizard项目添加EnvDTE80的引用，修改RootWizardImpl类，将其改为：&lt;/p&gt;public class RootWizardImpl : IWizard&lt;br/&gt;{&lt;br/&gt;    private bool buildSolutionRequired;&lt;br/&gt;    private string safeprojectname;&lt;br/&gt;    private EnvDTE80.DTE2 dteObject;&lt;br/&gt;&lt;br/&gt;    private static Dictionary&amp;lt;string, string&amp;gt; globalParameters = new Dictionary&amp;lt;string, string&amp;gt;();&lt;br/&gt;&lt;br/&gt;    public static IEnumerable&amp;lt;KeyValuePair&amp;lt;string, string&amp;gt;&amp;gt; GlobalParameters&lt;br/&gt;    {&lt;br/&gt;        get { return globalParameters; }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #region IWizard Members&lt;br/&gt;&lt;br/&gt;    public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem) { }&lt;br/&gt;&lt;br/&gt;    public void ProjectFinishedGenerating(EnvDTE.Project project) { }&lt;br/&gt;&lt;br/&gt;    public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem) { }&lt;br/&gt;&lt;br/&gt;    public void RunFinished()&lt;br/&gt;    {&lt;br/&gt;        EnvDTE80.Solution2 solution = (EnvDTE80.Solution2)dteObject.Solution;&lt;br/&gt;        if (buildSolutionRequired)&lt;br/&gt;            solution.SolutionBuild.Build();&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    public void RunStarted(object automationObject, &lt;br/&gt;        Dictionary&amp;lt;string, string&amp;gt; replacementsDictionary, &lt;br/&gt;        WizardRunKind runKind, object[] customParams)&lt;br/&gt;    {&lt;br/&gt;        try&lt;br/&gt;        {&lt;br/&gt;            dteObject = (automationObject as EnvDTE80.DTE2);&lt;br/&gt;            safeprojectname = replacementsDictionary["$safeprojectname$"];&lt;br/&gt;            globalParameters["$safeprojectname$"] = safeprojectname;&lt;br/&gt;            frmOptions options = new frmOptions();&lt;br/&gt;            if (options.ShowDialog() == DialogResult.OK)&lt;br/&gt;            {&lt;br/&gt;                buildSolutionRequired = options.BuildSolutionRequired;&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;        catch (Exception ex) { MessageBox.Show(ex.ToString()); }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    public bool ShouldAddProjectItem(string filePath) { return true; }&lt;br/&gt;&lt;br/&gt;    #endregion&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;重新编译CMSProjectTemplateWizard，并将其重装到GAC，然后尝试新建一个CMSProjectTemplate的项目，Visual Studio在创建项目之前会给出一个对话框，提示用户是否需要立即编译：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016388266.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201182016436691.png" alt="image" width="719" height="388" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;细心的朋友会发现，结合场景一和场景二的应用，我们就可以为用户提供一个动态参数输入的界面，而在项目模板中使用这个参数。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;场景三：动态创建解决方案文件夹（Solution Folder）&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;通常，我们都会在Template Wizard执行完成之后，动态创建解决方案文件夹（Solution Folder）。假设我们需要在解决方案中添加一个名为ReferencedProjects文件夹，我们可以在RootWizardImpl.RunFinished方法中添加如下代码：&lt;/p&gt;public void RunFinished()&lt;br/&gt;{&lt;br/&gt;    EnvDTE80.Solution2 solution = (EnvDTE80.Solution2)dteObject.Solution;&lt;br/&gt;    Project refProjectsFolderProject = solution.AddSolutionFolder("ReferencedProjects");&lt;br/&gt;}&lt;p&gt;&lt;strong&gt;场景四：在解决方案文件夹下引用已经存在的项目文件&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在场景三中，我们已经在解决方案下创建了一个ReferencedProjects文件夹，现在更进一步，将一个已存在于C:\Test目录下的C#项目文件Test.csproj添加到这个文件夹下。基于场景三中的代码，我们修改RunFinished方法如下：&lt;/p&gt;public void RunFinished()&lt;br/&gt;{&lt;br/&gt;    EnvDTE80.Solution2 solution = (EnvDTE80.Solution2)dteObject.Solution;&lt;br/&gt;    Project refProjectsFolderProject = solution.AddSolutionFolder("ReferencedProjects");&lt;br/&gt;    EnvDTE80.SolutionFolder refProjectsSolutionFolder = &lt;br/&gt;    (EnvDTE80.SolutionFolder)refProjectsFolderProject.Object;&lt;br/&gt;    string csprojFileName = @"C:\Test\Test.csproj";&lt;br/&gt;    refProjectsSolutionFolder.AddFromFile(csprojFileName);&lt;br/&gt;}&lt;p&gt;&lt;strong&gt;场景五：Project GUID问题的解决&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这个问题描述起来有点点复杂，总的来说，虽然我们可以在CMSProjectTemplate项目中，在所包含的csproj文件中将ProjectGuid节点的值设置为$guid1$等，但在最终产生的项目文件上，我们发现，Visual Studio 2010会自动重新生成一个GUID来覆盖我们所指定的这个。换句话说，即使是在RootWizardImpl.RunFinished方法中，也得不到这个最终的Project GUID。通常情况下，这不是什么大问题，因为一般我们也不太关心这个ProjectGuid究竟用什么值，因为项目之间的引用也是通过项目名称实现的。比如在我们的CMSProjectTemplate中就不存在这样的问题。然而有些第三方的项目类型或许就会使用Project GUID来实现项目引用，比如大名鼎鼎的Windows Installer XML Toolset（WiX），它就是根据Project GUID来决定其所关联的项目的，这样就出现问题了：在WiX项目的模板中，我们可以给定其引用的项目的GUID，但在最后生成的解决方案中，被引用的这个项目的GUID发生了变化，导致WiX项目无法对所需的项目进行引用，用户需要手动地重新添加项目引用，这样做就达不到自动化项目创建的目的。&lt;/p&gt;&lt;p&gt;这个问题我上网研究了很长时间，网上也没有找到合适的办法，很多国外技术社区的朋友也在一直抱怨为什么Visual Studio 2010在创建解决方案的时候需要重新产生Project GUID。最后经过我的反复试验，我找到了解决这个问题的办法。既然我们无法修改被引用项目的Project GUID，那么我们就直接在WiX项目上动手，在WiX项目中将它所设置的Project GUID替换为被引用项目的最终Project GUID。如何确定这个被引用项目的最终的Project GUID呢？只需要在解决方案资源管理器中找到这个被引用的项目，然后执行Save操作，项目的Project GUID就会被确定下来，然后再使用文本读取等手段获得这个最终的Project GUID即可。详细代码如下：&lt;/p&gt;using System;&lt;br/&gt;using System.Collections.Generic;&lt;br/&gt;using System.IO;&lt;br/&gt;using System.Windows.Forms;&lt;br/&gt;using System.Xml;&lt;br/&gt;using EnvDTE;&lt;br/&gt;using Microsoft.VisualStudio.TemplateWizard;&lt;br/&gt;&lt;br/&gt;public void RunFinished()&lt;br/&gt;{&lt;br/&gt;  // 获取Solution对象&lt;br/&gt;  EnvDTE80.Solution2 solution = (EnvDTE80.Solution2)dteObject.Solution;&lt;br/&gt;&lt;br/&gt;  Project webProject = null;&lt;br/&gt;  Project wixProject = null;&lt;br/&gt;  foreach (Project p in solution.Projects)&lt;br/&gt;  {&lt;br/&gt;      if (p.Name == string.Format("{0}.Web", safeprojectname))&lt;br/&gt;      {&lt;br/&gt;          webProject = p;&lt;br/&gt;      }&lt;br/&gt;      if (p.Name == string.Format("{0}.Wix", safeprojectname))&lt;br/&gt;      {&lt;br/&gt;          wixProject = p;&lt;br/&gt;      }&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  // 保存web项目，使得其Project GUID能够被最终确定下来.&lt;br/&gt;  webProject.Save();&lt;br/&gt;  // 保存需要修改的WiX项目，以确保&amp;ldquo;保存项目&amp;rdquo;对话框不会弹出.&lt;br/&gt;  wixProject.Save();&lt;br/&gt;&lt;br/&gt;  // 在解决方案资源管理器中定位WiX项目&lt;br/&gt;  Window solutionExplorerWindow = dteObject.ToolWindows.SolutionExplorer.Parent as Window;&lt;br/&gt;  solutionExplorerWindow.Activate();&lt;br/&gt;  UIHierarchyItem solutionHier = dteObject.ToolWindows.SolutionExplorer.UIHierarchyItems.Item(1);&lt;br/&gt;  UIHierarchyItem wixProjectHier = null;&lt;br/&gt;  foreach (UIHierarchyItem item in solutionHier.UIHierarchyItems)&lt;br/&gt;  {&lt;br/&gt;      if (item.Name == string.Format("{0}.Wix", safeprojectname))&lt;br/&gt;      {&lt;br/&gt;          wixProjectHier = item;&lt;br/&gt;          break;&lt;br/&gt;      }&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  if (wixProjectHier != null)&lt;br/&gt;  {&lt;br/&gt;      // 在解决方案资源管理器中将WiX项目选中&lt;br/&gt;      wixProjectHier.Select(vsUISelectionType.vsUISelectionTypeSelect);&lt;br/&gt;      // 将WiX项目从解决方案中卸载（Unload）&lt;br/&gt;      dteObject.ExecuteCommand("Project.UnloadProject");&lt;br/&gt;      // 调用ReplaceProjectGuid方法，修改WiX项目中对web项目&lt;br/&gt;      // 的引用Guid&lt;br/&gt;      ReplaceProjectGuid(webProject, wixProject);&lt;br/&gt;      // 稍等片刻...&lt;br/&gt;      System.Threading.Thread.Sleep(500);&lt;br/&gt;      // 重新加载WiX项目&lt;br/&gt;      dteObject.ExecuteCommand("Project.ReloadProject");&lt;br/&gt;  }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;private void ReplaceProjectGuid(Project webProject, Project wixProject)&lt;br/&gt;{&lt;br/&gt;    var webProjectFullName = webProject.FullName;&lt;br/&gt;    var webProjectText = File.ReadAllText(webProjectFullName);&lt;br/&gt;&lt;br/&gt;    int pos = webProjectText.IndexOf("&amp;lt;ProjectGuid&amp;gt;", StringComparison.InvariantCultureIgnoreCase);&lt;br/&gt;    var guid = webProjectText.Substring(pos + "&amp;lt;ProjectGuid&amp;gt;".Length, 38);&lt;br/&gt;&lt;br/&gt;    var wixProjectFullName = wixProject.FullName;&lt;br/&gt;    XmlDocument xmlDoc = new XmlDocument();&lt;br/&gt;    XmlNamespaceManager namespaceMgr = new XmlNamespaceManager(xmlDoc.NameTable);&lt;br/&gt;    namespaceMgr.AddNamespace("ns", "http://schemas.microsoft.com/developer/msbuild/2003");&lt;br/&gt;    xmlDoc.Load(wixProjectFullName);&lt;br/&gt;&lt;br/&gt;    XmlNode node = xmlDoc.SelectSingleNode("//ns:Project//ns:ItemGroup[3]//ns:ProjectReference[2]//ns:Project", namespaceMgr);&lt;br/&gt;    node.InnerText = guid;&lt;br/&gt;    &lt;br/&gt;    xmlDoc.Save(wixProjectFullName);&lt;br/&gt;}&lt;br/&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;至此，我们已经成功地借助Template Wizard创建了一个多项目解决方案的模板，我们还学习了Template Wizard的一些高级应用。但我们的CMSProjectTemplate还没有全部完成，我们还需要为其提供一个更好听的名字、更好看的图标，而且我们还希望能够通过Visual Studio 2010 Extension来实现一个安装包，以便用户能够直接安装并使用我们的模板。这部分内容我会在下一篇文章中重点介绍。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;本文案例下载&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://files.cnblogs.com/daxnet/CMSProjectTemplate.Step2.rar" target="_blank"&gt;CMSProjectTemplate&lt;/a&gt;&lt;/strong&gt;（至目前为止，未完成）&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2325928.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/01/18/2325928.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2012/01/17/2324969.html</id><title type="text">在Visual Studio 2010中创建多项目（解决方案）模板【一】</title><summary type="text">当我们使用Visual Studio来新建某个项目（Project）时，通常都会使用File –&gt; New –&gt; Project菜单来打开New Project（新建项目）对话框，里面列出了各种项目类型以供我们选择。大部分读者朋友都应该知道，这个对话框其实是列出了所有已经安装的项目模板，不仅如此，Visual Studio还允许用户通过File –&gt; Export Template菜单将现有的项目导出为项目模板。平时我们最为常见的是使用Export Template来创建单一项目的项目模板，此时使用Export Template功能就十分有效。当然，社区里也有一些工具（比如微</summary><published>2012-01-17T11:21:00Z</published><updated>2012-01-17T11:21:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2012/01/17/2324969.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2012/01/17/2324969.html"/><content type="html">&lt;p&gt;当我们使用Visual Studio来新建某个项目（Project）时，通常都会使用File &amp;ndash;&amp;gt; New &amp;ndash;&amp;gt; Project菜单来打开New Project（新建项目）对话框，里面列出了各种项目类型以供我们选择。大部分读者朋友都应该知道，这个对话框其实是列出了所有已经安装的项目模板，不仅如此，Visual Studio还允许用户通过File &amp;ndash;&amp;gt; Export Template菜单将现有的项目导出为项目模板。&lt;/p&gt;&lt;p&gt;平时我们最为常见的是使用Export Template来创建单一项目的项目模板，此时使用Export Template功能就十分有效。当然，社区里也有一些工具（比如微软官方的&lt;strong&gt;&lt;a href="http://visualstudiogallery.msdn.microsoft.com/57320b20-34a2-42e4-b97e-e615c71aca24/" target="_blank"&gt;Export Template Wizard&lt;/a&gt;&lt;/strong&gt;工具）能够帮忙创建项目模板，甚至可以直接将模板压缩包文件编译成供Visual Studio 2010使用的扩展安装程序（VSIX），这样用户就可以通过VSIX直接将项目模板安装到Visual Studio 2010中。虽然社区已经提供了各种各样的创建项目模板的方法，而且网上有关项目模板创建的文章和论坛帖子也不少，但大多数都是在讨论单一项目模板的创建。本文打算从一个案例解决方案开始，边做边讨论，看看在Visual Studio 2010中是如何创建多项目（解决方案）模板的。&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920084777.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; float: right; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920108658.png" alt="image" width="293" height="366" align="right" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;案例解决方案 &amp;ndash; RainbowCMS&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;为了演示的需要，我随手新建了一个非常简单的面向DDD的案例解决方案：RainbowCMS。这个解决方案并没有包含任何界面，也没有提供Web Service的接口，而仅仅是实现了一个非常简单的管理客户信息的应用服务。这个解决方案主要包含如下四个项目：&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;RainbowCMS.Application：此项目包含了RainbowCMS的应用服务，以及与之相关的数据传输对象&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;RainbowCMS.Domain：此项目包含了RainbowCMS的领域模型、仓储接口以及与DDD相关的一些接口。为了演示需要，我只在RainbowCMS中提供了非常简单的&amp;ldquo;客户-地址&amp;rdquo;模型&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;RainbowCMS.Domain.Repositories：此项目实现了针对&amp;ldquo;客户&amp;rdquo;聚合的仓储，它本身应该属于领域层的一个插件，所以最后实现的时候，是需要通过IoC注射到应用层的&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;RainbowCMS.Infrastructure：此项目是整个解决方案的基础结构层，包含了一些与技术相关的基础结构，比如IoCFactory&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这个解决方案所包含的项目简单地将一个面向DDD的应用程序的各个部分组织在一起（展现层除外），它仅仅用于演示Visual Studio 2010中多项目解决方案模板的创建过程，因此请读者朋友不要过分地纠结其DDD架构的实现过程。&lt;/p&gt;&lt;p&gt;现在，我们开始基于RainbowCMS创建多项目解决方案模板。在开始之前，请先确保你的机器装有&lt;strong&gt;&lt;a href="http://www.microsoft.com/download/en/details.aspx?id=21835" target="_blank"&gt;Visual Studio 2010 SDK&lt;/a&gt;&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;多项目解决方案模板的创建&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在安装了Visual Studio 2010 SDK之后，我们就可以开始创建项目模板了。在Visual Studio 2010中选择File &amp;ndash;&amp;gt; New &amp;ndash;&amp;gt;Project菜单，在弹出的New Project对话框的Installed Templates下，选择Visual C#/Extensibility节点，可以看到类似如下的模板选项：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920152655.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920212584.png" alt="image" width="597" height="369" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;现在，让我们开始使用C# Project Template项目来创建多项目解决方案模板。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;新建模板项目&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在上面的New Project对话框中，选择C# Project Template，在Name文本框中输入项目名称：CMSProjectTemplate，然后单击OK按钮。此时将创建一个名为CMSProjectTemplate的解决方案：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920223009.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920237926.png" alt="image" width="269" height="166" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在该解决方案中真正有意义的部分是CMSProjectTemplate.vstemplate文件，CMSProjectTemplate.ico则是今后用于显示在New Project对话框中的图标；而其它的所有文件都是用来表示一个完整的C#项目的相关文件。由于现在我们是在创建多项目解决方案的模板，所以其它的这部分文件（包括AssemblyInfo.cs、Class1.cs以及ProjectTemplate.csproj）都可以删掉。于是，我们的CMSProjectTemplate解决方案就变成了如下的结构：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920239256.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920263385.png" alt="image" width="248" height="119" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;更改CMSProjectTemplate.vstemplate文件&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;既然我们已经在解决方案中删掉了一部分内容，那么CMSProjectTemplate.vstemplate文件也要作相应的修改。首先，找到VSTemplate节点的Type属性，将其改为ProjectGroup，这一点很重要，否则今后产生的项目将不会被添加到解决方案资源管理器中；其次，将TemplateContent下所有的内容删掉，并添加一个ProjectCollection节点。更改以后的CMSProjectTemplate.vstemplate内容如下：&lt;/p&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;br/&gt;&amp;lt;VSTemplate Version="3.0.0" Type="ProjectGroup" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005"&amp;gt;&lt;br/&gt;  &amp;lt;TemplateData&amp;gt;&lt;br/&gt;    &amp;lt;Name&amp;gt;CMSProjectTemplate&amp;lt;/Name&amp;gt;&lt;br/&gt;    &amp;lt;Description&amp;gt;&amp;amp;lt;No description available&amp;amp;gt;&amp;lt;/Description&amp;gt;&lt;br/&gt;    &amp;lt;Icon&amp;gt;CMSProjectTemplate.ico&amp;lt;/Icon&amp;gt;&lt;br/&gt;    &amp;lt;ProjectType&amp;gt;CSharp&amp;lt;/ProjectType&amp;gt;&lt;br/&gt;    &amp;lt;RequiredFrameworkVersion&amp;gt;2.0&amp;lt;/RequiredFrameworkVersion&amp;gt;&lt;br/&gt;    &amp;lt;SortOrder&amp;gt;1000&amp;lt;/SortOrder&amp;gt;&lt;br/&gt;    &amp;lt;TemplateID&amp;gt;36e8d4bf-48bc-42c9-91ab-e45348393288&amp;lt;/TemplateID&amp;gt;&lt;br/&gt;    &amp;lt;CreateNewFolder&amp;gt;true&amp;lt;/CreateNewFolder&amp;gt;&lt;br/&gt;    &amp;lt;DefaultName&amp;gt;CMSProjectTemplate&amp;lt;/DefaultName&amp;gt;&lt;br/&gt;    &amp;lt;ProvideDefaultName&amp;gt;true&amp;lt;/ProvideDefaultName&amp;gt;&lt;br/&gt;  &amp;lt;/TemplateData&amp;gt;&lt;br/&gt;  &amp;lt;TemplateContent&amp;gt;&lt;br/&gt;    &amp;lt;ProjectCollection&amp;gt;&lt;br/&gt;    &amp;lt;/ProjectCollection&amp;gt;&lt;br/&gt;  &amp;lt;/TemplateContent&amp;gt;&lt;br/&gt;&amp;lt;/VSTemplate&amp;gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;导出项目模板并添加到CMSProjectTemplate解决方案&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;打开RainbowCMS解决方案，然后选择File &amp;ndash;&amp;gt; Export Template菜单，此时将打开Export Template Wizard对话框，在对话框中选择Project Template，表示我们要将所选的项目导出为项目模板，然后在下部的下拉框中，选择需要导出为模板的项目。在这里我们选择RainbowCMS.Application，然后单击Next按钮。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920329411.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920383277.png" alt="image" width="518" height="409" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;在Select Template Options页中，设置与模板有关的参数，比如模板名称和描述等，在此我们都使用默认参数，注意将Automatically import the template into Visual Studio复选框去掉，表示不需要将产生的模板直接应用到Visual Studio中。在单击Finish后，向导会直接将导出模板所在的目录以文件管理器的方式打开。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920501465.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920539707.png" alt="image" width="518" height="409" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;将产生的RainbowCMS.Application.zip文件解压到当前文件夹备用。&lt;/p&gt;&lt;p&gt;回到CMSProjectTemplate项目，在项目中创建一个CMSTemplate的目录，并在该目录下创建Application目录，通过复制/粘贴的方式将RainbowCMS.Application.zip中解压出来的文件添加到CMSProjectTemplate项目的CMSTemplate/Application目录下，之后按以下步骤操作：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;将所有*.cs文件的编译方式改为None，否则将出现编译错误&lt;br /&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920545463.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920555364.png" alt="image" width="400" height="137" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;将RainbowCMS.Application.csproj重命名为Application.csproj，并在MyTemplate.vstemplate中，将第一个Project节点的File属性改为Application.csproj，TargetFileName属性改为$safeprojectname$.Application.csproj。注意：这里的$safeprojectname$是项目模板的一个内置的宏，表示一个&amp;ldquo;安全&amp;rdquo;的项目名称（也就是由Visual Studio处理过的，用户在New Project对话框中输入的那个项目名称）。此外，对于单一项目模板而言，直接使用这个$safeprojectname$是没有问题的，但如果是多项目解决方案的模板，那么要在各个项目中使用这个宏，就需要一些额外操作，这部分内容会在后面介绍。有关项目模板所使用的宏，请参见：&lt;strong&gt;&lt;a href="http://msdn.microsoft.com/zh-cn/library/eehb4faa.aspx" target="_blank"&gt;模板参数&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;将CMSTemplate\Application目录下Application.csproj以及所有C#文件中的RainbowCMS改为$safeprojectname$宏&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;修改CMSProjectTemplate.vstemplate文件，在ProjectCollection节点中添加如下代码：&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&amp;lt;ProjectTemplateLink ProjectName="$safeprojectname$.Application"&amp;gt;&lt;br/&gt;  CMSTemplate\Application\MyTemplate.vstemplate&lt;br/&gt;&amp;lt;/ProjectTemplateLink&amp;gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;在Application.csproj文件中修改RootNamespace和AssemblyName，将其改为$safeprojectname$.Application&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;经过上面的修改，CMSProjectTemplate的解决方案内容如下：&lt;br /&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920568787.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171920597934.png" alt="image" width="271" height="310" border="0" /&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;span style="font-family: Segoe UI; font-size: x-small;"&gt;用以上相同的步骤，将RainbowCMS.Domain、RainbowCMS.Domain.Repositories以及RainbowCMS.Infrastructure项目导出成模板并添加到CMSProjectTemplate解决方案。在完成了这一系列操作之后，CMSProjectTemplate解决方案如下所示：&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171921019720.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171921046010.png" alt="image" width="267" height="203" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;至此，我们已经将所需要的项目模板加入到了CMSProjectTemplate中，直接编译整个解决方案，就会在输出目录中出现一个ZIP文件，它就是Visual Studio的项目模板文件。将这个ZIP文件复制到&lt;em&gt;&amp;lt;User_Documents&amp;gt;&lt;/em&gt;\Visual Studio 2010\Templates\ProjectTemplates\Visual C#目录下，然后在Visual Studio中使用File &amp;ndash;&amp;gt; New &amp;ndash;&amp;gt; Project菜单打开New Project对话框，我们就可以在Visual C#的类别下找到CMSProjectTemplate的项目模板：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/20120117192107729.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171921117268.png" alt="image" width="537" height="282" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;选中这个模板，然后为我们新建的项目起个名字，比如CMSTest1，然后单击OK按钮，我们可以看到，一个新的解决方案被创建了，它有着下面的结构：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171921127693.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201201/201201171921135576.png" alt="image" width="242" height="392" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;与我们本文开始的RainbowCMS解决方案相比，除了项目名字不同（此时是CMSTest1）以外，其它的结构完全相同。双击IoCFactory.cs文件将其打开，我们发现，IoCFactory类所在的命名空间有误，我们希望的是CMSTest1.Infrastructure，而产生的代码里却是CMSTest1.Infrastructure.Infrastructure：&lt;/p&gt;namespace CMSTest1.Infrastructure.Infrastructure&lt;br/&gt;{&lt;br/&gt;    public static class IoCFactory&lt;br/&gt;    {&lt;br/&gt;        public static T GetObject&amp;lt;T&amp;gt;()&lt;br/&gt;        {&lt;br/&gt;            // TODO: Implement the IoC/DI logic here.&lt;br/&gt;            return default(T);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;打开CMSProjectTemplate项目下的CMSTemplate\Infrastructure.IoCFactory.cs文件，内容如下：&lt;/p&gt;namespace $safeprojectname$.Infrastructure&lt;br/&gt;{&lt;br/&gt;    public static class IoCFactory&lt;br/&gt;    {&lt;br/&gt;        public static T GetObject&amp;lt;T&amp;gt;()&lt;br/&gt;        {&lt;br/&gt;            // TODO: Implement the IoC/DI logic here.&lt;br/&gt;            return default(T);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;看来在这里$safeprojectname$指代的是CMSTest1.Infrastructure，而不是CMSTest1。通常，我们希望$safeprojectname$在解决方案的各个项目中，都是指代用户输入的项目名称（即CMSTest1），而不是每个项目各取不同的值。这样做其实很重要：比如在指定一个项目对另一个项目的引用时，例如：CMSTest1.Domain如果需要引用CMSTest1.Infrastructure，那么就需要在Domain.csproj中的项目引用部分加入$safeprojectname$.Infrastructure，然而如果$safeprojectname$指代的是CMSTest1.Domain的话，你就无法在Domain.csproj中加入这一引用，因为它会被替换成CMSTest1.Domain.Infrastructure。要解决这个问题，我们需要使用Template Wizard。我将在下一篇文章中详细介绍Template Wizard的使用。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;本文案例下载&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://files.cnblogs.com/daxnet/RainbowCMS.rar" target="_blank"&gt;RainbowCMS解决方案&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://files.cnblogs.com/daxnet/CMSProjectTemplate.Step1.rar" target="_blank"&gt;CMSProjectTemplate解决方案&lt;/a&gt;&lt;/strong&gt;（至目前为止，未完成）&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2324969.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2012/01/17/2324969.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2011/12/24/2300169.html</id><title type="text">【领域驱动设计】浅谈聚合的划分与设计</title><summary type="text">聚合以及聚合根是领域驱动设计中的重要概念，根据定义，聚合是针对数据变化可以考虑成一个单元的一组相关的对象。聚合使用边界将内部和外部的对象划分开来。每个聚合有一个根。这个根是一个实体，并且它是外部可以访问的唯一的对象。根可以保持对任意聚合对象的引用，并且其他的对象可以持有任意其他的对象，但一个外部对象只能持有根对象的引用。如果边界内有其他的实体，那些实体的标识符是本地化的，只在聚合内有意义（参见《领域驱动设计-精简版》第42页）。从定义上看，貌似针对特定上下文的领域模型来讲，聚合的划分与设计并不那么困难，但事实却并非如此。在本文中，我将大致总结一下自己的经验，同时也欢迎关注领域驱动设计的朋友能够</summary><published>2011-12-24T02:00:00Z</published><updated>2011-12-24T02:00:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2011/12/24/2300169.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2011/12/24/2300169.html"/><content type="html">&lt;p&gt;聚合以及聚合根是领域驱动设计中的重要概念，根据定义，聚合是针对数据变化可以考虑成一个单元的一组相关的对象。聚合使用边界将内部和外部的对象划分开来。每个聚合有一个根。这个根是一个实体，并且它是外部可以访问的唯一的对象。根可以保持对任意聚合对象的引用，并且其他的对象可以持有任意其他的对象，但一个外部对象只能持有根对象的引用。如果边界内有其他的实体，那些实体的标识符是本地化的，只在聚合内有意义（参见《&lt;strong&gt;&lt;a href="http://www.infoq.com/cn/minibooks/domain-driven-design-quickly" target="_blank"&gt;领域驱动设计-精简版&lt;/a&gt;&lt;/strong&gt;》第42页）。从定义上看，貌似针对特定上下文的领域模型来讲，聚合的划分与设计并不那么困难，但事实却并非如此。在本文中，我将大致总结一下自己的经验，同时也欢迎关注领域驱动设计的朋友能够提出自己的见解。&lt;/p&gt;&lt;p&gt;几周前我与园友&lt;strong&gt;&lt;a href="http://www.cnblogs.com/netfocus/" target="_blank"&gt;netfocus&lt;/a&gt;&lt;/strong&gt;讨论过这个问题，在他的&lt;strong&gt;&lt;a href="http://www.cnblogs.com/netfocus/archive/2011/12/03/2274680.html" target="_blank"&gt;这篇博客&lt;/a&gt;&lt;/strong&gt;中，简单地提及了为何我们需要在领域驱动设计中引入&amp;ldquo;聚合&amp;rdquo;的概念。我们围绕着&lt;span style="background-color: #ffffff;"&gt;&lt;a href="http://files.cnblogs.com/daxnet/Vernon_2011_1.pdf" target="_blank"&gt;&lt;strong&gt;《Effective Aggregate Design Part I: Modeling a Single Aggregate》&lt;/strong&gt;&lt;/a&gt;一文&lt;/span&gt;中的一些观点对聚合的划分与设计展开讨论，文中对聚合的设计问题作了讨论，比如：如果将聚合设计得很大，则会带来一些诸如一致性和事务失效（transaction failure）的问题；不仅如此，大聚合会导致系统性能低下，即使采用延迟加载（Lazy Loading）也无法彻底解决性能问题，例如当更新某个聚合中实体集合中的某个实体时，会连同整个集合被同时加载到内存中，而事实上这是不必要的。之后，文中提出将大聚合拆分成多个小聚合，通过引入领域服务（Domain Service），并使用实体标识（Entity Identifier）来替代对象引用（reference）以避免大聚合带来的并发问题，因此可以得出结论，就是聚合不能设计得很大结构很复杂。然而应该如何合理地去设计聚合、划分聚合边界呢？这就是本文所要讨论的问题。&lt;/p&gt;&lt;p&gt;事实上，我对这篇文章的分析问题的方式不太认同，虽然最后的问题都归结到聚合的划分与设计上。聚合的划分与设计问题，我觉得不应该从技术的角度去分析引出，而应该从领域模型和通用语言的角度去概括，虽然文章中所阐述的问题都是现实存在的，但我觉得如果能够从模型的角度来分析问题，或许能够得到更好的结果。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;聚合划分与设计其实与并发和事务性并不矛盾&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;首先需要了解的是，合理地划分和设计聚合，并不会产生任何并发和事务性问题。我们所讨论的文章中之所以第一个设计方案会出现并发和事务性问题，就是因为它的聚合设计本身就不合理。这其实在本文一开始就明确了这个问题：聚合是针对数据变化可以考虑成一个单元的一组相关的对象。因此，必须承认对于一个聚合，其中包含的所有对象必须&amp;ldquo;同生死，共存亡&amp;rdquo;，基于聚合的数据操作应该就是原子操作，基础结构机制需要保证以聚合为单位的数据一致性。换句话说，聚合在数据一致性方面的表现，应该与基础结构机制所保证的并发和事务的正确性是等价的。数据访问时出现的事务失效现象，其实是源于聚合的不合理划分。比如，在《Effective Aggregate Design》一文中的例子里，事实上Product并不一定要依赖于Release才能存在，因此，在Product的聚合中，就不应该包含对Release的引用，然而相反，Release是没法脱离Product而单独存在的，因为如果是这样的话，Release也就失去了本身的含义，所以，Release可以定义成一个聚合，而Product则是这个聚合中的一个实体。&lt;/p&gt;&lt;p&gt;至此，我们可以得知，聚合的划分和设计必须依赖对通用语言、领域概念和模型的正确把握。接下来再让我们看两个我们经常遇到的例子：销售订单和论坛主题。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;两个例子：销售订单（Sales Order）/订单明细（Sales Line） vs. 论坛主题（Post）/回复（Reply）&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;很多网友会在这两个领域的建模上感到纠结，如果我们从数据库设计上考虑（以数据库驱动的开发方式进行思考），两者非常相似，都是主从表结构，都是1对多（1:N）的关系：一个销售订单对应多条订单明细，一个论坛主题对应多条回复。但如果我们用领域驱动的思想来考虑这个问题，我们会发现，这是两个截然不同的例子！两个例子中实体之间的关系完全不同。&lt;/p&gt;&lt;p&gt;首先分析销售订单（Sales Order）/订单明细（Sales Line）：对于一张销售订单来说，订单明细是不可缺少的，否则就不成其为销售订单。试想，一张订单没有包含任何购买的货品信息，这意味着什么？因此，销售订单和订单明细之间的关系是一种固定的不可变（invariant）的关系，就像《领域驱动设计》一书中所讲的汽车与车轮之间的关系那样，汽车少了轮子就不成其为汽车了。反过来看，订单明细也离不开销售订单，这很简单，因为很明细订单明细是描述销售订单的一个不可或缺的部分。于是，在这个例子中，我们有一个聚合根为销售订单，其中包含一条或多条订单明细的聚合，聚合及其实体间的关系可以用下图表示：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201112/201112241000163642.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201112/201112241000166739.png" alt="image" width="451" height="176" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;对于论坛主题（Post）/回复（Reply）之间的关系，情况却完全不同。论坛的主题是可以脱离回复单独存在的（一个主题可以没有任何人对其进行回复），而回复却不能脱离主题（没有主题的回复是没有意义的）。鉴于这样的事实，实际上在主题与回复这部分模型中，存在两个聚合：第一个聚合是以主题（Post）为聚合根，且仅包含其本身一个对象的聚合；另一个聚合是以回复（Reply）为聚合根，其中包含了对主题（Post）的引用的聚合。其关系可以如下表示：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/daxnet/201112/201112241000175377.png"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="image" src="http://images.cnblogs.com/cnblogs_com/daxnet/201112/201112241000175966.png" alt="image" width="347" height="276" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;这样的设计，会让有些朋友感到不适应，原因是我们无法直接从Post实体获得其下所有的Reply实体，那么对于&amp;ldquo;通过给定的Post，获得与它相关的所有Reply信息&amp;rdquo;这样的用例，在实现上就不那么直接。此时，我们需要在应用层，通过Reply的仓储来获得，比如：&lt;/p&gt;public IEnumerable&amp;lt;ReplyDataObject&amp;gt; GetRepliesForPost(Guid postId)&lt;br/&gt;{&lt;br/&gt;    using (IRepositoryContext context = IoCFactory.GetService&amp;lt;IRepositoryContext&amp;gt;();&lt;br/&gt;    {&lt;br/&gt;        ISpecification&amp;lt;Reply&amp;gt; spec = Specification&amp;lt;Reply&amp;gt;.Eval(r =&amp;gt; r.Post.Id == postId);&lt;br/&gt;        IRepository&amp;lt;Reply&amp;gt; replyRepository = context.GetRepository&amp;lt;Reply&amp;gt;();&lt;br/&gt;        IEnumerable&amp;lt;Reply&amp;gt; replies = replyRepository.FindAll(spec);&lt;br/&gt;        List&amp;lt;ReplyDataObject&amp;gt; result = new List&amp;lt;ReplyDataObject&amp;gt;();&lt;br/&gt;        if (replies != null)&lt;br/&gt;        {&lt;br/&gt;                replies.ToList().ForEach(r =&amp;gt; result.Add(DataObjectMapper.MapToDataObject(r));&lt;br/&gt;        }&lt;br/&gt;        return result;&lt;br/&gt;    }&lt;br/&gt;}&lt;p&gt;这部分内容牵涉到了应用层，或许你会觉得，这样做是不是把业务逻辑迁移到了应用层，导致领域模型失血。其实不然，在这里，应用层并没有参与任何业务逻辑，从仓储读取领域对象以及将领域对象转换成数据传输对象（DTO），这些并不属于业务逻辑的范畴：因为从领域模型和业务逻辑的角度看，它们并不能知道什么是仓储、什么是规约、什么是数据传输对象。应用层在这里起到了任务协调、数据转换等作用。不仅如此，应用层甚至还可以包含业务规则引擎以及工作流的实现（workflow）。这部分内容我将在后续的博文中介绍。&lt;/p&gt;&lt;p&gt;本文简要介绍了一些有关聚合设计与划分的思想，有兴趣的朋友可以继续进行深入思考，也欢迎大家提出各自的见解与想法，一起讨论。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2300169.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2011/12/24/2300169.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/daxnet/archive/2011/11/25/2262764.html</id><title type="text">应网友邀请参加了2011年度51CTO的IT博客大赛</title><summary type="text">今应博客园网友sula的邀请，参加了2011年度51CTO的IT博客大赛，如果有朋友对我的博客感兴趣的话，欢迎点击此处为我投票，我也将再接再厉，把更优秀精彩的内容展现给读者朋友。</summary><published>2011-11-25T02:12:00Z</published><updated>2011-11-25T02:12:00Z</updated><author><name>dax.net</name><uri>http://www.cnblogs.com/daxnet/</uri></author><link rel="alternate" href="http://www.cnblogs.com/daxnet/archive/2011/11/25/2262764.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/daxnet/archive/2011/11/25/2262764.html"/><content type="html">&lt;p&gt;今应博客园网友&lt;strong&gt;&lt;a href="http://home.cnblogs.com/u/129522/" target="_blank"&gt;sula&lt;/a&gt;&lt;/strong&gt;的邀请，参加了2011年度51CTO的IT博客大赛，如果有朋友对我的博客感兴趣的话，欢迎&lt;strong&gt;&lt;a href="http://blog.51cto.com/contest2011/3996721" target="_blank"&gt;点击此处&lt;/a&gt;&lt;/strong&gt;为我投票，我也将再接再厉，把更优秀精彩的内容展现给读者朋友。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/daxnet/aggbug/2262764.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/daxnet/archive/2011/11/25/2262764.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
