<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>ASP.NET - 老赵点滴 - 追求编程之美</title>
    <link>http://blog.zhaojie.me/asp-net/</link>
    <description>先做人，再做技术人员，最后做程序员。打造国内最好的.NET技术博客。</description>
    <language>zh-cn</language>
    <managingEditor>jeffz@live.com (老赵)</managingEditor>
    <webMaster>jeffz@live.com (老赵)</webMaster>
    <pubDate>Wed, 30 Sep 2009 02:44:00 GMT</pubDate>
    <lastBuildDate>Wed, 30 Sep 2009 02:44:00 GMT</lastBuildDate>
    <ttl>60</ttl>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/translation/">翻译引进</category>
      <title>Padding Oracle Attack实例分析</title>
      <link>http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html</link>
      <guid>http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html</guid>
      <description>&lt;p&gt;在之前的《&lt;a href="http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html"&gt;浅谈&lt;/a&gt;》一文中，我提到《&lt;a href="http://www.gdssecurity.com/l/b/2010/09/14/automated-padding-oracle-attacks-with-padbuster/"&gt;Automated Padding Oracle Attacks with PadBuster&lt;/a&gt;》一文对理解Padding Oracle Attack非常有帮助，并打算将其翻译出来。现在我便来实现承诺了。《Automated》一文其实是在介绍PadBuster这个自动攻击工具，不过其中也通过实例加配图详细介绍了Padding Oracle Attack的原理——这也是我会翻译的部分。这篇文章写的非常通俗易懂，您只需要了解一点点关于加密的基础概念即可，不需要对加密算法或其证明有任何了解。我想只要配合些许Wikipedia上的定义，大部分朋友应该都能顺利地理解这篇文章。&lt;/p&gt;

&lt;p&gt;以下为翻译内容。&lt;/p&gt;

&lt;p&gt;最近出现了许多有关Padding Oracle Attack的声音，在今年夏天早些时候的BlakHat Europe会议上，&lt;a href="http://netifera.com/research/"&gt;Juliano Rizzo和Thai Duong&lt;/a&gt;在他们的演讲中演示了这种攻击方式。虽然Padding Oracle是种相对容易的攻击方式，但如果您还没有对它的自动攻击原理有一定了解，那么利用它进行攻击还是需要不少时间的。由于缺少好用的工具以识别及利用Padding Oracles，我们开发了一个基于Padding Oracle的内部脚本，PadBuster，现在我们打算将它与社区分享。&lt;a href="https://www.gdssecurity.com/l/t/d.php?k=PadBuster"&gt;您可以在这里下载工具&lt;/a&gt;，现在我们也会花些时间来讨论这个工具的工作方式，以及它所支持的几种场景。&lt;/p&gt;

&lt;h1&gt;一些背景知识&lt;/h1&gt;

&lt;p&gt;在讨论PadBuster之前，我们先来简单讨论一下典型的Padding Oracle Attack基础。故名思义，Padding Oracle Attack背后的关键性概念便是加/解密时的填充（Padding）。明文信息可以是任意长度，但是块状加密算法需要所有的信息都由一定数量的数据块组成。为了满足这样的需求，便需要对明文进行填充，这样便可以将它分割为完整的数据块。&lt;/p&gt;

&lt;p&gt;加密时可以使用多种填充规则，但最常见的填充方式之一是在PKCS#5标准中定义的规则。PCKS#5的填充方式为：明文的最后一个数据块包含N个字节的填充数据（N取决于明文最后一块的数据长度）。下图是一些示例，展示了不同长度的单词（FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT）以及它们使用PKCS#5填充后的结果（每个数据块为8字节长）。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig1.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig1.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;请注意，每个字符串都至少有1个字节的填充数据，因此7字节的值（如AVOCADO）则使用0x01进行填充，而8字节的值（如PLANTAIN）则会填充一个额外的数据块。填充字节的值也说明了填充的字节数，因此待加密数据的最后几个字节必须是以下几种情况之一：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个0x01（0x01） &lt;/li&gt;

  &lt;li&gt;两个0x02（0x02，0x02） &lt;/li&gt;

  &lt;li&gt;三个0x03（0x03，0x03，0x03） &lt;/li&gt;

  &lt;li&gt;四个0x04（0x04，0x04，0x04，0x04） &lt;/li&gt;

  &lt;li&gt;…… &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果解密后的最后一个数据块末尾并非这些合法的字节序列，大部分加/解密程序都会抛出一个填充异常。这个异常对于攻击者尤为关键，它是Padding Oracle Attack的基础。&lt;/p&gt;

&lt;h1&gt;一个基本的Padding Oracle Attack场景&lt;/h1&gt;

&lt;p&gt;作为一个具体例子，请考虑以下场景：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;某个应用程序使用Query String参数来传递一个用户加密后的用户名，公司ID及角色ID。参数使用CBC模式加密，每次都使用不同的初始化向量（IV，Initialization Vector）并添加在密文前段。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;当应用程序接受到加密后的值以后，它将返回三种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;接受到正确的密文之后（填充正确且包含合法的值），应用程序正常返回（200 - OK）。 &lt;/li&gt;

  &lt;li&gt;接受到非法的密文之后（解密后发现填充不正确），应用程序抛出一个解密异常（500 - Internal Server Error）。 &lt;/li&gt;

  &lt;li&gt;接受到合法的密文（填充正确）但解密后得到一个非法的值，应用程序显示自定义错误消息（200 - OK）。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述的场景体现了一个典型的Padding Oracle（填充提示），我们可以利用应用程序的行为轻易了解某个加密的值是否填充正确。这里的单词Oracle代表了一种机制，用于了解某个测试是否通过。&lt;/p&gt;

&lt;p&gt;既然已经给出了场景，那么我们便来查看应用程序所使用的一个加密后的参数。这个参数保存了使用分号隔离的一系列值，在我们的示例中，则是用户名（BRIAN），公司ID（12）及角色ID（12）：因此这里的明文是“&lt;strong&gt;BRIAN;12;2;&lt;/strong&gt;”。以下则是经过加密的Query String实例，请注意加密后的UID参数使用了ASCII十六进制表示法。&lt;/p&gt;

&lt;pre class="code"&gt;http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6&lt;/pre&gt;

&lt;p&gt;在实际情况中，攻击者并不会知道这里所对应的明文是多少，不过作为示例，我们已经知道了明文、填充、以及加密后的值（如下表）。正如之前所提到的那样，IV添加在密文的前段，即最前面8个字节。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig2.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig2.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;攻击者可以根据加密后值的长度来推测出数据块的大小。由于长度（这里是24）能被8整除但不能被16整除，因此可以得知数据块的大小是8个字节。现在我们来观察下加密和解密的内部实现，下图便展示了字节级别的运算方式，这对以后攻击方式的讨论很有帮助。请注意，其中带圆圈的加号表示XOR（异或）操作。&lt;/p&gt;

&lt;p&gt;加密过程：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig3.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig3.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;解密过程：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig4.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig4.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;同样值得指出的是，解密之后的最后一个数据块，其结尾应该包含正确的填充序列。如果这点没有满足，那么加/解密程序就会抛出一个填充异常。&lt;/p&gt;

&lt;h1&gt;利用Padding Oracle进行解密&lt;/h1&gt;

&lt;p&gt;我们现在来关注一下如何利用Padding Oracle Attack进行解密。我们将每次操作一个单独的加密块，因此我们可以独立出第一块密文（IV后的那块），在前面加上全为NULL的IV值，并发送至应用程序。以下是URL极其相关回复：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=0000000000000000F851D6CC68FC9537
Response: 500 - Internal Server Error&lt;/pre&gt;

&lt;p&gt;回复的500错误是意料之中的，因为这个值在解密后完全非法。下图展示了应用程序在尝试解密的时候究竟做了哪些事情。您会发现，因为我们只处理单个数据块，因此它的结尾必须包含正确的填充字节，才能避免出现非法填充异常。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig5.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig5.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;如上图所示，在解密之后，数据块的末尾并没有包含正确的填充序列，因此出现了异常。现在我们将IV加一，并发送同样的密文，看看会发生什么：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=0000000000000001F851D6CC68FC9537
Response: 500 - Internal Server Error&lt;/pre&gt;

&lt;p&gt;与之前一样，我们得到了500异常。这是因为在解密后我们还是没有获得合法的填充序列。稍有不同的是，我们在深入内部之后会发现，最后一个字节的值会有所变化（变成了0x3C而不是0x3D）。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig6.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig6.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;如果我们重复发送这样的请求，每次将IV的最后一个字节加一（直至0xFF），那么最终我们将会产生一个合法的单字节填充序列（0x01）。对于可能的256个值中，只有一个值会产生正确的填充字节0x01。遇上这个值的时候，你应该得到一个不同于其他255个请求的回复结果：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=000000000000003CF851D6CC68FC9537
Response: 200 OK&lt;/pre&gt;

&lt;p&gt;同样，我们从示意图中了解一下此时发生了什么：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig7.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig7.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;在这个情况下，我们便可以推断出中间值（Intermediary Value）的最后一个字节，因为我们知道它和0x3C异或后的结果为0x01，于是：&lt;/p&gt;

&lt;pre class="code"&gt;因为 [Intermediary Byte] ^ 0×3C == 0×01, 
得到 [Intermediary Byte] == 0×3C ^ 0×01, 
所以 [Intermediary Byte] == 0×3D&lt;/pre&gt;

&lt;p&gt;现在我们可以更进一步。我们已经知道了中间值的最后一个字节，于是我们可以推断出解密后的值是多少。您可以回忆一下，在解密的过程中，中间值的每个字节都会与密文中的前一个数据块（对于第一个数据块来说便是IV）的对应字节进行异或操作，于是我们使用之前示例中原来的IV中的最后一个字节（0x0F），与中间值异或一下便可以得到明文。不出意料，我们会得到0x32，这表示数字“2”（明文中第一个数据块的最后一个字节）。&lt;/p&gt;

&lt;p&gt;我们现在已经破解了示例数据块中的第8个字节，是时候关注第7个字节了。在破解第8个字节时，我们使用暴力枚举IV，让解密后的最后一个字节成为0x01（合法填充）。在破解第7个字节的时候，我们要做的事情也差不多，不过此时要求第7个字节与第8个字节都为0x02（再重复一遍，这表示合法的填充）。我们已经知道，中间值的最后一个字节是0x3D，因此我们可以将IV中的第8个字节设为0x3F（这会产生0x02）并暴力枚举IV的第七个字节（从0x00开始，直至0xFF）。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig8.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig8.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;我们再次遭遇填充异常，直至遇上某个值，它使得解密后的第7个字节成为0x02（正确填充），此时IV中的字节为0x24：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig9.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig9.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;使用这种技巧，我们可以从后往前破解中间值里的每个字节，最终得到解密后的值（尽管每次一个字节）。下图展示了完全破解后的IV值，此时整个数据块都为填充值（0x08）：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig10.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig10.png" width="450" /&gt;&lt;/a&gt; 

&lt;h1&gt;使用PadBuster进行解密&lt;/h1&gt;

&lt;p&gt;（译注：这段内容为PadBuster的使用指南，在此略过，如果您对这部分内容感兴趣可以阅读原文。）&lt;/p&gt;

&lt;h1&gt;加密任意的值&lt;/h1&gt;

&lt;p&gt;我们已经知道如何利用Padding Oracle和PadBuster来依次破解每个加密的数据块。现在，我们就来观察下如何使用同样的漏洞来加密任意数据。&lt;/p&gt;

&lt;p&gt;可能您已经发现，一旦我们可以推断出密文数据块的中间值，我们便能通过操作IV的值来完全控制解密所得到的结果。例如，在前面的示例中，如果想要将密文中第一个数据块解密为“TEST”这个值，您可以计算出它所需要的IV值，只要将目标明文与中间值进行异或操作即可。因此，只要您将字符串“TEST”（自然，还包括四个0x04字节作为填充）与中间值异或之后，便可以得到最终的IV，即0×6D，0×36，0×70，0×76，0×03，0×6E，0×22，0×39：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig11.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig11.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;这种做法对于单个数据块来说自然没有问题，但如果我们想要用它来生成长度超过一个数据块的值又该怎么办呢？我们来看一个简单通俗的实际案例。这次我们要生成一个加密的字符串“ENCRYPT TEST”而不仅仅是“TEST”。第一步，还是将文本分拆成数据块，并补上必须的填充字节，如下图：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig12.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig12.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;在构造超过一个数据块的值时，我们实际上是从最后一个数据块开始，向前依次生成所需的密文。在这里，最后的数据块与之前的相同，因此我们已经知道以下的IV和密文能够生成字符串“TEST”：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=6D367076036E2239F851D6CC68FC9537&lt;/pre&gt;

&lt;p&gt;接下来，我们需要弄明白中间值6D367076036E2239在作为密文，而不是IV传递至应用程序时会被如何解密。在这里只要使用与破解过程相同的技巧就行了，我们把它作为密文传递给应用程序，并从全部为NULL的IV开始进行暴力破解：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=00000000000000006D367076036E2239&lt;/pre&gt;

&lt;p&gt;一旦我们通过暴力破解得到中间值之后，IV便可以用来生成我们想要的任意值。新的IV可以被放在前一个示例的前面，这样便可以得到一个符合我们要求的，包含两个数据块的密文了。这个过程可以不断重复，这样便能生成任意长度的数据了。&lt;/p&gt;

&lt;h1&gt;使用PadBuster加密任意的值&lt;/h1&gt;

&lt;p&gt;（译注：这段内容为PadBuster的使用指南，在此略过，如果您对这部分内容感兴趣可以阅读原文。）&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html#comments</comments>
      <pubDate>Fri, 08 Oct 2010 16:20:05 GMT</pubDate>
      <lastBuildDate>Fri, 08 Oct 2010 16:20:05 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>浅谈这次ASP.NET的Padding Oracle Attack相关内容</title>
      <link>http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html</link>
      <guid>http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html</guid>
      <description>&lt;p&gt;上一周爆出了一个关于ASP.NET的安全漏洞，有关这个漏洞的第一篇文章应该是&lt;a href="http://weblogs.asp.net/scottgu/archive/2010/09/18/important-asp-net-security-vulnerability.aspx"&gt;ScottGu的说明&lt;/a&gt;，但是其中各方面谈的也是语焉不详。由于这个漏洞关系到“安全”这样敏感的话题，其中又涉及到密码学这样常人看不明白的技术，于是导致了各种猜测和推测，其中甚至与我对ASP.NET的了解所有矛盾，因此我觉得也大都不靠谱。中秋休息在家，我简单地了解了一下与这个漏洞有关的内容，总结出了一些“能够说服自己”的内容，在此记录下来。因此，这篇文章的面向读者是那些和我差不多的同学：对ASP.NET有所了解，但对密码学知之甚少。&lt;/p&gt;

&lt;h1&gt;什么是Padding和Oracle&lt;/h1&gt;

&lt;p&gt;要谈这个问题，先要了解什么是Padding Oracle Attack。有些文章把Padding和Oracle，与CSS样式表或是那个收购了Sun的甲骨文公司联系起来，这就驴唇不对马嘴了。&lt;/p&gt;

&lt;p&gt;Padding在这里的含义是“填充”，因为对于加密算法来说，它们是基于等长的“数据块”进行操作的（如对于RC2，DES或TripleDES算法来说这个长度是8字节，而对于Rijndael算法来说则是16、24或32字节）。但是，我们的输入数据长度是不规则的，因此必然需要进行“填充”才能形成完整的“块”。“填充”时比较常用的是&lt;a href="http://tools.ietf.org/html/rfc2898"&gt;PKCS #5&lt;/a&gt;规则，简单地说，便是根据最后一个数据块所缺少的长度来选择填充的内容。&lt;/p&gt;

&lt;p&gt;例如，数据块长度要求是8字节，如果输入的最后一个数据块只有5个字节的数据，那么则在最后补充三个字节的0x3。如果输入的最后一个数据块正好为8字节长，则在最后补充一个完整的长为8字节的数据块，每个字节填0x8。使用这个规则，我们便可以根据填充的内容来得知填充的长度，以便在解密后去除填充的字节。&lt;/p&gt;

&lt;p&gt;在解密时，如果算法发现解密后得到的结果，它的填充方式不符合规则，那么表示输入数据有问题，对于解密的类库来说，往往便会抛出一个异常，提示Padding不正确。Oracle在这里便是“提示”的意思，和甲骨文公司没有任何关系。&lt;/p&gt;

&lt;h1&gt;如何进行Padding Oracle Attack&lt;/h1&gt;

&lt;p&gt;刚才已经提到，如果输入的密文不合法，类库则会抛出异常，这便是一种提示。攻击者可以不断地提供密文，让解密程序给出提示，不断修正，最终得到的所需要的结果。这里的一个关键在于，攻击者所需要的提示仅仅是“解密成功与否”这样一个二元信息，例如它在一个Web程序中可能只是“200 - OK”及“500 - Internal Server Error”这样的表现形式，而不需要其他任何详细信息。&lt;/p&gt;

&lt;p&gt;例如，现代流行的Web框架大都是开源的，因此它的加密方式完全透明（当然这点其实并不是必须的，只是大有帮助而已），对于攻击者来说唯一不知道的便是密钥。于是攻击者便可以根据这个加密方式设计有针对性的密文，最终得到密钥（及&lt;a href="http://en.wikipedia.org/wiki/Initialization_vector"&gt;IV&lt;/a&gt;等信息）。在很多时候，一个网站都会使用同样的密钥和IV，于是只需从一个漏洞，便可以在网站的其他方面进行破坏，或解密信息，或绕开验证。&lt;/p&gt;

&lt;p&gt;在具体操作上还可以有许多方式进行辅助，在&lt;a href="http://netifera.com/research/"&gt;Juliano Rizzo和Thai Duong&lt;/a&gt;的《&lt;a href="http://usenix.org/events/woot10/tech/full_papers/Rizzo.pdf"&gt;Practical Padding Oracle Attacks&lt;/a&gt;》（&lt;a href="https://media.blackhat.com/bh-eu-10/whitepapers/Duong_Rizzo/BlackHat-EU-2010-Duong-Rizzo-Padding-Oracle-wp.pdf"&gt;及此&lt;/a&gt;）论文（下文称PPOA）中便提到了很多方式，例如使用Google搜索异常的关键字（这说明许多站点都把异常信息输出在页面上），检查代码，从外表检查一些BASE64形式的字符串，猜测常见的分割符，如“--”，“|”或是“:”等等。PPOA认为，如今Padding Oracle漏洞与SQL注入，脚本注入等漏洞一样无处不在，论文中还详细讨论了利用这个漏洞来攻击eBay拉丁美洲站点，CAPTCHA等应用，以及在JSF（包括&lt;a href="http://myfaces.apache.org/"&gt;Apache MyFaces&lt;/a&gt;和&lt;a href="https://javaserverfaces.dev.java.net/"&gt;Sun Mojarra&lt;/a&gt;实现），Ruby on Rails等Web框架中的漏洞——奇怪的是其中反而没有提到ASP.NET。&lt;/p&gt;

&lt;p&gt;关于Padding Oracle Attack的具体细节，您可以从《&lt;a href="http://www.gdssecurity.com/l/b/2010/09/14/automated-padding-oracle-attacks-with-padbuster/"&gt;Automated Padding Oracle Attacks with PadBuster&lt;/a&gt;》及《&lt;a href="http://www.isg.rhul.ac.uk/~kp/secretIV.pdf"&gt;Padding Oracle Attacks on CBC-mode Encryption with Secret and Random IVs&lt;/a&gt;》两篇文章中得到更详细的信息，它们似乎并不像表面那样高深莫测，尤其是前者，有机会我也打算将它翻译一下。&lt;/p&gt;

&lt;h1&gt;针对ASP.NET的攻击及其危害&lt;/h1&gt;

&lt;p&gt;那么，这次又是如何对ASP.NET站点进行攻击的呢？方式有不少，例如攻击者可以为一个需要认证的请求发送自定义的cookie值，如果没有通过认证，则会得到一个转向登陆页面的302跳转。一个更为直观和通用的作法来自于PPOA论文的作者&lt;a href="http://www.youtube.com/watch?v=yghiC_U2RaM"&gt;所提供的一段视频&lt;/a&gt;，其中使用了WebResources.axd?d=xyz进行探测工作。WebResource.axd有一个特点，便是会对错误的密文（即d=xyz中的xyz）产生500错误，而对正确的密文产生404错误，这便形成了足够的提示。&lt;/p&gt;

&lt;p&gt;好，那么假设攻击者已经得到了站点的Machine Key，也就是网站所使用的密钥，那么它又能造成什么危害呢？&lt;/p&gt;

&lt;p&gt;一些危害是很容易理解的，例如解密（或注入）ViewState，或是如视频里那样设置一个管理员的cookie。在ScottGu等文章中描述这个漏洞的危害时还提到，这个漏洞可以用来下载web.config等私密文件，这又是如何办到的呢？要知道web.config文件的下载是被IIS和ASP.NET所禁止的，它似乎和加密解密或是Machine Key无关。不过您是否意识到，在ASP.NET 3.5 SP1以后，我们可以利用ScriptManager来打包输出本地的脚本文件？例如： &lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptManager &lt;/span&gt;&lt;span style="color: red"&gt;ID&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;sm&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;runat&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;server&amp;quot;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;CompositeScript&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;Scripts&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptReference &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;~/scripts/core.js&amp;quot; /&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptReference &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;~/scripts/lib.js&amp;quot; /&amp;gt;
        &amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;Scripts&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;CompositeScript&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptManager&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;这段内容会在页面上放置一段ScriptResource.axd的引用，它的Query String便包含了需要输出的文件路径，它是与ScriptManager等组件完全独立的。那么，如果攻击者告诉它输出“~/web.config”的时候……&lt;/p&gt;

&lt;p&gt;有趣的是，PPOA论文作者同时还在今年两月和六月分别提供了&lt;a href="http://www.youtube.com/watch?v=e46A-PUpDvk"&gt;攻击CAPTCHA&lt;/a&gt;和&lt;a href="http://www.youtube.com/watch?v=euujmKDxmC4"&gt;攻击Apache MyFaces&lt;/a&gt;的视频，同时也提供了一个&lt;a href="http://netifera.com/research/"&gt;针对JSF的自动攻击工具&lt;/a&gt;，不过它们并没有形成微软对ASP.NET的漏洞那样强烈反应。&lt;/p&gt;

&lt;h1&gt;防止攻击&lt;/h1&gt;

&lt;p&gt;目前ScottGu给出了多个workaround，归根结底便是消除“Oracle”，也就是提示信息。例如他强调要为404和500错误提供完全相同的反馈——不止是输出的错误页面，也包括所有的头信息（如Server Time等自然除外），这种做法会让攻击者无法得到提示信息，自然也就无法解密了。此外，ScottGu的一些代码同时让错误页面Sleep一小段时间，这也是种常用的混淆手段，让攻击者无法从响应时间长短上来了解这个请求“性质”如何。&lt;/p&gt;

&lt;p&gt;从上面的分析中我们可以知道，这种统一错误信息的作法似乎是针对WebResource.axd和ScriptResource.axd的。由于我们知道了攻击的手段，便也可以采取其他作法。例如对于不需要这两个Handler的站点，就把它们从ASP.NET或IIS里直接摘除吧。还有，如果在日志中发现太多CryptographicException异常，便要关注站点是否遭受的攻击。&lt;/p&gt;

&lt;p&gt;但是，Padding Oracle Attack的危害之处在于它所需要的信息实在太少，攻击者只需分辨两种状态便可以进行攻击，即便WebResource.axd的攻击被您防止了，那么之前提到的用户认证所带来的302跳转又如何？对于我们独立编写的应用程序来说，要绕开这个问题可以使用各种技巧。但对于微软来说可能就不容易了，因为ASP.NET作为一个框架，它提供的是一种统一的，普适的机制，这也是为什么这个漏洞会影响几乎所有微软ASP.NET产品的缘故。&lt;/p&gt;

&lt;p&gt;此外还有一些做法也是可取的。例如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;避免在ViewState和Cookie中存放敏感数据。 &lt;/li&gt;

  &lt;li&gt;不要过度依赖Machine Key。&lt;/li&gt;

  &lt;li&gt;在认证cookie里保存的不只是用户名，而是外界无法得知的ID，或是同时保存checksum等额外的验证信息。 &lt;/li&gt;

  &lt;li&gt;为ScriptResource.axd写一个Wrapper，只让它输出扩展名为js的内容。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些做法的目的是：即使攻击者得到了Machine Key，也无法对站点造成破坏。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;安全性漏洞总是不令人愉快的，但是在遇到这种状况的同时，我们也要努力得知问题的真实情况。在如今信息爆炸的时代，产生和获取一条没有多大价值甚至是错误的信息，可谓是非常容易的。排除干扰寻求真相，即便只是种态度和意愿，也是一名技术人员的基本素质。因此在这个问题上，我最反感的便是“微软的产品就是不安全”，“反正我不会被攻击”这样的态度。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html#comments</comments>
      <pubDate>Fri, 24 Sep 2010 18:25:07 GMT</pubDate>
      <lastBuildDate>Fri, 24 Sep 2010 18:25:07 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>当类型为dynamic的视图模型遭遇匿名对象</title>
      <link>http://blog.zhaojie.me/2010/05/asp-net-mvc-dynamic-view-model-binding-error-with-anonymous-types.html</link>
      <guid>http://blog.zhaojie.me/2010/05/asp-net-mvc-dynamic-view-model-binding-error-with-anonymous-types.html</guid>
      <description>&lt;p&gt;当年在ASP.NET MVC 1.0时代我提到，在开发时最好将视图的Model定制为强类型的，这样可以充分利用静态检查功能进行排错。不过有人指出，这么做虽然易于静态检查，但是定义强类型的Model类型实在是太麻烦了，因此也出现了基于SmartBag等折衷方案。强类型是一种极端方案，而在C# 4.0中我们又可以使用另一个极端，那就是让Model成为dynamic类型，这样在视图中便可以完全自由地获取数据了。不过，在使用匿名对象的情况下视图会抛出奇怪的“无法找到成员”异常，我们必须解决这个问题。&lt;/p&gt;

&lt;h1&gt;dynamic类型的视图模型&lt;/h1&gt;

&lt;p&gt;我们现在先来创建一个Model类型为dynamic的视图，例如Views\Home\Index.aspx，我们要在一开始的Page标记中设置它的基类类型：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="background: yellow"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;@ &lt;/span&gt;&lt;span style="color: maroon"&gt;Page &lt;/span&gt;&lt;span style="color: red"&gt;Language&lt;/span&gt;&lt;span style="color: blue"&gt;="C#" &lt;/span&gt;&lt;span style="color: red"&gt;Inherits&lt;/span&gt;&lt;span style="color: blue"&gt;="System.Web.Mvc.ViewPage&lt;&lt;span style="background-color: yellow"&gt;dynamic&lt;/span&gt;&gt;" &lt;/span&gt;&lt;span style="background: yellow"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;我们将这里的范型参数设为dynamic。这一做法可能会让某些朋友感到新奇，不过这其实十分正常。事实上，dynamic关键字在C#的概念中（不论实现），其实就是和int、string或是Controller一样，都是一种“类型”，只不过对于这样一个类型会有些动态分发等特殊处理。这样定义视图类型之后，便可以在Controller中自由提供视图模型了，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Index()
{
    &lt;span style="color: blue"&gt;dynamic &lt;/span&gt;model = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ExpandoObject&lt;/span&gt;();
    model.Hello = &lt;span style="color: #a31515"&gt;"World"&lt;/span&gt;;

    &lt;span style="color: blue"&gt;return &lt;/span&gt;View(model);
}&lt;/pre&gt;

&lt;p&gt;ExpandoObject是.NET 4.0在BCL中提供的类，它的作用便是利用.NET中定义的动态分发功能，定义了一个可以任意扩展的类型。例如在上面的代码中，我们可以直接赋予它Hello属性。然后在视图模板中读出：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="background: yellow"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Model.Hello &lt;span style="background: yellow"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;这样看起来是不是很方便？这样虽然没有了任何的静态教研，倒也带来了十足的自由性——就像是Ruby on Rails等动态语言的框架一样。&lt;/p&gt;

&lt;h1&gt;奇怪的“无法找到成员”异常&lt;/h1&gt;

&lt;p&gt;不过，这种dynamic有时候会有非常奇怪的表现，例如我们把上面Index的代码改成使用匿名类型对象之后：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Index()
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;View(&lt;span style="color: blue"&gt;new &lt;/span&gt;{ Hello = &lt;span style="color: #a31515"&gt;"World" &lt;/span&gt;});
}&lt;/pre&gt;

&lt;p&gt;再次执行就会抛出异常：&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/mvc-dynamic-model-bind-error.png" /&gt; 

&lt;p&gt;简单说来，便是指Model对象没有找到合适的Hello成员，因此绑定失败。但是，我们明明有这个属性，不是吗？更奇怪的是，如果是在一个Console应用程序中这么写则是没有任何问题的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;dynamic &lt;/span&gt;model = &lt;span style="color: blue"&gt;new &lt;/span&gt;{ Hello = &lt;span style="color: #a31515"&gt;"World" &lt;/span&gt;};
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(model.Hello);&lt;/pre&gt;

&lt;p&gt;问题似乎就是出在ASP.NET的页面上。说到原因，我也不知道，一开始我以为是由于只读属性造成的，但是在尝试了显式定义类型之后发现也不是这个原因。我相信仔细分析之后一定可以得出结果，不过现在最重要的是想个办法解决它。毕竟这个情况很容易遇到，ExpandoObject只能解决极小部分的问题。因为在Controller中的常见需求之一，便是使用如LINQ表达式等方式生成一些供视图使用的匿名对象。&lt;/p&gt;

&lt;h1&gt;生成动态类型&lt;/h1&gt;

&lt;p&gt;这个问题以前也有人遇到过，他的做法是&lt;a href="http://blogs.msdn.com/davidebb/archive/2009/12/18/passing-anonymous-objects-to-mvc-views-and-accessing-them-using-dynamic.aspx"&gt;为视图模型定义一个封装类&lt;/a&gt;。可惜这么做其实并没有解决问题，事实上它只解决了Model本身使用匿名对象的问题，但是如果是Model的某个字段返回一个匿名对象呢？再访问这个匿名对象还是有相同问题出现。幸好我们知道一个显示定义的类型是可以正常使用的，于是一个比较容易想到的方法便是在运行时生成动态类型。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DynamicFactory
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ConcurrentDictionary&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;&gt; s_dynamicTypes = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ConcurrentDictionary&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;&gt;();

    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;&gt; s_dynamicTypeCreator = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;&gt;(CreateDynamicType);

    &lt;span style="color: blue"&gt;public static object &lt;/span&gt;ToDynamic(&lt;span style="color: blue"&gt;this object &lt;/span&gt;entity)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;entityType = entity.GetType();
        &lt;span style="color: blue"&gt;var &lt;/span&gt;dynamicType = s_dynamicTypes.GetOrAdd(entityType, s_dynamicTypeCreator);

        &lt;span style="color: blue"&gt;var &lt;/span&gt;dynamicObject = &lt;span style="color: #2b91af"&gt;Activator&lt;/span&gt;.CreateInstance(dynamicType);
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;entityProperty &lt;span style="color: blue"&gt;in &lt;/span&gt;entityType.GetProperties())
        {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;value = entityProperty.GetValue(entity, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
            dynamicType.GetField(entityProperty.Name).SetValue(dynamicObject, value);
        }

        &lt;span style="color: blue"&gt;return &lt;/span&gt;dynamicObject;
    }

    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;CreateDynamicType(&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;entityType)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;asmName = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AssemblyName&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"DynamicAssembly_" &lt;/span&gt;+ &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid());
        &lt;span style="color: blue"&gt;var &lt;/span&gt;asmBuilder = &lt;span style="color: #2b91af"&gt;AppDomain&lt;/span&gt;.CurrentDomain.DefineDynamicAssembly(asmName, &lt;span style="color: #2b91af"&gt;AssemblyBuilderAccess&lt;/span&gt;.Run);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;moduleBuilder = asmBuilder.DefineDynamicModule(&lt;span style="color: #a31515"&gt;"DynamicModule_" &lt;/span&gt;+ &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid());

        &lt;span style="color: blue"&gt;var &lt;/span&gt;typeBuilder = moduleBuilder.DefineType(
            entityType.GetType() + &lt;span style="color: #a31515"&gt;"$DynamicType"&lt;/span&gt;,
            &lt;span style="color: #2b91af"&gt;TypeAttributes&lt;/span&gt;.Public);

        typeBuilder.DefineDefaultConstructor(&lt;span style="color: #2b91af"&gt;MethodAttributes&lt;/span&gt;.Public);

        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;entityProperty &lt;span style="color: blue"&gt;in &lt;/span&gt;entityType.GetProperties())
        {
            typeBuilder.DefineField(entityProperty.Name, entityProperty.PropertyType, &lt;span style="color: #2b91af"&gt;FieldAttributes&lt;/span&gt;.Public);
        }

        &lt;span style="color: blue"&gt;return &lt;/span&gt;typeBuilder.CreateType();
    }
}&lt;/pre&gt;

&lt;p&gt;我在这里定义了一个ToDynamic扩展方法，用于从任意对象扩展出一个……动态类型的对象。这个动态类型会根据输入对象中的属性信息，生成对应的公有字段，然后使用反射进行赋值。有了这个方法以后，遇到匿名类型也就只需稍多一步就够了：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;return&lt;/span&gt; View(&lt;span style="color: blue"&gt;new &lt;/span&gt;{ Hello = &lt;span style="color: #a31515"&gt;"World" &lt;/span&gt;}&lt;span style="background-color: yellow"&gt;.ToDynamic()&lt;/span&gt;);&lt;/pre&gt;

&lt;p&gt;即便像我之前所说的那样，使用LINQ语句为视图准备一些可用的匿名对象，也可以这样：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;categories = ...; &lt;span style="color: green"&gt;// get categories&lt;/span&gt;;

&lt;span style="color: blue"&gt;dynamic &lt;/span&gt;model = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ExpandoObject&lt;/span&gt;();
model.PrivateCategories =
    &lt;span style="color: blue"&gt;from &lt;/span&gt;c &lt;span style="color: blue"&gt;in &lt;/span&gt;categories
    &lt;span style="color: blue"&gt;where &lt;/span&gt;c.AccessLevel == &lt;span style="color: #2b91af"&gt;AccessLevel&lt;/span&gt;.Private
    &lt;span style="color: blue"&gt;orderby &lt;/span&gt;c.IsDefault &lt;span style="color: blue"&gt;descending&lt;/span&gt;, c.Name
    &lt;span style="color: blue"&gt;select new
    &lt;/span&gt;{
        CategoryID = c.NoteCategoryID,
        CategoryName = c.Name,
        IsDefault = c.IsDefault,
        Count = c.NoteCount
    }&lt;span style="background-color: yellow"&gt;.ToDynamic()&lt;/span&gt;;

model.PublicCategories =
    &lt;span style="color: blue"&gt;from &lt;/span&gt;c &lt;span style="color: blue"&gt;in &lt;/span&gt;categories
    &lt;span style="color: blue"&gt;where &lt;/span&gt;c.AccessLevel == &lt;span style="color: #2b91af"&gt;AccessLevel&lt;/span&gt;.Public
    &lt;span style="color: blue"&gt;orderby &lt;/span&gt;c.IsDefault &lt;span style="color: blue"&gt;descending&lt;/span&gt;, c.Name
    &lt;span style="color: blue"&gt;select new
    &lt;/span&gt;{
        CategoryID = c.NoteCategoryID,
        CategoryName = c.Name,
        IsDefault = c.IsDefault,
        Count = c.NoteCount
    }&lt;span style="background-color: yellow"&gt;.ToDynamic()&lt;/span&gt;;

&lt;span style="color: blue"&gt;return &lt;/span&gt;View(model);&lt;/pre&gt;

&lt;p&gt;问题就这样解决了。&lt;/p&gt;

&lt;h1&gt;改进&lt;/h1&gt;

&lt;p&gt;很显然，上面的实现只是个雏形，其中最大的问题应该就是“性能”了。现在的代码中反复使用了反射，对此我们可以使用如&lt;a href="http://blog.zhaojie.me/2009/01/fast-reflection-library.html"&gt;Fast Reflection Library&lt;/a&gt;那样的方式来改善反射读写字段的执行效率。当然，可能最好的办法是动态生成出这样的代码了：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DynamicType&lt;/span&gt;
{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;DynamicType(&lt;span style="color: blue"&gt;object &lt;/span&gt;entity)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;strongTyped = (&lt;span style="color: #2b91af"&gt;EntityType&lt;/span&gt;)entity;

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Abc = strongTyped.Abc;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Ijk = strongTyped.Ijk;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Xyz = strongTyped.Xyz;
        ...
    }
}&lt;/pre&gt;

&lt;p&gt;这其实也很简单，有兴趣的话您也可以试试看。一会儿我也会去实现一下类似的功能，顺便尝试一下.NET 4.0——据说.NET 4.0的类库对生成动态方法较之前的版本有了更好的支持。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/05/asp-net-mvc-dynamic-view-model-binding-error-with-anonymous-types.html#comments</comments>
      <pubDate>Tue, 11 May 2010 02:16:50 GMT</pubDate>
      <lastBuildDate>Tue, 11 May 2010 02:16:50 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>F#与ASP.NET（2）：使用F#实现基于事件的异步模式</title>
      <link>http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-2-implement-event-based-asynchronous-pattern-with-fsharp.html</link>
      <guid>http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-2-implement-event-based-asynchronous-pattern-with-fsharp.html</guid>
      <description>&lt;p&gt;在&lt;a href="http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-1-event-based-asynchronous-pattern-and-async-action.html"&gt;上一篇文章&lt;/a&gt;中，我们的简单讨论了.NET中两种异步模型以及它们在异常处理上的区别，并且简单观察了ASP.NET MVC 2中异步Action的编写方式。从中我们得知，ASP.NET MVC 2的异步Action并非使用了传统基于Begin/End的异步编程模型，而是另一种基于事件的异步模式。此外，ASP.NET MVC 2对于这种异步模式提供了必要的支持，使此方面的程序设计变得相对简单一些。但是，简单的原因主要还是在于已经由其他组件提供了良好的，基于事件的异步模式。那么现在我们就来看看一般我们应该如何来实现这样的功能，以及F#是如何美化我们的生活的吧。&lt;/p&gt;  &lt;h1&gt;异步数据传输&lt;/h1&gt;  &lt;p&gt;我们为什么要异步，主要目的之一还是提高I/O操作的伸缩性。I/O操作主要是I/O设备的事情，这些设备在好好工作的时候，是不需要系统花心思进行照看的，它们只要能够在完成指定工作后通知系统就可以了。这也是异步I/O高效的原理，因为它将程序从等待I/O完成的苦闷中解脱出来，这对用户或是系统来说都是一件绝好的事情。&lt;/p&gt;  &lt;p&gt;那么说到I/O操作，最典型的场景之一便是数据传输了。比如有两个数据流streamIn和streamOut，我们需要异步地从streamIn中读取数据，并异步地写入到streamOut中。在这个过程中，我们使用一个相对较小的byte数组作为缓存空间，这样程序在进行数据传输时便不会占用太多内存。那么，如果现在需要您编写一个组件完成这样的数据传输工作，并使用标准的基于事件的异步模式释放出来，您会怎么做？&lt;/p&gt;  &lt;p&gt;再啰嗦一次，基于事件的异步模式，要求在任务完成时使用事件进行提示。同时在出错的时候将异常对象保存在事件的参数中。现在我已经帮您写好了这样的事件参数：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CompletedEventArgs &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;EventArgs
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;CompletedEventArgs(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Error = ex;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;Error { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;那么接下来的工作就交给您了，加油！&lt;/p&gt;

&lt;p&gt;嗯？那么快就写完啦？再想想？如果真确定了，就展开下面的代码对比一下吧：&lt;/p&gt;

&lt;pre id="this-is-code-A-hide" class="code"&gt;&lt;a href="javascript:__showAndHide('this-is-code-A-show', 'this-is-code-A-hide')"&gt;展开代码&lt;/a&gt;&lt;/pre&gt;

&lt;pre id="this-is-code-A-show" class="code jeffz_seo"&gt;&lt;a href="javascript:__showAndHide('this-is-code-A-hide', 'this-is-code-A-show')"&gt;隐藏代码&lt;/a&gt;

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncTransfer
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stream &lt;/span&gt;m_streamIn;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stream &lt;/span&gt;m_streamOut;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;AsyncTransfer(&lt;span style="color: #2b91af"&gt;Stream &lt;/span&gt;streamIn, &lt;span style="color: #2b91af"&gt;Stream &lt;/span&gt;streamOut)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_streamIn = streamIn;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_streamOut = streamOut;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;StartAsync()
    {
        &lt;span style="color: blue"&gt;byte&lt;/span&gt;[] buffer = &lt;span style="color: blue"&gt;new byte&lt;/span&gt;[1024];

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_streamIn.BeginRead(
            buffer, 0, buffer.Length,
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.EndReadInputStreamCallback, buffer);
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;EndReadInputStreamCallback(&lt;span style="color: #2b91af"&gt;IAsyncResult &lt;/span&gt;ar)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;buffer = (&lt;span style="color: blue"&gt;byte&lt;/span&gt;[])ar.AsyncState;
        &lt;span style="color: blue"&gt;int &lt;/span&gt;lengthRead;

        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            lengthRead = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_streamIn.EndRead(ar);
        }
        &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.OnCompleted(ex);
            &lt;span style="color: blue"&gt;return&lt;/span&gt;;
        }

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(lengthRead &lt;= 0)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.OnCompleted(&lt;span style="color: blue"&gt;null&lt;/span&gt;);
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;try
            &lt;/span&gt;{
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_streamOut.BeginWrite(
                    buffer, 0, lengthRead,
                    &lt;span style="color: blue"&gt;this&lt;/span&gt;.EndWriteOutputStreamCallback, buffer);
            }
            &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
            {
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.OnCompleted(ex);
            }
        }
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;EndWriteOutputStreamCallback(&lt;span style="color: #2b91af"&gt;IAsyncResult &lt;/span&gt;ar)
    {
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_streamOut.EndWrite(ar);

            &lt;span style="color: blue"&gt;var &lt;/span&gt;buffer = (&lt;span style="color: blue"&gt;byte&lt;/span&gt;[])ar.AsyncState;
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_streamIn.BeginRead(
                buffer, 0, buffer.Length,
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.EndReadInputStreamCallback, buffer);
        }
        &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.OnCompleted(ex);
        }
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;OnCompleted(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;handler = &lt;span style="color: blue"&gt;this&lt;/span&gt;.Completed;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(handler != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            handler(&lt;span style="color: blue"&gt;this&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CompletedEventArgs&lt;/span&gt;(ex));
        }
    }

    &lt;span style="color: blue"&gt;public event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;CompletedEventArgs&lt;/span&gt;&gt; Completed;
}&lt;/pre&gt;

&lt;p&gt;是不是很复杂的样子？&lt;/p&gt;

&lt;p&gt;编写异步程序，基本则意味着要将原本同步的调用拆成两段：发起及回调，这样便让上下文状态的保存便的困难起来。幸运的是，C#这门语言提供了方便好用的匿名函数语法，这对于编写一个回调函数来说已经非常容易了。但是，如果需要真正写一个稳定、安全的异步程序，需要做的事情还有很多。例如，一次异步操作结束之后会执行一个回调函数，那么如果在这个回调函数中抛出了一个异常那该怎么办？如果不正确处理这个异常，轻则造成资源泄露，重则造成进程退出。因此在每个回调函数中，您会发现try...catch块是必不可少的——甚至还需要两段。&lt;/p&gt;

&lt;p&gt;更复杂的可能还是在于逻辑控制上。这样一个数据传输操作很显然需要循环——读一段，写一段。但是由于需要编写成二段式的异步调用，因此程序的逻辑会被拆得七零八落，我们没法使用一个while块包围整段逻辑。&lt;/p&gt;

&lt;p&gt;编写一个异步程序本来就是那么复杂。&lt;/p&gt;

&lt;h1&gt;编写简单的代理&lt;/h1&gt;

&lt;p&gt;嗯，我们继续。现在我们已经有了一个异步传输数据的组件，就用它来做一些有趣的事情吧。例如，我们可以在ASP.NET应用程序中建立一个简单的代理，即给定一个URL，在服务器端发起这样一个请求，并将这个URL的数据传输到客户端来。简单起见，除了进行数据传输之外，我们只需要简单地输出Content Type头信息即可。&lt;/p&gt;

&lt;p&gt;写好了吗？我也写了一个，仅供参考：&lt;/p&gt;

&lt;pre id="this-is-code-B-hide" class="code"&gt;&lt;a href="javascript:__showAndHide('this-is-code-B-show', 'this-is-code-B-hide')"&gt;展开代码&lt;/a&gt;&lt;/pre&gt;

&lt;pre id="this-is-code-B-show" class="code jeffz_seo"&gt;&lt;a href="javascript:__showAndHide('this-is-code-B-hide', 'this-is-code-B-show')"&gt;隐藏代码&lt;/a&gt;

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncWebTransfer
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebRequest &lt;/span&gt;m_request;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebResponse &lt;/span&gt;m_response;

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HttpContextBase &lt;/span&gt;m_context;
    &lt;span style="color: blue"&gt;private string &lt;/span&gt;m_url;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;AsyncWebTransfer(&lt;span style="color: #2b91af"&gt;HttpContextBase &lt;/span&gt;context, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_context = context;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_url = url;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;StartAsync()
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_request = &lt;span style="color: #2b91af"&gt;WebRequest&lt;/span&gt;.Create(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_url);
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_request.BeginGetResponse(&lt;span style="color: blue"&gt;this&lt;/span&gt;.EndGetResponseCallback, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;EndGetResponseCallback(&lt;span style="color: #2b91af"&gt;IAsyncResult &lt;/span&gt;ar)
    {
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_response = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_request.EndGetResponse(ar);
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_context.Response.ContentType = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_response.ContentType;

            &lt;span style="color: blue"&gt;var &lt;/span&gt;streamIn = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_response.GetResponseStream();
            &lt;span style="color: blue"&gt;var &lt;/span&gt;streamOut = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_context.Response.OutputStream;

            &lt;span style="color: blue"&gt;var &lt;/span&gt;transfer = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncTransfer&lt;/span&gt;(streamIn, streamOut);
            transfer.Completed += (sender, args) =&gt; &lt;span style="color: blue"&gt;this&lt;/span&gt;.OnCompleted(args.Error);
            transfer.StartAsync();
        }
        &lt;span style="color: blue"&gt;catch&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.OnCompleted(ex);
        }
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;OnCompleted(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_response != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_response.Close();
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_response = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
        }

        &lt;span style="color: blue"&gt;var &lt;/span&gt;handler = &lt;span style="color: blue"&gt;this&lt;/span&gt;.Completed;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(handler != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            handler(&lt;span style="color: blue"&gt;this&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CompletedEventArgs&lt;/span&gt;(ex));
        }
    }

    &lt;span style="color: blue"&gt;public event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;CompletedEventArgs&lt;/span&gt;&gt; Completed;
}&lt;/pre&gt;

&lt;p&gt;如果说之前的AsyncTransfer类是基于“Begin/End异步编程模型”实现的基于事件的异步模式，那么AsyncWebTransfer便是基于“基于事件的异步模式”实现的基于事件的异步模式了。嗯，似乎有点绕口，不过我相信这段代码对您来说还是不难理解的。&lt;/p&gt;

&lt;h1&gt;使用F#完成异步工作&lt;/h1&gt;

&lt;p&gt;好吧，我承认，前面的代码我也觉得很复杂，很难写。事实上我已经很久没有写过这样的代码了，咎其原因还是被F#给宠坏了。尝试了C# 2.0之后我便抛弃了Java语言，熟悉了C# 3.0之后我用C# 2.0就快写不了程序了，而使用了F#进行异步编程之后，我就再也没有使用C#写过异步操作了。&lt;/p&gt;

&lt;p&gt;那么我们就来看看F#是如何进行异步数据传输的吧：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let rec &lt;/span&gt;transferAsync (streamIn: Stream) (streamOut: Stream) buffer = 
    async {
        &lt;span style="color: blue"&gt;let! &lt;/span&gt;lengthRead = streamIn.AsyncRead(buffer, 0, buffer.Length)
        &lt;span style="color: blue"&gt;if &lt;/span&gt;lengthRead &gt; 0 &lt;span style="color: blue"&gt;then
            do! &lt;/span&gt;streamOut.AsyncWrite(buffer, 0, lengthRead)
            &lt;span style="color: blue"&gt;do! &lt;/span&gt;transferAsync streamIn streamOut buffer
    }&lt;/pre&gt;

&lt;p&gt;喔喔，上面的代码利用了尾递归进行不断地数据传输，我们也可以使用传统的while循环来实现这个功能（关于ref的作用可参考&lt;a href="http://blog.zhaojie.me/2010/03/reference-cell-retrieval-in-fsharp.html"&gt;这篇文章&lt;/a&gt;）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;transferImperativelyAsync (streamIn: Stream) (streamOut: Stream) buffer = 
    async {
        &lt;span style="color: blue"&gt;let &lt;/span&gt;hasData = ref &lt;span style="color: blue"&gt;true
        while &lt;/span&gt;(hasData.Value) &lt;span style="color: blue"&gt;do
            let! &lt;/span&gt;lengthRead = streamIn.AsyncRead(buffer, 0, buffer.Length)
            &lt;span style="color: blue"&gt;if &lt;/span&gt;lengthRead &gt; 0 &lt;span style="color: blue"&gt;then
                do! &lt;/span&gt;streamOut.AsyncWrite(buffer, 0, lengthRead)
            &lt;span style="color: blue"&gt;else
                &lt;/span&gt;hasData := &lt;span style="color: blue"&gt;false
    &lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;有了transferAsync函数，编写一个资源请求的代理也是几分钟的事情：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;webTransferAsync (context: HttpContextBase) (url: string) =
    async {
        &lt;span style="color: blue"&gt;let &lt;/span&gt;request = WebRequest.Create(url)
        &lt;span style="color: blue"&gt;use! &lt;/span&gt;response = request.GetResponseAsync()
        context.Response.ContentType &lt;- response.ContentType
        
        &lt;span style="color: blue"&gt;let &lt;/span&gt;streamIn = response.GetResponseStream()
        &lt;span style="color: blue"&gt;let &lt;/span&gt;streamOut = context.Response.OutputStream
        &lt;span style="color: blue"&gt;let &lt;/span&gt;buffer = Array.zeroCreate 1024
        &lt;span style="color: blue"&gt;do! &lt;/span&gt;transferAsync streamIn streamOut buffer
    }&lt;/pre&gt;

&lt;p&gt;没错，就是这么简单，这就是F#中编写异步任务方式（关于这方面的更多细节，您可以阅读F#的主要设计者&lt;a href="http://research.microsoft.com/en-us/people/dsyme/"&gt;Don Syme&lt;/a&gt;所撰写的&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html"&gt;一系列文章&lt;/a&gt;）。在执行这两个函数时（当然确切地说，是执行这两个函数所生成的异步工作流），便会在出现“感叹号”的操作之处自动分成二段式的异步调用，但是在程序的写法上和同步代码可谓毫无二致。&lt;/p&gt;

&lt;h1&gt;使用F#实现基于事件的异步模式&lt;/h1&gt;

&lt;p&gt;当然，光有上面的代码还不够，因为这样的代码无法交给C#代码来使用，我们还需要将它们封装成基于事件的异步模式。不过这也非常简单，使用一个通用的抽象基类即可：&lt;/p&gt;

&lt;pre class="code"&gt;[&amp;lt;AbstractClass&amp;gt;]
&lt;span style="color: blue"&gt;type &lt;/span&gt;AsyncWorker(asyncWork: Async&lt;unit&gt;) = 
    
    &lt;span style="color: blue"&gt;let &lt;/span&gt;completed = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&lt;CompletedEventArgs&gt;()

    [&amp;lt;CLIEvent&amp;gt;]
    &lt;span style="color: blue"&gt;member &lt;/span&gt;e.Completed = completed.Publish

    &lt;span style="color: blue"&gt;member &lt;/span&gt;e.StartAsync() = 
        Async.StartWithContinuations
            (asyncWork,
             (&lt;span style="color: blue"&gt;fun &lt;/span&gt;_ &lt;span style="color: blue"&gt;-&gt; &lt;/span&gt;completed.Trigger(&lt;span style="color: blue"&gt;new &lt;/span&gt;CompletedEventArgs(&lt;span style="color: blue"&gt;null&lt;/span&gt;))),
             (&lt;span style="color: blue"&gt;fun &lt;/span&gt;ex &lt;span style="color: blue"&gt;-&gt; &lt;/span&gt;completed.Trigger(&lt;span style="color: blue"&gt;new &lt;/span&gt;CompletedEventArgs(ex))),
             (&lt;span style="color: blue"&gt;fun &lt;/span&gt;ex &lt;span style="color: blue"&gt;-&gt; &lt;/span&gt;ex |&gt; ignore))&lt;/pre&gt;

&lt;p&gt;在使用F#进行面向对象开发时，由于不需要C#的架子代码，它实现相同的结构一般都会显得紧凑不少（不过在我看来，C#在进行一般的命令式编程时还是比F#来的方便一些）。在StartAsync方法中，我们使用Async.StartWithContinuations发起一个异步工作流，而这个异步工作流便是从构造函数中传入的具体任务。StartWithContinuations方法的后三个参数分别是成功时的回调，失败后的回调，以及任务取消后的回调（我们并不需要这个回调，随意提供一个即可）。&lt;/p&gt;

&lt;p&gt;您可能会说，难道F#中不需要异常处理，不需要资源释放吗？当然需要。只不过：1) 异常处理已经由StartWithContinuations统一完成了，我们只要按照“同步式”代码的写法编写逻辑，也就是说，从语义上说您可以看作存在一个巨大的try...catch围绕着整段代码。2) 而对于资源释放来说，您可以发现在webTransferAsync方法中有一个use!指令，这便是告诉F#的异步框架，在整个异步工作流结束之后需要调用这个资源的Dispose方法——没错，您可以把它看作是一种能在异步环境下工作的C# using关键字。&lt;/p&gt;

&lt;p&gt;有了AsyncWorker类之后，AsyncTransfer和WebAsyncTransfer类也可轻易实现了：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;AsyncTransfer(streamIn: Stream, streamOut: Stream) = 
    &lt;span style="color: blue"&gt;inherit &lt;/span&gt;AsyncWorker(
        Transfer.transferAsync streamIn streamOut (Array.zeroCreate 1024))

&lt;span style="color: blue"&gt;type &lt;/span&gt;AsyncWebTransfer(context: HttpContextBase, url: string) =
    &lt;span style="color: blue"&gt;inherit &lt;/span&gt;AsyncWorker(Transfer.webTransferAsync context url)&lt;/pre&gt;

&lt;p&gt;最后，只要在ASP.NET MVC中使用即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public void &lt;/span&gt;LoadFsAsync(&lt;span style="color: blue"&gt;string &lt;/span&gt;url)
{
    AsyncManager.OutstandingOperations.Increment();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;transfer = &lt;span style="color: blue"&gt;new &lt;/span&gt;FSharpAsync.&lt;span style="color: #2b91af"&gt;AsyncWebTransfer&lt;/span&gt;(HttpContext, url);

    transfer.Completed += (sender, args) =&gt;
        AsyncManager.OutstandingOperations.Decrement();

    transfer.StartAsync();
}

&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;LoadFsCompleted()
{
    &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EmptyResult&lt;/span&gt;();
}&lt;/pre&gt;

&lt;p&gt;事实上，在ImageController中我还提供了一个LoadAsync及对应的LoadCompleted方法，它们使用的是利用C#实现的AsyncWebTransfer类。猜猜看这样的代码长成什么样？其实只是将上面的AsyncWebTransfer的命名空间改成CSharpAsync而已——F#与其它.NET代码是真正做到无缝集成的。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;这便是F#的伟大之处。时常有朋友会问我为什么对F#有那么大的兴趣，我想，&lt;font color="#ff0000"&gt;如果借助F#可以用十分之一的时间，十分之一的代码行数，写出执行效果相同，但可维护性高出好几倍的程序来&lt;/font&gt;——我为什么会不感兴趣呢？&lt;/p&gt;

&lt;p&gt;您可以&lt;a href="http://github.com/JeffreyZhao/samples/tree/master/FSharpAsyncWeb/"&gt;在这里访问到本文的示例代码&lt;/a&gt;，其中我在WebApp项目中实现了简单的入口页面，您访问“/image”便会出现两个文本框，您填入一个URL（例如某幅图片）并提交，它会将URL提交至“/image/load”或“/image/loadfs”中，它们分别使用了C#和F#实现的AsyncWebTransfer类，从效果上说两者完全相同。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-1-event-based-asynchronous-pattern-and-async-action.html"&gt;F#与ASP.NET（1）：基于事件的异步模式与异步Action&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;F#与ASP.NET（2）：使用F#实现基于事件的异步模式 &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-2-implement-event-based-asynchronous-pattern-with-fsharp.html#comments</comments>
      <pubDate>Mon, 05 Apr 2010 12:37:47 GMT</pubDate>
      <lastBuildDate>Mon, 05 Apr 2010 12:37:47 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>F#与ASP.NET（1）：基于事件的异步模式与异步Action</title>
      <link>http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-1-event-based-asynchronous-pattern-and-async-action.html</link>
      <guid>http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-1-event-based-asynchronous-pattern-and-async-action.html</guid>
      <description>&lt;p&gt;提高ASP.NET应用程序伸缩性的有效手段之一便是&lt;a href="http://blog.zhaojie.me/2009/01/lab-async-request.html"&gt;使用异步请求&lt;/a&gt;。而在ASP.NET MVC 1中是不能直接支持异步Action的，因此我们需要使用一些&lt;a href="http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action.html"&gt;简单的Hack方式来实现这一点&lt;/a&gt;。不过简单的Hack毕竟无法利用ASP.NET MVC的完整功能，幸好ASP.NET MVC 2已经正式支持ASP.NET中的异步请求处理方式，并且通过一种比较易于使用的方式提供给开发人员使用。只可惜，由于语言层面的约束，这种使用方式还是有些不便，而此时便是F#的用武之地了。&lt;/p&gt;  &lt;h1&gt;基于事件的异步模式&lt;/h1&gt;  &lt;p&gt;说起.NET中的异步编程模型，.NET程序员最熟悉的应该就是Begin/End方法了。例如在WebRequest类中，便有这样一对方法：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;request = &lt;span style="color: #2b91af"&gt;WebRequest&lt;/span&gt;.Create(&lt;span style="color: #a31515"&gt;&amp;quot;http://blog.zhaojie.me/&amp;quot;&lt;/span&gt;);
request.BeginGetResponse(ar =&amp;gt;
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;response = request.EndGetResponse(ar);
    &lt;span style="color: green"&gt;// use the response object

&lt;/span&gt;}, &lt;span style="color: blue"&gt;null&lt;/span&gt;);&lt;/pre&gt;

&lt;p&gt;在调用WebRequest对象的BeginGetResponse方法之后，当前调用线程不会被阻塞，而在异步操作完成之后，便会调用一个回调函数（即这里使用Lambda表达式构造的代码快）进行通知，在这个回调函数中调用EndGetResponse方法便可以得到一个WebResponse对象作为结果。在这个异步操作中，由于伟大的IOCP，我们可以使用极少数的线程同时发起成千上万个连接（豪不夸张，我曾经在IIS里&lt;a href="http://blog.zhaojie.me/2009/12/fsharp-comet-prototype.html"&gt;进行Comet试验&lt;/a&gt;，同时建立起超过2w个连接进行通信）。&lt;/p&gt;

&lt;p&gt;不过，事实上在.NET中还有一种基于事件的异步模式（Event-based Asynchronous Pattern，EAP），这个编程模式也已经在《&lt;a href="http://www.amazon.com/CLR-via-Pro-Developer-Jeffrey-Richter/dp/0735627045"&gt;CLR via C# (3rd Edition)&lt;/a&gt;》中得到了正名（顺便一提，这本书我还差几十页就看完了，的确有相当大的更新，值得一看）。基于事件的异步编程的典型案例之一便是WebClient类：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;client = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebClient&lt;/span&gt;();
client.DownloadStringCompleted += (sender, args) =&amp;gt;
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;html = args.Result;
    &lt;span style="color: green"&gt;// ...
&lt;/span&gt;};

client.DownloadStringAsync(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Uri&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;http://blog.zhaojie.me/&amp;quot;&lt;/span&gt;));&lt;/pre&gt;

&lt;p&gt;基于事件的异步模式的关键便在于，它是使用事件来作为工作结束时的通知机制。它和Begin/End的异步模型有明显区别。例如，在发生错误时，对于Begin/End模型来说会在End方法调用时抛出异常，而对于基于事件的异步模式来说，它则是使用事件参数的Exception属性来告诉程序员是否有异常发生。如果Exception属性为null，则说明一切正常，否则它便返回异步调用过程中发生的异常。&lt;/p&gt;

&lt;h1&gt;在ASP.NET MVC中使用异步Action&lt;/h1&gt;

&lt;p&gt;当年&lt;a href="http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action-2.html"&gt;我的Hack使用的是Begin/End异步编程模型&lt;/a&gt;，而ASP.NET MVC 2则使用了基于事件的异步模式。围绕这种模式，ASP.NET MVC的AsyncController还提供了相关的辅助方法，让异步Action的编写变得相对容易一些。这里我则直接引用&lt;a href="http://msdn.microsoft.com/en-us/library/ee728598(VS.100).aspx"&gt;MSDN上的示例&lt;/a&gt;来说明问题。首先，我们准备一个普通的同步Action：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PortalController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;News(&lt;span style="color: blue"&gt;string &lt;/span&gt;city)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;newsService = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NewsService&lt;/span&gt;();
        &lt;span style="color: blue"&gt;var &lt;/span&gt;headlines = newsService.GetHeadlines(city);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;View(headlines);
    }
}&lt;/pre&gt;

&lt;p&gt;与它等价的异步Action则为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PortalController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;AsyncController
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;NewsAsync(&lt;span style="color: blue"&gt;string &lt;/span&gt;city)
    {
        AsyncManager.OutstandingOperations.Increment();

        &lt;span style="color: blue"&gt;var &lt;/span&gt;newsService = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NewsService&lt;/span&gt;();
        newsService.GetHeadlinesCompleted += (sender, e) =&amp;gt;
        {
            AsyncManager.Parameters[&lt;span style="color: #a31515"&gt;&amp;quot;headlines&amp;quot;&lt;/span&gt;] = e.Value;
            AsyncManager.OutstandingOperations.Decrement();
        };

        newsService.GetHeadlinesAsync(city);
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;NewsCompleted(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] headlines)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;View(&lt;span style="color: #a31515"&gt;&amp;quot;News&amp;quot;&lt;/span&gt;, headlines);
    }
}&lt;/pre&gt;

&lt;p&gt;很显然，异步Action也是标准的二段式调用，不过这个二段式调用却由比较特别的“约定”。在ASP.NET MVC 2中使用异步Action时，首先需要继承AsyncController类，并构造XyzAsync及XyzCompleted两个方法，前者返回void，后者返回ActionResult——这便表示一个异步的Action，名为Xyz。&lt;/p&gt;

&lt;p&gt;ASP.NET MVC 2中对于异步Action的开发也提供了一定支持，这个支持便来自于AsyncManager。在发起异步操作之前，我们可以调用其OutstandingOperations对象的Increment方法，表示需要“进行几次异步操作”。而每次异步操作结束之后，也就是在事件的处理函数中，便会调用对应的Decrement方法。这个方法表示“完成了一次异步操作”，而Decrement至零之后ASP.NET MVC便会得知所有的异步操作已经完成，于是便会调用XynCompleted方法，得到所需的ActionResult对象。&lt;/p&gt;

&lt;p&gt;至于XyzCompleted方法所需要的参数，从代码中便可看出是通过AsyncManager的Parameters集合进行“过渡”的。这里有个不是很理想的地方，便是使用了字符串这种“弱类型”的方式，假设参数名改变，则对应的字符串也需要跟着改变。&lt;/p&gt;

&lt;h1&gt;选择Begin/End还是基于事件的异步模式？&lt;/h1&gt;

&lt;p&gt;很显然，在ASP.NET MVC中使用既可以使用Begin/End或是基于事件的异步编程模式，因为ASP.NET MVC本身只是根据AsyncManager的行为来进行异步操作。不过在ASP.NET MVC中，似乎更看重的是基于事件的异步模式。我估计，这是由于两种异步模式对于异常的行为差异所造成的吧。&lt;/p&gt;

&lt;p&gt;正如我之前所提到的那样，在使用Begin/End异步模式时，如果出现了错误则会在End方法调用时抛出异常。要知道在回调函数中抛出异常是异步编程中最危险的情况（有没有之一？），如果没有正确地进行捕获则会让整个进程崩溃——当然，我们也可以在配置文件中设置成“忽略”，但是这明显也不妥当，例如会造成请求永远无法结束，直至超时，并且有可能造成资源泄露。与之相对，使用基于事件的异步模式则不会出现这个问题，因为在这种情况下，事件一定会被正确调用，而异常则永远安安稳稳地保存在事件参数的Exception属性中。因此，使用Begin/End则需要额外的try...catch进行保护，使用基于事件的异步编程模式则会让代码变得精简一些。&lt;/p&gt;

&lt;p&gt;当然，用着简单，也只是因为那些异常已经被异步操作的“提供方”给处理了。试想，WebClient之所以可以通过事件参数来暴露异常，一定是因为在它内部使用了try...catch。同理，如果我们要实现一个基于事件的异步模式，例如上面的NewsService，那也一定少不了对异常进行仔细处理。&lt;/p&gt;

&lt;p&gt;因此，该来的还是会来，想躲也躲不掉。&lt;/p&gt;

&lt;p&gt;嗯，也该轮到F#登场了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/04/fsharp-for-asp-net-1-event-based-asynchronous-pattern-and-async-action.html#comments</comments>
      <pubDate>Thu, 01 Apr 2010 16:09:50 GMT</pubDate>
      <lastBuildDate>Thu, 01 Apr 2010 16:09:50 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <title>数十行F#打造简易Comet聊天服务</title>
      <link>http://blog.zhaojie.me/2009/12/fsharp-comet-prototype.html</link>
      <guid>http://blog.zhaojie.me/2009/12/fsharp-comet-prototype.html</guid>
      <description>&lt;p&gt;普通的Web应用程序，都是靠大量HTTP短连接维持的。如实现一个聊天服务时，客户端会不断轮询服务器端索要新消息。这种做法的优势在于简单有效，因此广为目前的聊天服务所采用。不过Comet技术与之不同，简单地说，&lt;a href="http://en.wikipedia.org/wiki/Comet_(programming)"&gt;Comet&lt;/a&gt;便是指服务器推（Server-Push）技术。它的实现方式是（这里只讨论基于浏览器的Web平台）在浏览器与服务器之间建立一个长连接，待获得消息之后立即返回。否则持续等待，直至超时。客户端得到消息或超时之后，又会立即建立另一个长连接。Comet技术的最大优势，自然就是很高的即使性。&lt;/p&gt;  &lt;p&gt;如果要在ASP.NET平台上实现Comet技术，那么自然需要在服务器端使用异步请求处理。如果是普通处理方式的话，每个请求都会占用一个工作线程，要知道Comet是“长连接”，因此不需要多少客户端便会占用大量的线程，这对资源消耗是巨大的。如果是异步请求的话，虽然客户端和服务器端之间一直保持着连接，但是客户端在等待消息的时候是不占用线程的，直到“超时”或“消息到达”时才继续执行。&lt;/p&gt;  &lt;p&gt;以前&lt;a href="http://www.codeproject.com/KB/aspnet/CometAsync.aspx"&gt;也有人实现过基于ASP.NET的Comet服务原型&lt;/a&gt;，不过是使用C#的。而现在我们用F#来实现这个功能。您会发现F#对于此类异步场景有其独特的优势。&lt;/p&gt;  &lt;p&gt;F#常用的工作单元是“模块”，其中定义了大量函数或字段。例如我们要打造一个聊天服务的话，我便定义了一个Chat模块：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;#light

module internal &lt;/span&gt;Comet.Chating.Chat

&lt;span style="color: blue"&gt;open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Collections.Concurrent

&lt;span style="color: blue"&gt;type &lt;/span&gt;ChatMsg = {
    From: string;
    Text: string;&lt;span style="color: green"&gt;
&lt;/span&gt;}

&lt;span style="color: blue"&gt;let private &lt;/span&gt;agentCache = &lt;span style="color: blue"&gt;new &lt;/span&gt;ConcurrentDictionary&amp;lt;string, MailboxProcessor&amp;lt;ChatMsg&amp;gt;&amp;gt;()

&lt;span style="color: blue"&gt;let private &lt;/span&gt;agentFactory = &lt;span style="color: blue"&gt;new &lt;/span&gt;Func&amp;lt;string, MailboxProcessor&amp;lt;ChatMsg&amp;gt;&amp;gt;(&lt;span style="color: blue"&gt;fun &lt;/span&gt;_ &lt;span style="color: blue"&gt;-&amp;gt; 
    &lt;/span&gt;MailboxProcessor.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;o &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;async { o |&amp;gt; ignore }))

&lt;span style="color: blue"&gt;let private &lt;/span&gt;GetAgent name = agentCache.GetOrAdd(name, agentFactory)&lt;/pre&gt;

&lt;p&gt;在这里我构建了一个名为ChatMsg的Record类型，一个ChatMsg对象便是一条消息。然后，我使用一个名为agentCache的ConcurrentDictionary对象来保存每个用户所对应的聊天队列——MailboxProcessor。它是F#核心库中内置的，用于实现消息传递式并发的组件，非常轻量级，因此我为每个用户分配一个也只使用很少的资源。GetAgent函数的作用是根据用户的名称获取对应的MailboxProcessor对象，自不必多说。&lt;/p&gt;

&lt;p&gt;Chat模块中还定义了send和receive两个公开方法，如下：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;send fromName toName msg = 
    &lt;span style="color: blue"&gt;let &lt;/span&gt;agent = GetAgent toName&lt;span style="color: green"&gt;
    &lt;/span&gt;{ From = fromName; Text = msg; } |&amp;gt; agent.Post

&lt;span style="color: blue"&gt;let &lt;/span&gt;receive name = 
    &lt;span style="color: blue"&gt;let rec &lt;/span&gt;receive' (agent: MailboxProcessor&amp;lt;ChatMsg&amp;gt;) messages = 
        async {
            &lt;span style="color: blue"&gt;let! &lt;/span&gt;msg = agent.TryReceive &lt;span style="color: brown"&gt;0
            &lt;/span&gt;&lt;span style="color: blue"&gt;match &lt;/span&gt;msg &lt;span style="color: blue"&gt;with
            &lt;/span&gt;| None &lt;span style="color: blue"&gt;-&amp;gt; return &lt;/span&gt;messages
            | Some s &lt;span style="color: blue"&gt;-&amp;gt; return! &lt;/span&gt;receive' agent (s :: messages)
        }

    &lt;span style="color: blue"&gt;let &lt;/span&gt;agent = GetAgent name

    async {
        &lt;span style="color: blue"&gt;let! &lt;/span&gt;messages = receive' agent List.empty
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(not messages.IsEmpty) &lt;span style="color: blue"&gt;then return &lt;/span&gt;messages
        &lt;span style="color: blue"&gt;else
            let! &lt;/span&gt;msg = agent.TryReceive &lt;span style="color: brown"&gt;3000
            &lt;/span&gt;&lt;span style="color: blue"&gt;match &lt;/span&gt;msg &lt;span style="color: blue"&gt;with
            &lt;/span&gt;| None &lt;span style="color: blue"&gt;-&amp;gt; return &lt;/span&gt;[]
            | Some s &lt;span style="color: blue"&gt;-&amp;gt; return &lt;/span&gt;[s]
    }&lt;/pre&gt;

&lt;p&gt;send方法接受3个参数，没有返回值，它的实现只是简单地构造一个ChatMsg对象，并塞入对应的MailboxProcessor。不过receive方法是这里最关键的部分（没有之一）。receive函数的作用是接受并返回MailboxProcessor中已有的对象，或者等待3秒钟后超时——这么说其实不太妥当，因为receive方法其实只是构造了一个“做这件事情”的Async Workflow，而还没有真正执行它。至于它是如何执行的，我们稍候再谈。&lt;/p&gt;

&lt;p&gt;receive函数的逻辑是这样的：首先我们构造一个辅助函数receive’来“尝试获取”队列中已有的所有消息。receive’是一个递归函数，每次获取一个，并递归获取剩余的消息。agent.TryReceive函数接受0，表示查询队列，并立即返回一个Option&amp;lt;ChatMsg&amp;gt;结果，如果这个结果为None，则表示队列已为空。于是在receive这个主函数中，便先使用receive’函数获取已有消息，如果存在则立即返回，否则便接收3秒钟内获得的第一个消息，如果3秒结束还没有收到则返回None。&lt;/p&gt;

&lt;p&gt;在receive和receive’函数中都使用了let!获取agent.TryReceive函数的结果。let!是F#中构造Workflow的关键字，它起到了“语法糖”的作用。例如，以下的Async Workflow：&lt;/p&gt;

&lt;pre class="code"&gt;async {
    &lt;span style="color: blue"&gt;let &lt;/span&gt;req = WebRequest.Create(&lt;span style="color: maroon"&gt;&amp;quot;http://www.cnblogs.com/&amp;quot;&lt;/span&gt;)
    &lt;span style="color: blue"&gt;let! &lt;/span&gt;resp = req.GetResponseAsync()
    &lt;span style="color: blue"&gt;let &lt;/span&gt;stream = resp.GetResponseStream()
    &lt;span style="color: blue"&gt;let &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;StreamReader(stream)
    &lt;span style="color: blue"&gt;let! &lt;/span&gt;html = reader.ReadToEndAsync()
    html
}&lt;/pre&gt;

&lt;p&gt;事实上在“解糖”后就变成了：&lt;/p&gt;

&lt;pre class="code"&gt;async.Delay(&lt;span style="color: blue"&gt;fun &lt;/span&gt;() &lt;span style="color: blue"&gt;-&amp;gt;
    &lt;/span&gt;async.Let(WebRequest.Create(&lt;span style="color: maroon"&gt;&amp;quot;http://www.cnblogs.com/&amp;quot;&lt;/span&gt;), (&lt;span style="color: blue"&gt;fun &lt;/span&gt;req &lt;span style="color: blue"&gt;-&amp;gt;
        &lt;/span&gt;&lt;font color="#ff0000"&gt;async.Bind&lt;/font&gt;(req.GetResponseAsync(), (&lt;span style="color: blue"&gt;fun &lt;/span&gt;resp &lt;span style="color: blue"&gt;-&amp;gt;
            &lt;/span&gt;async.Let(resp.GetResponseStream(), (&lt;span style="color: blue"&gt;fun &lt;/span&gt;stream &lt;span style="color: blue"&gt;-&amp;gt;
                &lt;/span&gt;async.Let(&lt;span style="color: blue"&gt;new &lt;/span&gt;StreamReader(stream), (&lt;span style="color: blue"&gt;fun &lt;/span&gt;reader &lt;span style="color: blue"&gt;-&amp;gt;
                    &lt;/span&gt;&lt;font color="#ff0000"&gt;async.Bind&lt;/font&gt;(reader.ReadToEndAsync(), (&lt;span style="color: blue"&gt;fun &lt;/span&gt;html &lt;span style="color: blue"&gt;-&amp;gt;
                        &lt;/span&gt;async.Return(html))))))))))&lt;/pre&gt;
&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;

&lt;p&gt;let!关键字则会转化为Bind函数调用，Bind调用有两个参数，第一个参数为Async&amp;lt;’a&amp;gt;类型，它便负责一个“回调”，待回调后才执行一个匿名函数——也就是Bind函数的第二个参数。可见，let!关键字的一个重要作用，便是将流程的“控制权”转交给“系统”，待合适的时候再继续执行下去。这便是关键，因为这样的话，在接受一个消息的时候，这等待的3秒钟是不占用任何线程的，也就是真正的纯异步。但是如果观察代码——难道不是纯粹的顺序型写法吗？&lt;/p&gt;

&lt;p&gt;这就是F#的神奇之处。&lt;/p&gt;

&lt;p&gt;在ASP.NET处理时需要Handler，于是在Send阶段便是简单的IHttpHandler：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;#light

namespace &lt;/span&gt;Comet.Chating

&lt;span style="color: blue"&gt;open &lt;/span&gt;Comet
&lt;span style="color: blue"&gt;open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Web

&lt;span style="color: blue"&gt;type &lt;/span&gt;SendHandler() =

    &lt;span style="color: blue"&gt;interface &lt;/span&gt;IHttpHandler &lt;span style="color: blue"&gt;with
        member &lt;/span&gt;h.IsReusable = &lt;span style="color: blue"&gt;false
        member &lt;/span&gt;h.ProcessRequest(context) = 
            &lt;span style="color: blue"&gt;let &lt;/span&gt;fromName = context.Request.Form.Item(&lt;span style="color: maroon"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;);
            &lt;span style="color: blue"&gt;let &lt;/span&gt;toName = context.Request.Form.Item(&lt;span style="color: maroon"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;)
            &lt;span style="color: blue"&gt;let &lt;/span&gt;msg = context.Request.Form.Item(&lt;span style="color: maroon"&gt;&amp;quot;msg&amp;quot;&lt;/span&gt;)
            Chat.send fromName toName msg
            context.Response.Write &lt;span style="color: maroon"&gt;&amp;quot;sent&amp;quot;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;而Receive阶段则是个异步的IHttpAsyncHandler：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;#light

namespace &lt;/span&gt;Comet.Chating

&lt;span style="color: blue"&gt;open &lt;/span&gt;Comet
&lt;span style="color: blue"&gt;open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Collections.Generic
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Web
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Web.Script.Serialization

&lt;span style="color: blue"&gt;type &lt;/span&gt;ReceiveHandler() =

    &lt;span style="color: blue"&gt;let mutable &lt;/span&gt;m_context = &lt;span style="color: blue"&gt;null
    let mutable &lt;/span&gt;m_endReceive = &lt;span style="color: blue"&gt;null

    interface &lt;/span&gt;IHttpAsyncHandler &lt;span style="color: blue"&gt;with
        member &lt;/span&gt;h.IsReusable = &lt;span style="color: blue"&gt;false
        member &lt;/span&gt;h.ProcessRequest(context) = failwith &lt;span style="color: maroon"&gt;&amp;quot;not supported&amp;quot;

        &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;h.BeginProcessRequest(c, cb, state) =
            m_context &amp;lt;- c

            &lt;span style="color: blue"&gt;let &lt;/span&gt;name = c.Request.QueryString.Item(&lt;span style="color: maroon"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;)
            &lt;span style="color: blue"&gt;let &lt;/span&gt;receive = Chat.receive name
            &lt;span style="color: blue"&gt;let &lt;/span&gt;beginReceive, e, _ = &lt;font color="#ff0000"&gt;Async.AsBeginEnd&lt;/font&gt; receive
            m_endReceive &amp;lt;- &lt;span style="color: blue"&gt;new &lt;/span&gt;Func&amp;lt;_, _&amp;gt;(e)

            beginWork (cb, state)

        &lt;span style="color: blue"&gt;member &lt;/span&gt;h.EndProcessRequest(ar) =
            &lt;span style="color: blue"&gt;let &lt;/span&gt;convert (m: Chat.ChatMsg) =
                &lt;span style="color: blue"&gt;let &lt;/span&gt;o = &lt;span style="color: blue"&gt;new &lt;/span&gt;Dictionary&amp;lt;_, _&amp;gt;();
                o.Add(&lt;span style="color: maroon"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;, m.From)
                o.Add(&lt;span style="color: maroon"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;, m.Text)&lt;span style="color: green"&gt;
                &lt;/span&gt;o

            &lt;span style="color: blue"&gt;let &lt;/span&gt;result = m_endReceive.Invoke ar
            &lt;span style="color: blue"&gt;let &lt;/span&gt;serializer = &lt;span style="color: blue"&gt;new &lt;/span&gt;JavaScriptSerializer()
            result
            |&amp;gt; List.map convert
            |&amp;gt; serializer.Serialize
            |&amp;gt; m_context.Response.Write&lt;/pre&gt;

&lt;p&gt;这里的关键是Async.AsBeginEnd函数，它将Chat.receive函数生成的Async Workflow转化成一组标准APM形式的begin/end对，然后我们只要把BeginProcessRequest和EndProcessReqeust的职责直接交给即可。剩下的，便是一些序列化成JSON的工作了。&lt;/p&gt;
&lt;script language="javascript" type="text/javascript"&gt;
function __openDemo__() {
    var uid = "u_" + Math.round(Math.random() * 100000) + "_" + new Date().getTime();
    window.open("http://51programming.com/Chat.aspx?name=" + uid);
}
&lt;/script&gt;

&lt;p&gt;于是我们可以新建一个Web项目，引用F#工程，在Web.config里配置两个Handler，再准备一个Chat.aspx页面即可。您可以在文末的链接中查看该页面的代码，&lt;a href="javascript:__openDemo__()"&gt;也可以在这里试用其效果&lt;/a&gt;。作为演示页面，您其实只能“自己给自己”发送消息，其主要目的是查看其响应时间而已。例如，以下便是使用效果一例：&lt;/p&gt;

&lt;pre class="code"&gt;2 - receiving...
3026 - received nothing (3024ms)
3026 - receiving...
6055 - received nothing (3028ms)
6055 - receiving...
7256 - sending 123654...
7268 - received: 123654 (&lt;font color="#ff0000"&gt;1213ms&lt;/font&gt;)
7268 - receiving...
10281 - received nothing (3013ms)
10281 - receiving...
13298 - received nothing (3017ms)
13298 - receiving...
13679 - sending 123456...
13698 - received: 123456 (&lt;font color="#ff0000"&gt;400ms&lt;/font&gt;)
13698 - receiving...
16716 - received nothing (3018ms)
16716 - receiving...
18256 - sending hello world...
18265 - received: hello world (&lt;font color="#ff0000"&gt;1549ms&lt;/font&gt;)
18266 - receiving...
21281 - received nothing (3015ms)
21281 - receiving...&lt;/pre&gt;

&lt;p&gt;可见，如果没有收到消息，那么receive操作会在3秒钟后返回。当send一条消息后，先前的receive操作便会立即获得消息了，即无需等待3秒便可提前返回。这便是Comet的效果。&lt;/p&gt;

&lt;p&gt;至于性能，我写了一个客户端小程序，用于模拟大量用户同时聊天，每个用户每隔1秒便给另外5个用户发送一条消息，然后查看这条消息收到时产生多少的延迟。经过本机测试（2.4GHz双核，2G内存），当超过2K个在线用户时（即2000个长连接）延迟便超过了1秒——到20K还差不多。这个性能其实并不理想。不过，我这个测试也很一般。因为测试环境相当马虎，大量程序（如N个VS）基本上已经完全用满了所有的物理内存，测试客户端和服务器也是同一台机器，甚至代码也是Debug编译的……而根据监视，测试用的客户端小程序CPU占用超过50%，而服务器进程对应的w3wp.exe的CPU占用却小于10%。因此，我们可以这样推断，其实服务器端的性能并没有用足，也有可能是MailboxProcessor的调度方式不甚理想。至于具体是什么原因，我还在调查之中。&lt;/p&gt;

&lt;p&gt;最后我想说的是，这个Comet实现只是一个原型，我最想说明的问题其实是F#在异步编程中的优势。目前我写的一些程序，例如一些网络爬虫，都已经使用F#进行开发了，因为它的Async Workflow实在是过于好用，为我省了太多力气。同时我还想证明，“语言特性”并非不重要，它对于编程的简化也是至关重要的。在我看来，“类库”也好，“框架”也罢都是可以补充的，但是语言特性是个无法突破的“限制”。例如，异步编程对于F#来说简化了不少，这是因为我们可以使用顺序的方式编写异步程序。在C#中略有不足，但还有yield可以起到相当作用，因此我们可以&lt;a href="http://blog.zhaojie.me/2009/02/simplify-async-programming-1.html"&gt;使用CCR和AsyncEnumerator简化异步操作&lt;/a&gt;。但如果您使用的是Java这种劣质语言……因此，放弃Java，使用Scala吧。&lt;/p&gt;

&lt;p&gt;值得一提的是，Async Workflow并不是F#的语言特性，F#的语言特性是Workflow，而Async Workflow其实只是实现了一个Workflow Builder，也就是那个async { ... }，以此来简化异步编程而已。PDC 09上关于F#对异步编程的支持&lt;a href="http://www.infoq.com/cn/news/2009/11/pdc09-fsharp"&gt;也有相应的介绍&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://gist.github.com/253968"&gt;本文代码&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/12/fsharp-comet-prototype.html#comments</comments>
      <pubDate>Fri, 11 Dec 2009 04:00:00 GMT</pubDate>
      <lastBuildDate>Fri, 11 Dec 2009 04:00:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>Route组件GetVirtualPath方法性能优化结果</title>
      <link>http://blog.zhaojie.me/2009/12/route-getvirtualpath-optimization.html</link>
      <guid>http://blog.zhaojie.me/2009/12/route-getvirtualpath-optimization.html</guid>
      <description>&lt;p&gt;由于使用&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;Lambda表达式生成URL的方式性能较差&lt;/a&gt;，因此我使用&lt;a href="http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html"&gt;Fluent Interface来代替原有的Lambda表达式构建方式&lt;/a&gt;。Fluent Interface主要对生成URL的前两个阶段（创建对象及分析对象）进行了优化，分别带来了超过2/3和1/2的性能优化，但因为最后一步，也就是使用Route对象的GetVirtualPath方法构造URL的性能没有提高，因此总体性能只提高了30%。于是我打算重新实现GetVirtualPath方法，希望得到更好的性能。&lt;/p&gt;  &lt;p&gt;我的实现放在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;中MvcPatch.Routing项目里的FastRoute类。FastRoute的使用与ASP.NET Routing自带的Route比较相似，可以通过URL Pattern，Defaults及Constraints构造一个对象，而实际使用过程中可以通过MvcPatch.Extensions项目中定义的MapFast扩展方法进行注册。FastRoute将GetRouteData方法直接委托给内部的Route对象，但是重新实现了一个性能较高的GetVirtualPath方法。&lt;/p&gt;  &lt;p&gt;说出来有些好笑，事实上我并没有完全理解GetVirtualPath的行为，我只是让GetVirtualPath方法通过了一些Test Case而已，这些Case的来源是我的“期望”以及微软官方，及我实际项目中所总结出来的使用方式。从目前看来，应该没有太大问题，最有可能出现问题的原因是我对Route对象本身的“设想”有所偏差，但它的确满足我目前的实际需求。&lt;/p&gt;  &lt;p&gt;此外，在原版的Route对象中，Constraints集合支持两种约束方式，即“正则表达式”及“IRouteConstraint对象”。前者为开发人员提供的一个字符串，效果自不必说，而后者则是一个实现IRouteConstraint接口的对象。这个接口有一个Match方法，会将用于构造数据的RouteValueDictionary集合传入该方法，并返回一个布尔值表明是否满足该约束。它对性能的伤害之处需要完整复制一份的RouteValueDictionary（因为我们无法保证Match方法不会修改这个集合）。由于实际情况下我还从来没有真正使用过这个功能，因此将其抛弃。不过，如果真实现这个功能的话性能应该也不会差太多，因为假如开发人员不提供此类约束方式，我们不去复制该集合即可。&lt;/p&gt;  &lt;p&gt;FastRoute的GetVirtualPath方法性能提升主要在三个方面：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;strong&gt;验证及字符串拼接同时进行：&lt;/strong&gt;在Route类中，每次Match方法都会先验证一遍传入的数据是否满足规则，如果不满足则直接返回null，然后再根据默认值和用户提供的值进行合并得到最终使用的集合，再进行拼接。这种方式较为“美观”但由于需要多次遍历，并构造并填充一些额外的RouteValueDictionary，因此虽然时间复杂度没有提高，但是影响性能的常数较大。由于项目中基本上都是通过名称直接找到RouteBase对象，因此不会失败，一边验证一边拼接的做法性能较高。 &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;使用快速的索引方式：&lt;/strong&gt;Route类中的索引使用了字典，而FastRoute使用了较为丑陋的方式，将URL Pattern拆分为多个Token，并使用多个数组保存Token的信息，例如当前Token是个常量还是个占位符。字典本身性能已经很高了，但是使用数组保存数据后，可以直接通过下标来获取Token的信息，一边读取，一边填充目标数组（见下一条）。 &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;使用高效的字符串拼接方式：&lt;/strong&gt;Route类使用了StringBuilder进行字符串拼接，而FastRoute使用了&lt;a href="http://blog.zhaojie.me/2009/11/string-concat-perf-1-benchmark.html"&gt;性能更高的String.Concat方法&lt;/a&gt;。由于URL Pattern的Token数量以及需要多少Query String都是在拼接前可以分析出来的，因此FastRoute在生成字符串之前便已经确定了整个字符串数组的长度。然后便是从后向前逆序填充数组内容，这是因为URL靠后的部分可能需要省略，这样便在数组的那个位置填入空字符串即可。 &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;总之，FastRoute的性能优势在于精打细算每步操作，并根据数据特征选择相对高效的做法。不过目前的实现非常丑陋，我会继续进行一些调整和改进。使用FastRoute后，四种生成方式的耗时如下：&lt;/p&gt; &lt;iframe height="300" src="https://spreadsheets.google.com/pub?key=tKjqmrvUdjU-Q6ugVjCSTPg&amp;amp;single=true&amp;amp;gid=2&amp;amp;output=html&amp;amp;widget=true" frameborder="0" width="100%"&gt;&lt;/iframe&gt;  &lt;p&gt;您可以将它和&lt;a href="http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html"&gt;上次的结果&lt;/a&gt;进行对比，如果将Raw的性能视为100，则使用Route和FastRoute时其他3种方法的性能得分便是：&lt;/p&gt;  &lt;table style="text-align: center" border="1" cellspacing="0" cellpadding="5"&gt;&lt;thead&gt;     &lt;tr&gt;       &lt;th&gt;&amp;#160;&lt;/th&gt;        &lt;th&gt;Route&lt;/th&gt;        &lt;th&gt;FastRoute&lt;/th&gt;     &lt;/tr&gt;   &lt;/thead&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;th&gt;Route&lt;/th&gt;        &lt;td&gt;19.47167118&lt;/td&gt;        &lt;td&gt;47.14237983&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;th&gt;Lambda&lt;/th&gt;        &lt;td&gt;8.208318357&lt;/td&gt;        &lt;td&gt;11.82049872&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;th&gt;Fluent&lt;/th&gt;        &lt;td&gt;12.37249537&lt;/td&gt;        &lt;td&gt;21.76159473&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;绘制成图表似乎更加一目了然：&lt;/p&gt; &lt;img src="https://spreadsheets.google.com/pub?key=tKjqmrvUdjU-Q6ugVjCSTPg&amp;amp;oid=4&amp;amp;output=image" /&gt;  &lt;p&gt;可见，FastRoute让纯粹通过Route构造URL的做法性能提高了1倍多，而使用Lambda表达式和Fluent的做法也有较为明显的性能提升。值得一提的是，Fluent + FastRoute的性能已经超过了原有Route的做法，这意味着如果&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;之前Route构造方式的性能能够令人接受的话&lt;/a&gt;，则目前Fluent方式的性能也已够用了。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cid-fba4447598b1d752.skydrive.live.com/self.aspx/Public/UrlGenBenchmark%5E_V3.zip"&gt;测试项目下载&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html"&gt;各种URL生成方式的性能对比&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;各种URL生成方式的性能对比（结论及分析）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html"&gt;为URL生成设计流畅接口（Fluent Interface）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html"&gt;URL生成方式性能优化结果&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;Route组件GetVirtualPath方法性能优化结果&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/12/route-getvirtualpath-optimization.html#comments</comments>
      <pubDate>Mon, 07 Dec 2009 17:32:00 GMT</pubDate>
      <lastBuildDate>Mon, 07 Dec 2009 17:32:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>真有必要去除HTML中的空白字符吗？</title>
      <link>http://blog.zhaojie.me/2009/12/is-it-really-necessary-to-strip-white-space-in-html.html</link>
      <guid>http://blog.zhaojie.me/2009/12/is-it-really-necessary-to-strip-white-space-in-html.html</guid>
      <description>&lt;p&gt;刚才有朋友在MSN上问我说，他的页面中有许多空白字符，打开源文件一看发现这代码稀疏得很。他觉得很浪费，他说有什么办法可以去除它们。我问他“你的页面使用GZip压缩了吗？”他说用了，于是我回答说“那么就不用去除空白字符了，连续空白字符压缩得很好，去掉后效果不大的”。这时我又不禁想到早上那篇《&lt;a href="http://www.cnblogs.com/cmt/archive/2009/12/07/1618301.html"&gt;博客园首页优化心得&lt;/a&gt;》中也有一条是“去除HTML中的空格、空行”，于是我便打算尝试一下，去除空白字符到底有多少效果。&lt;/p&gt;  &lt;p&gt;我的实验目标是我博客的前40篇文章的详细页，未压缩前的体积从98K到277K不等，我想也应该算是博客园中比较典型的页面大小吧。我使用这样的测试代码：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;files = &lt;span style="color: #2b91af"&gt;Directory&lt;/span&gt;.GetFiles(&lt;span style="color: #a31515"&gt;&amp;#64;&amp;quot;d:\pages&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;f &lt;span style="color: blue"&gt;in &lt;/span&gt;files) Compare(&lt;span style="color: #2b91af"&gt;File&lt;/span&gt;.ReadAllText(f));

    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;Press enter to exit...&amp;quot;&lt;/span&gt;);
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.ReadLine();
}

&lt;span style="color: blue"&gt;private static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex &lt;/span&gt;REGEX_LINE_BREAKS = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;#64;&amp;quot;\n\s*&amp;quot;&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;RegexOptions&lt;/span&gt;.Compiled);
&lt;span style="color: blue"&gt;private static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex &lt;/span&gt;REGEX_LINE_SPACE = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;#64;&amp;quot;\n\s*\r&amp;quot;&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;RegexOptions&lt;/span&gt;.Compiled);
&lt;span style="color: blue"&gt;private static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex &lt;/span&gt;REGEX_SPACE = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;#64;&amp;quot;( )+&amp;quot;&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;RegexOptions&lt;/span&gt;.Compiled);

&lt;span style="color: blue"&gt;private static void &lt;/span&gt;Compare(&lt;span style="color: blue"&gt;string &lt;/span&gt;html)
{
    &lt;span style="color: blue"&gt;long &lt;/span&gt;original, compressedOriginal;
    GetLength(html, &lt;span style="color: blue"&gt;out &lt;/span&gt;original, &lt;span style="color: blue"&gt;out &lt;/span&gt;compressedOriginal);

    &lt;span style="color: blue"&gt;long &lt;/span&gt;stripped, compressedStripped;
    html = REGEX_LINE_BREAKS.Replace(html, &lt;span style="color: #a31515"&gt;&amp;quot;&amp;quot;&lt;/span&gt;);
    html = REGEX_LINE_SPACE.Replace(html, &lt;span style="color: #a31515"&gt;&amp;quot;&amp;quot;&lt;/span&gt;);
    html = REGEX_SPACE.Replace(html, &lt;span style="color: #a31515"&gt;&amp;quot; &amp;quot;&lt;/span&gt;);
    GetLength(html, &lt;span style="color: blue"&gt;out &lt;/span&gt;stripped, &lt;span style="color: blue"&gt;out &lt;/span&gt;compressedStripped);

    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;{0}\t{1}\t{2}\t{3}\t{4}\t{5}&amp;quot;&lt;/span&gt;,
        original, compressedOriginal,
        stripped, compressedStripped,
        original - stripped, compressedOriginal - compressedStripped);
}

&lt;span style="color: blue"&gt;private static void &lt;/span&gt;GetLength(&lt;span style="color: blue"&gt;string &lt;/span&gt;html, &lt;span style="color: blue"&gt;out long &lt;/span&gt;original, &lt;span style="color: blue"&gt;out long &lt;/span&gt;compressed)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;bytes = &lt;span style="color: #2b91af"&gt;Encoding&lt;/span&gt;.UTF8.GetBytes(html);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;stream = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MemoryStream&lt;/span&gt;();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;gzipStream = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;GZipStream&lt;/span&gt;(stream, &lt;span style="color: #2b91af"&gt;CompressionMode&lt;/span&gt;.Compress);
    gzipStream.Write(bytes, 0, bytes.Length);
    gzipStream.Flush();

    original = bytes.LongLength;
    compressed = stream.Length;
}&lt;/pre&gt;

&lt;p&gt;在上面这段代码里我使用的是博客园去空白字符的方法，结果如下：&lt;/p&gt;

&lt;table style="text-align: center" cellpadding="5" border="1" cellspacing="0"&gt;&lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;原页面&lt;/th&gt;

      &lt;th&gt;原页面（压缩）&lt;/th&gt;

      &lt;th&gt;去空白后&lt;/th&gt;

      &lt;th&gt;去空白后（压缩）&lt;/th&gt;

      &lt;th&gt;去空白前后&lt;/th&gt;

      &lt;th&gt;去空白前后（压缩）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;&lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;130760&lt;/td&gt;

      &lt;td&gt;36018&lt;/td&gt;

      &lt;td&gt;117354&lt;/td&gt;

      &lt;td&gt;34702&lt;/td&gt;

      &lt;td&gt;13406&lt;/td&gt;

      &lt;td&gt;1316&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;255935&lt;/td&gt;

      &lt;td&gt;63406&lt;/td&gt;

      &lt;td&gt;240433&lt;/td&gt;

      &lt;td&gt;61870&lt;/td&gt;

      &lt;td&gt;15502&lt;/td&gt;

      &lt;td&gt;1536&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;278871&lt;/td&gt;

      &lt;td&gt;86794&lt;/td&gt;

      &lt;td&gt;263704&lt;/td&gt;

      &lt;td&gt;85298&lt;/td&gt;

      &lt;td&gt;15167&lt;/td&gt;

      &lt;td&gt;1496&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;221248&lt;/td&gt;

      &lt;td&gt;53148&lt;/td&gt;

      &lt;td&gt;205440&lt;/td&gt;

      &lt;td&gt;51548&lt;/td&gt;

      &lt;td&gt;15808&lt;/td&gt;

      &lt;td&gt;1600&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;151612&lt;/td&gt;

      &lt;td&gt;40260&lt;/td&gt;

      &lt;td&gt;137939&lt;/td&gt;

      &lt;td&gt;38940&lt;/td&gt;

      &lt;td&gt;13673&lt;/td&gt;

      &lt;td&gt;1320&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;135019&lt;/td&gt;

      &lt;td&gt;36000&lt;/td&gt;

      &lt;td&gt;121593&lt;/td&gt;

      &lt;td&gt;34750&lt;/td&gt;

      &lt;td&gt;13426&lt;/td&gt;

      &lt;td&gt;1250&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;128239&lt;/td&gt;

      &lt;td&gt;36230&lt;/td&gt;

      &lt;td&gt;114658&lt;/td&gt;

      &lt;td&gt;34878&lt;/td&gt;

      &lt;td&gt;13581&lt;/td&gt;

      &lt;td&gt;1352&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;161530&lt;/td&gt;

      &lt;td&gt;42776&lt;/td&gt;

      &lt;td&gt;147189&lt;/td&gt;

      &lt;td&gt;41392&lt;/td&gt;

      &lt;td&gt;14341&lt;/td&gt;

      &lt;td&gt;1384&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;99884&lt;/td&gt;

      &lt;td&gt;28372&lt;/td&gt;

      &lt;td&gt;87047&lt;/td&gt;

      &lt;td&gt;27084&lt;/td&gt;

      &lt;td&gt;12837&lt;/td&gt;

      &lt;td&gt;1288&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;173534&lt;/td&gt;

      &lt;td&gt;43724&lt;/td&gt;

      &lt;td&gt;158446&lt;/td&gt;

      &lt;td&gt;42272&lt;/td&gt;

      &lt;td&gt;15088&lt;/td&gt;

      &lt;td&gt;1452&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;191519&lt;/td&gt;

      &lt;td&gt;50398&lt;/td&gt;

      &lt;td&gt;176958&lt;/td&gt;

      &lt;td&gt;48888&lt;/td&gt;

      &lt;td&gt;14561&lt;/td&gt;

      &lt;td&gt;1510&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;176996&lt;/td&gt;

      &lt;td&gt;40274&lt;/td&gt;

      &lt;td&gt;162706&lt;/td&gt;

      &lt;td&gt;38978&lt;/td&gt;

      &lt;td&gt;14290&lt;/td&gt;

      &lt;td&gt;1296&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;206348&lt;/td&gt;

      &lt;td&gt;47362&lt;/td&gt;

      &lt;td&gt;191400&lt;/td&gt;

      &lt;td&gt;45964&lt;/td&gt;

      &lt;td&gt;14948&lt;/td&gt;

      &lt;td&gt;1398&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;137014&lt;/td&gt;

      &lt;td&gt;38608&lt;/td&gt;

      &lt;td&gt;122855&lt;/td&gt;

      &lt;td&gt;37076&lt;/td&gt;

      &lt;td&gt;14159&lt;/td&gt;

      &lt;td&gt;1532&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;144715&lt;/td&gt;

      &lt;td&gt;37260&lt;/td&gt;

      &lt;td&gt;131097&lt;/td&gt;

      &lt;td&gt;35748&lt;/td&gt;

      &lt;td&gt;13618&lt;/td&gt;

      &lt;td&gt;1512&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;146531&lt;/td&gt;

      &lt;td&gt;36704&lt;/td&gt;

      &lt;td&gt;132619&lt;/td&gt;

      &lt;td&gt;35302&lt;/td&gt;

      &lt;td&gt;13912&lt;/td&gt;

      &lt;td&gt;1402&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;199915&lt;/td&gt;

      &lt;td&gt;49224&lt;/td&gt;

      &lt;td&gt;182227&lt;/td&gt;

      &lt;td&gt;47452&lt;/td&gt;

      &lt;td&gt;17688&lt;/td&gt;

      &lt;td&gt;1772&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;106929&lt;/td&gt;

      &lt;td&gt;29850&lt;/td&gt;

      &lt;td&gt;93690&lt;/td&gt;

      &lt;td&gt;28518&lt;/td&gt;

      &lt;td&gt;13239&lt;/td&gt;

      &lt;td&gt;1332&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;136264&lt;/td&gt;

      &lt;td&gt;36664&lt;/td&gt;

      &lt;td&gt;121548&lt;/td&gt;

      &lt;td&gt;34990&lt;/td&gt;

      &lt;td&gt;14716&lt;/td&gt;

      &lt;td&gt;1674&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;148750&lt;/td&gt;

      &lt;td&gt;37990&lt;/td&gt;

      &lt;td&gt;134567&lt;/td&gt;

      &lt;td&gt;36578&lt;/td&gt;

      &lt;td&gt;14183&lt;/td&gt;

      &lt;td&gt;1412&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;282886&lt;/td&gt;

      &lt;td&gt;71924&lt;/td&gt;

      &lt;td&gt;266336&lt;/td&gt;

      &lt;td&gt;70306&lt;/td&gt;

      &lt;td&gt;16550&lt;/td&gt;

      &lt;td&gt;1618&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;176099&lt;/td&gt;

      &lt;td&gt;41468&lt;/td&gt;

      &lt;td&gt;161322&lt;/td&gt;

      &lt;td&gt;40126&lt;/td&gt;

      &lt;td&gt;14777&lt;/td&gt;

      &lt;td&gt;1342&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;108394&lt;/td&gt;

      &lt;td&gt;30456&lt;/td&gt;

      &lt;td&gt;95428&lt;/td&gt;

      &lt;td&gt;29216&lt;/td&gt;

      &lt;td&gt;12966&lt;/td&gt;

      &lt;td&gt;1240&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;152578&lt;/td&gt;

      &lt;td&gt;40186&lt;/td&gt;

      &lt;td&gt;138543&lt;/td&gt;

      &lt;td&gt;38866&lt;/td&gt;

      &lt;td&gt;14035&lt;/td&gt;

      &lt;td&gt;1320&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;230243&lt;/td&gt;

      &lt;td&gt;59970&lt;/td&gt;

      &lt;td&gt;215389&lt;/td&gt;

      &lt;td&gt;58554&lt;/td&gt;

      &lt;td&gt;14854&lt;/td&gt;

      &lt;td&gt;1416&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;251183&lt;/td&gt;

      &lt;td&gt;57156&lt;/td&gt;

      &lt;td&gt;234862&lt;/td&gt;

      &lt;td&gt;55694&lt;/td&gt;

      &lt;td&gt;16321&lt;/td&gt;

      &lt;td&gt;1462&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;196957&lt;/td&gt;

      &lt;td&gt;48176&lt;/td&gt;

      &lt;td&gt;181608&lt;/td&gt;

      &lt;td&gt;46776&lt;/td&gt;

      &lt;td&gt;15349&lt;/td&gt;

      &lt;td&gt;1400&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;172267&lt;/td&gt;

      &lt;td&gt;41340&lt;/td&gt;

      &lt;td&gt;158105&lt;/td&gt;

      &lt;td&gt;40056&lt;/td&gt;

      &lt;td&gt;14162&lt;/td&gt;

      &lt;td&gt;1284&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;265877&lt;/td&gt;

      &lt;td&gt;63650&lt;/td&gt;

      &lt;td&gt;248974&lt;/td&gt;

      &lt;td&gt;62142&lt;/td&gt;

      &lt;td&gt;16903&lt;/td&gt;

      &lt;td&gt;1508&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;147403&lt;/td&gt;

      &lt;td&gt;38894&lt;/td&gt;

      &lt;td&gt;133751&lt;/td&gt;

      &lt;td&gt;37492&lt;/td&gt;

      &lt;td&gt;13652&lt;/td&gt;

      &lt;td&gt;1402&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;149091&lt;/td&gt;

      &lt;td&gt;36460&lt;/td&gt;

      &lt;td&gt;134998&lt;/td&gt;

      &lt;td&gt;35190&lt;/td&gt;

      &lt;td&gt;14093&lt;/td&gt;

      &lt;td&gt;1270&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;167741&lt;/td&gt;

      &lt;td&gt;43200&lt;/td&gt;

      &lt;td&gt;153614&lt;/td&gt;

      &lt;td&gt;41856&lt;/td&gt;

      &lt;td&gt;14127&lt;/td&gt;

      &lt;td&gt;1344&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;171564&lt;/td&gt;

      &lt;td&gt;40898&lt;/td&gt;

      &lt;td&gt;157333&lt;/td&gt;

      &lt;td&gt;39648&lt;/td&gt;

      &lt;td&gt;14231&lt;/td&gt;

      &lt;td&gt;1250&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;125812&lt;/td&gt;

      &lt;td&gt;34570&lt;/td&gt;

      &lt;td&gt;111047&lt;/td&gt;

      &lt;td&gt;33200&lt;/td&gt;

      &lt;td&gt;14765&lt;/td&gt;

      &lt;td&gt;1370&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;190649&lt;/td&gt;

      &lt;td&gt;46524&lt;/td&gt;

      &lt;td&gt;175197&lt;/td&gt;

      &lt;td&gt;45040&lt;/td&gt;

      &lt;td&gt;15452&lt;/td&gt;

      &lt;td&gt;1484&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;153807&lt;/td&gt;

      &lt;td&gt;39462&lt;/td&gt;

      &lt;td&gt;139401&lt;/td&gt;

      &lt;td&gt;38054&lt;/td&gt;

      &lt;td&gt;14406&lt;/td&gt;

      &lt;td&gt;1408&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;120788&lt;/td&gt;

      &lt;td&gt;32228&lt;/td&gt;

      &lt;td&gt;107534&lt;/td&gt;

      &lt;td&gt;30930&lt;/td&gt;

      &lt;td&gt;13254&lt;/td&gt;

      &lt;td&gt;1298&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;163327&lt;/td&gt;

      &lt;td&gt;41110&lt;/td&gt;

      &lt;td&gt;148763&lt;/td&gt;

      &lt;td&gt;39710&lt;/td&gt;

      &lt;td&gt;14564&lt;/td&gt;

      &lt;td&gt;1400&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;103101&lt;/td&gt;

      &lt;td&gt;29476&lt;/td&gt;

      &lt;td&gt;90284&lt;/td&gt;

      &lt;td&gt;28222&lt;/td&gt;

      &lt;td&gt;12817&lt;/td&gt;

      &lt;td&gt;1254&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;141384&lt;/td&gt;

      &lt;td&gt;39784&lt;/td&gt;

      &lt;td&gt;126641&lt;/td&gt;

      &lt;td&gt;38350&lt;/td&gt;

      &lt;td&gt;14743&lt;/td&gt;

      &lt;td&gt;1434&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;值得关注的是最后两列，从中我们可以发现，虽然去除空白前后可以让页面体积减小十几K，但是经过压缩后其实只相差1-2K了——大约1-2个数据包。这些节省是否值得我们去这么做？再者，博客园的做法是为每个页面的内容使用正则表达式进行替换，那么它所带来开销又是否值得呢？这就要博客园自己进行profiling了……&lt;/p&gt;

&lt;p&gt;最后一提，其实去空白字符并非如此简单一件事情。最简单的例子是：您是否遇到某些HTML编辑器或RSS阅读器，它们会让文章中原本工整的代码变成一行？这就是因为它们武断地去除了所有的空白字符，但是忘了有个HTML标记叫做&amp;lt;pre /&amp;gt;……&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/12/is-it-really-necessary-to-strip-white-space-in-html.html#comments</comments>
      <pubDate>Mon, 07 Dec 2009 09:59:00 GMT</pubDate>
      <lastBuildDate>Mon, 07 Dec 2009 09:59:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <title>jQuery Validation插件remote验证方式的Bug</title>
      <link>http://blog.zhaojie.me/2009/12/jquery-validate-remote-bug.html</link>
      <guid>http://blog.zhaojie.me/2009/12/jquery-validate-remote-bug.html</guid>
      <description>&lt;p&gt;jQuery插件很多，其中一个重要的插件便是&lt;a href="http://plugins.jquery.com/project/validate"&gt;jQuery Validation&lt;/a&gt;，它的作用是对表单进行验证，还上了jQuery官网。不过奇怪的是，最近用下来感觉有些古怪，因为好像有些死板，已有功能的应变能力还不强，甚至还有个奇怪的Bug。任何项目有Bug其实也正常，但这个Bug其实是一个文档上已经记载了，却没有实现的功能，这就有些说不过去了。这个问题便出在remote验证方式上，还好修改起来非常容易，在此记录一下，也方便以后的参考。&lt;/p&gt;  &lt;p&gt;在表单验证时，有时候会需要发一个AJAX请求去服务器上进行判断，例如在用户注册时检查用户名是否存在。jQuery Validation插件提供了一种remote方式来实现这一点。例如我可以这样验证表单：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;form &lt;/span&gt;&lt;span style="color: red"&gt;id&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;regForm&amp;quot;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;text&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;userName&amp;quot; /&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;form&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;

&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;script &lt;/span&gt;&lt;span style="color: red"&gt;language&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;javascript&amp;quot;&amp;gt;
    &lt;/span&gt;$(&lt;span style="color: #a31515"&gt;'#regForm'&lt;/span&gt;).validate({
        &lt;span style="color: #a31515"&gt;'rules'&lt;/span&gt;: {
            &lt;span style="color: #a31515"&gt;'userName'&lt;/span&gt;: {
                &lt;span style="color: #a31515"&gt;'required'&lt;/span&gt;: &lt;span style="color: blue"&gt;true&lt;/span&gt;,
                &lt;span style="color: #a31515"&gt;'remote'&lt;/span&gt;: &lt;span style="color: #a31515"&gt;'/account/verify'
            &lt;/span&gt;}});
&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;script&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;这样，jQuery Validation便会请求“/account/verify?userName=jeffz”这样的URL来获取true/false。可惜的是，我们在使用ASP.NET MVC时，往往会将input的name写为特定的形式，目的是利用DefaultModelBinder的强大绑定功能。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;form &lt;/span&gt;&lt;span style="color: red"&gt;id&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;regForm&amp;quot;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;text&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;id&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;userName&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;user.Name&amp;quot; /&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;form&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;与此同时，我们用来进行验证的Action方法，它的参数名可能也有所不同：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Verify(&lt;span style="color: blue"&gt;string &lt;/span&gt;name) { ... }&lt;/pre&gt;

&lt;p&gt;根据&lt;a href="http://docs.jquery.com/Plugins/Validation/Methods/remote#options"&gt;文档描述&lt;/a&gt;，此时我们应该这样写：&lt;/p&gt;

&lt;pre class="code"&gt;$(&lt;span style="color: #a31515"&gt;'#regForm'&lt;/span&gt;).validate({
    &lt;span style="color: #a31515"&gt;'rules'&lt;/span&gt;: {
        &lt;span style="color: #a31515"&gt;'user.Name'&lt;/span&gt;: {
            &lt;span style="color: #a31515"&gt;'remote'&lt;/span&gt;: {
                url: &lt;span style="color: #a31515"&gt;'/account/verify'&lt;/span&gt;,
                data: {
                    name: &lt;span style="color: blue"&gt;function&lt;/span&gt;() { &lt;span style="color: blue"&gt;return &lt;/span&gt;$(&lt;span style="color: #a31515"&gt;&amp;quot;#userName&amp;quot;&lt;/span&gt;).val(); }
                }}}}});&lt;/pre&gt;

&lt;p&gt;可是，从实际效果来看，jQuery还是在请求“/account/verify?user.Name=jeffz”，百思不得其解。确认在三之后只得求助于jquery.validation.js源码，看后差点晕过去：&lt;/p&gt;

&lt;pre class="code"&gt;remote: &lt;span style="color: blue"&gt;function&lt;/span&gt;(value, element, param) {
    &lt;span style="color: blue"&gt;if &lt;/span&gt;( &lt;span style="color: blue"&gt;this&lt;/span&gt;.optional(element) )
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;dependency-mismatch&amp;quot;&lt;/span&gt;;
    
    ...
    
    param = &lt;span style="color: blue"&gt;typeof &lt;/span&gt;param == &lt;span style="color: #a31515"&gt;&amp;quot;string&amp;quot; &lt;/span&gt;&amp;amp;&amp;amp; {url:param} || param; 
    
    &lt;span style="color: blue"&gt;if &lt;/span&gt;( previous.old !== value ) {
        previous.old = value;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;validator = &lt;span style="color: blue"&gt;this&lt;/span&gt;;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.startRequest(element);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;data = {}; 
        data[element.name] = value; &lt;span style="color: green"&gt;// data还是以element.name为准？
        &lt;/span&gt;$.ajax($.extend(&lt;span style="color: blue"&gt;true&lt;/span&gt;, {
            url: param,&lt;span style="color: green"&gt;
            &lt;/span&gt;mode: &lt;span style="color: #a31515"&gt;&amp;quot;abort&amp;quot;&lt;/span&gt;,
            port: &lt;span style="color: #a31515"&gt;&amp;quot;validate&amp;quot; &lt;/span&gt;+ element.name,
            dataType: &lt;span style="color: #a31515"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;,
            data: data,
            success: &lt;span style="color: blue"&gt;function&lt;/span&gt;(response) {
                ...&lt;/pre&gt;

&lt;p&gt;我很奇怪，不知道为什么会这样做，这样根本没有起到指定参数名的作用。那么，改吧：&lt;/p&gt;

&lt;pre class="code"&gt;remote: &lt;span style="color: blue"&gt;function&lt;/span&gt;(value, element, param) {
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.optional(element))
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;dependency-mismatch&amp;quot;&lt;/span&gt;;

    ...

    param = &lt;span style="color: blue"&gt;typeof &lt;/span&gt; param == &lt;span style="color: #a31515"&gt;&amp;quot;string&amp;quot;&lt;/span&gt; &amp;amp;&amp;amp; {url:param} || param;

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(previous.old !== value) {
        previous.old = value;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;validator = &lt;span style="color: blue"&gt;this&lt;/span&gt;;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.startRequest(element);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;data = {};
        data[element.name] = value;
        $.ajax($.extend(&lt;span style="color: blue"&gt;true&lt;/span&gt;, {
            &lt;span style="color: green"&gt;// url: param,
            &lt;/span&gt;url: param.url,
            mode: &lt;span style="color: #a31515"&gt;&amp;quot;abort&amp;quot;&lt;/span&gt;,
            port: &lt;span style="color: #a31515"&gt;&amp;quot;validate&amp;quot; &lt;/span&gt;+ element.name,
            dataType: &lt;span style="color: #a31515"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;,
            &lt;span style="color: green"&gt;// data: data,
            &lt;/span&gt;data: param.data || data,
            success: &lt;span style="color: blue"&gt;function&lt;/span&gt;(response) {
                ...&lt;/pre&gt;

&lt;p&gt;修改两处即可，问题就此解决。只可惜，jquery.validate.min.js类似的文件只能自己进行压缩了。&lt;/p&gt;

&lt;p&gt;居然会出现这样的问题，实在令人费解。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/12/jquery-validate-remote-bug.html#comments</comments>
      <pubDate>Fri, 04 Dec 2009 06:29:00 GMT</pubDate>
      <lastBuildDate>Fri, 04 Dec 2009 06:29:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>视频：Microsoft PDC 09，算法及数据结构内容及其他</title>
      <link>http://blog.zhaojie.me/2009/11/videos-of-pdc09-algorithms-data-structure-visual-studio-documentary.html</link>
      <guid>http://blog.zhaojie.me/2009/11/videos-of-pdc09-algorithms-data-structure-visual-studio-documentary.html</guid>
      <description>&lt;p&gt;这里又有一些新整理好的视频。Microsoft PDC 09是最近的重头，只要您是搞微软技术的，无论关注哪个技术方面，都可以找到许多有用的内容。我也经常从此类大会中了解许多平时不太关注的内容，也算是保持知识的新鲜度。此外，还有算法和数据结构相关的内容，以及有趣的Visual Studio纪录片。&lt;/p&gt;  &lt;h1&gt;Microsoft PDC 09&lt;/h1&gt; &lt;img class="floatRight" src="http://img.zhaojie.me/blog/168980/o_PDC09Bling_BeforeAfter_136.jpg" /&gt;   &lt;p&gt;PDC，也就是Professional Developer Conference，专业开发者会议。这是微软的又一个技术大会，而且应该是个真正的技术大会。我为什么这么说呢？一个重要方面便是讲师的身份。PDC的讲师，大都是各微软产品团队的开发人员和设计人员，甚至一些科研人员，包括许多Technical Fellow和Distinguished Engineer等神仙级别的人物。总的来说，这个会议是来搞技术的。&lt;/p&gt;  &lt;p&gt;与此相对的一个典型是TechEd，它的定位是技术推广，让各厂商、合作伙伴了解微软技术、产品发展到什么样子了。因此，它的讲师大都是微软的咨询师，传教士等技术传播人员，而往往不是真正的技术开发者（当然我并不是说他们就不懂技术了）。从课程难度和深度上来说，TechEd大会的课程基本上都不能超过200，都应该属于介绍性质的，而PDC的内容，一般都要是300及以上了。&lt;/p&gt;  &lt;p&gt;因此我的观点就是，如果您专注于技术，那么不妨多专注于PDC或&lt;a href="http://visitmix.com/"&gt;MIX&lt;/a&gt;等技术会议，而不要在TechEd上追求太多——除非您已是高管，那么TechEd可以给您更凝练的感受。&lt;/p&gt;  &lt;p&gt;一星期前微软在洛杉矶举办了&lt;a href="http://microsoftpdc.com"&gt;PDC 09&lt;/a&gt;大会，然后放出了全部视频。然后经过了几天的下载和上传，我也终于把它们都放到优库上去了。如果您和我一样，在国内直接看PDC网站上的视频很慢，那么不妨可以看看优库上的版本。当然，您也可以选择去大会网站上下载高清版本，或低清版——还是比优库要清晰一些的。优库的好处在于想看便能立即看到，而且可以随意拖动。虽然不太清晰，但如果是需要让您看清楚的代码，讲师都会把它们放大，所以基本上也没有太大问题。&lt;/p&gt;  &lt;p&gt;这次的PDC的Session总共分七大部分，我将它们分为七个专辑：&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3966249.html"&gt;Keynotes (KEY)&lt;/a&gt; &lt;/li&gt;   &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3956816.html"&gt;Client (CL)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3959023.html"&gt;Frameworks &amp;amp; Tools (FT)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3960432.html"&gt;Productivity (PR)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3961503.html"&gt;Server (SVR)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3961304.html"&gt;Services (SVC)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3962400.html"&gt;Virtual&amp;#160; (VTL)&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;由于视频太多，我不可能一个个地整理，因此目前我只为那些我看过的或是感兴趣的（看了一部分的）的视频填写的信息。不过每个视频的编号（如FT09）都是完整且排好序的，您可以先去&lt;a href="http://microsoftpdc.com/videos"&gt;大会的视频列表&lt;/a&gt;中找到感兴趣的话题，然后再去优库上看。&lt;/p&gt;  &lt;p&gt;如果您看完了某个视频，我建议可以写一点总结，然后和大家一起分享，例如我目前已经写了《&lt;a href="http://www.infoq.com/cn/news/2009/11/pdc09-fsharp"&gt;并行和异步编程中的挑战及F#的应对方案&lt;/a&gt;》及《&lt;a href="http://www.infoq.com/cn/news/2009/11/pdc09-aspnet4"&gt;ASP.NET 4运行时的改进&lt;/a&gt;》两篇总结，接下来也打算总结更多内容。&lt;/p&gt;  &lt;h1&gt;算法及数据结构及其他&lt;/h1&gt;  &lt;p&gt;今天在Reddit上看到有人贴了一篇文章：&lt;a href="http://techbytes360.blogspot.com/2009/11/free-algorithms-and-data-structures.html"&gt;Top Algorithms and Data Structures Video Lectures&lt;/a&gt;，其中列出了：&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Algorithms&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://ocw.mit.edu/OcwWeb/Electrical-Engineering-and-Computer-Science/6-046JFall-2005/VideoLectures/index.htm"&gt;MIT OpenCourseWare: Introduction to Algorithms&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://nptel.iitm.ac.in/video.php?courseId=1065"&gt;NPTEL: Design and Analysis of Algorithm&lt;/a&gt; (by IIT and IISC, India) &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.cs.sunysb.edu/%7Ealgorith/video-lectures/"&gt;Stony Brook University: Skiena's Algorithms Lectures&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.aduni.org/courses/algorithms/index.php?view=cw"&gt;ArsDigita University: Algorithms&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;strong&gt;Data Structures&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://webcast.berkeley.edu/course_details.php?seriesid=1906978271"&gt;UC Berkeley: Data Structures&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://nptel.iitm.ac.in/video.php?courseId=1074"&gt;NPTEL: Data Structures And Algorithms&lt;/a&gt; (by IIT and IISC, India)&amp;#160; &lt;/li&gt;    &lt;li&gt;&lt;a href="https://wiki.cse.unsw.edu.au/cs1927cgi/09s2/Schedule"&gt;The University of New South Wales: Data Structures and Algorithms&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;而我发现，对于最负盛名的两所学校，MIT和UCB的算法与数据结构的课程，也已经有人整理在优库上了：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3967498.html"&gt;MIT: Introduction to Algorithms&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3763991.html"&gt;UC Berkeley: Data Structures&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;看到那么多学习资源，您还觉得哪里不满足吗？只是，看来学好英语是非常重要的——我现在越来越认同“大学毕业生必须过英语四级”这个硬性规定，只可惜我见过太多蒙混过关的人了……而且，很多人还觉得这是他们的幸运。这其实也挺悲哀的。&lt;/p&gt;  &lt;p&gt;最后，如果您对Visual Studio的历史感兴趣的话，Channel 9上最近在连载Visual Studio的纪录片，我也在优库上&lt;a href="http://www.youku.com/playlist_show/id_3963343.html"&gt;保持更新&lt;/a&gt;，除了技术内容至于，看这类内容也是挺有趣味的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/11/videos-of-pdc09-algorithms-data-structure-visual-studio-documentary.html#comments</comments>
      <pubDate>Fri, 27 Nov 2009 05:57:00 GMT</pubDate>
      <lastBuildDate>Fri, 27 Nov 2009 05:57:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>URL生成方式性能优化结果</title>
      <link>http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html</link>
      <guid>http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html</guid>
      <description>&lt;p&gt;继上次发现&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;URL生成的性能问题&lt;/a&gt;之后，我最近一直在关注一些细节的性能优化。这些优化方式不是宏观的，理论的，而是在实践上对相同问题的不同做法进行探索。我把探索的过程和结论都发布在博客上了，从结果上看性能提高是比较明显的。但是，把它们用于解决实际问题时，效果又会如何呢？我把&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;进行了一些修改，然后再使用UrlGenBenchmark进行了一番比较。&lt;/p&gt;  &lt;p&gt;对于性能方面的优化，我做了以下一些改进。首先，我使用&lt;a href="http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html"&gt;Fluent Interface来代替原有的Lambda表达式构建方式&lt;/a&gt;，期望可以减小表达式树构建的开销。此时，原本使用Lambda表达式的做法：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToPost(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.Action&amp;lt;&lt;span style="color: #2b91af"&gt;BlogController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Post(blog, post));
}&lt;/pre&gt;

&lt;p&gt;现在就变成了：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToPostByFluent(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.To&amp;lt;&lt;span style="color: #2b91af"&gt;BlogController&lt;/span&gt;&amp;gt;().Action(c =&amp;gt; c.Post, blog, post);
}&lt;/pre&gt;

&lt;p&gt;此外，对于Attribute标记Binder的做法也作了一些处理，不过没有使用&lt;a href="http://blog.zhaojie.me/2009/11/attribute-performance-improvement.html"&gt;昨天谈到的优化方式&lt;/a&gt;，而是通过在Model Binder类型标记RunnableAttribute的做法来复用对象。例如：&lt;/p&gt;

&lt;p&gt;同时，如果CustomBinderAttribute也被标记了Runnable，那么这个Attribute也可以被复用。如MvcPatch中的BinderAttribute就被标记为Runnable。更细节的做法可参考MvcPatch及UrlGenBenchmark项目的源代码。&lt;/p&gt;

&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;Reusable&lt;/span&gt;]
&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PostBinder &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IModelBinder&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IRouteBinder&lt;/span&gt; { ... }&lt;/pre&gt;

&lt;p&gt;经过测试，我们可以得到如下结果：&lt;/p&gt;
&lt;iframe height="300" src="https://spreadsheets.google.com/pub?key=tyknXBUdaib9-a5Qp5x7Njw&amp;amp;single=true&amp;amp;gid=0&amp;amp;output=html&amp;amp;widget=true" frameborder="0" width="100%"&gt;&lt;/iframe&gt;

&lt;p&gt;将其绘制成图表：&lt;/p&gt;
&lt;img src="https://spreadsheets.google.com/pub?key=tyknXBUdaib9-a5Qp5x7Njw&amp;amp;oid=4&amp;amp;output=image" /&gt; 

&lt;p&gt;而&lt;a href="https://spreadsheets.google.com/pub?key=tyknXBUdaib9-a5Qp5x7Njw&amp;amp;single=true&amp;amp;gid=1&amp;amp;output=html"&gt;每秒生成页面的数量&lt;/a&gt;则是：&lt;/p&gt;
&lt;img src="https://spreadsheets.google.com/pub?key=tyknXBUdaib9-a5Qp5x7Njw&amp;amp;oid=3&amp;amp;output=image" /&gt; 

&lt;p&gt;从结果上看，Fluent的做法比Lambda的性能有大约30%的提升，但是和Route，尤其是Raw的做法有明显的差距。在&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;上一篇文章&lt;/a&gt;最后我们分析过，使用Lambda构建URL会经历三个部分，它们所占消耗分别是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;构造表达式树：约占29%。 &lt;/li&gt;

  &lt;li&gt;解析和运算表达式树：约占30%。 &lt;/li&gt;

  &lt;li&gt;使用Route生成URL：约占41%。 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;而使用Fluent Interface时，第一步，即构造对象的阶段，其开销是Lambda表达式的1/3。同时，最后一个步骤的开销不变。因此我们可以估算出第二个步骤的性能提升程度x：&lt;/p&gt;

&lt;pre class="code"&gt;14.37 / 21.67 = 0.29 / 3 + 0.3 * x + 0.41&lt;/pre&gt;

&lt;p&gt;等号左边两个数字分别是目前Fluent和Lambda两种做法消耗的时间。通过这个方程我们得出x大约是0.51，也就是说在第二步中我们也获得了大约50%的性能提高。客观来说，这个提升还是比较明显的。不过，绝对来看，这个结果并不让我十分满意。不过可能目前还能进行优化的应该是第3个步骤，那么我是否要朝这个方向努力呢？&lt;/p&gt;

&lt;p&gt;您可以在这里下载到&lt;a href="http://cid-fba4447598b1d752.skydrive.live.com/self.aspx/Public/UrlGenBenchmark%5E_V2.zip"&gt;UrlGenBenchmark V2的代码&lt;/a&gt;，然后通过访问以下几个URL来查看各种生成方式的性能：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/benchmark?iteration=1000&amp;amp;view=ByRaw：使用拼接字符串的方式生成URL &lt;/li&gt;

  &lt;li&gt;/benchmark?iteration=1000&amp;amp;view=ByRoute：使用Route生成URL &lt;/li&gt;

  &lt;li&gt;/benchmark?iteration=1000：使用Lambda表达式生成URL&lt;/li&gt;

  &lt;li&gt;/benchmark?iteration=1000&amp;amp;view=ByFluent：使用Fluent Interface生成URL&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html"&gt;各种URL生成方式的性能对比&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;各种URL生成方式的性能对比（结论及分析）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html"&gt;为URL生成设计流畅接口（Fluent Interface）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;URL生成方式性能优化结果&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/12/route-getvirtualpath-optimization.html"&gt;Route组件GetVirtualPath方法性能优化结果&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html#comments</comments>
      <pubDate>Thu, 19 Nov 2009 02:33:00 GMT</pubDate>
      <lastBuildDate>Thu, 19 Nov 2009 02:33:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>我在TechEd的演讲：Real World ASP.NET MVC</title>
      <link>http://blog.zhaojie.me/2009/11/dev343-teched-2009-real-world-aspnet-mvc.html</link>
      <guid>http://blog.zhaojie.me/2009/11/dev343-teched-2009-real-world-aspnet-mvc.html</guid>
      <description>&lt;p&gt;上周的TechEd 2009比想象中忙，掺和了不少Session。不过一到晚上就开始胡吃海喝，总体来说过得还是挺不错的——只不过博客就落下了。嗯嗯，从现在开始继续。&lt;/p&gt;  &lt;p&gt;这个是我在这次TechEd上关于ASP.NET MVC的演讲以及演示代码下载，内容比较多，时间有些不够用。大家不妨看看，权当消遣。我去处理别的事情了……太多邮件没有回复，太多RSS没有看了。&lt;/p&gt;
&lt;div style="width:425px;text-align:left" id="__ss_2453692"&gt;&lt;a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/jeffz/real-world-aspnet-mvc" title="Real World ASP.NET MVC"&gt;Real World ASP.NET MVC&lt;/a&gt;&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=dev343-091108195550-phpapp02&amp;stripped_title=real-world-aspnet-mvc" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=dev343-091108195550-phpapp02&amp;stripped_title=real-world-aspnet-mvc" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;"&gt;View more &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/"&gt;documents&lt;/a&gt; from &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://cid-fba4447598b1d752.skydrive.live.com/self.aspx/Public/TechEd2009%5E_DEV343%5E_Demo.zip"&gt;示例代码&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/11/dev343-teched-2009-real-world-aspnet-mvc.html#comments</comments>
      <pubDate>Mon, 09 Nov 2009 02:03:00 GMT</pubDate>
      <lastBuildDate>Mon, 09 Nov 2009 02:03:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>为URL生成设计流畅接口（Fluent Interface）</title>
      <link>http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html</link>
      <guid>http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html</guid>
      <description>&lt;p&gt;昨天我&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html"&gt;比较了三种URL生成方式的性能&lt;/a&gt;，并&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;对结果进行了分析&lt;/a&gt;。从结果中我们得知使用Lambda表达式生成URL的性能最差，而且差到了难以接受的地步。经过分析，我们发现其中光“构造表达式树”这个阶段就占了接近30%的开销。虽然表达式树的节点是有些多，但是.NET中创建对象其实非常快，我实在没想到它会占这么高的比例。因此，我们需要这种做法进行方向性的调整，减少对象创建的数目。&lt;/p&gt;  &lt;p&gt;但是，既然我们要尽可能地保留“静态检查”的优势，因此这里的关键是如何设计出既美观好用，又高效的做法。例如，我在上一篇文章最后留下的做法是这样的：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span&gt;// API签名&lt;/span&gt; 
&lt;span style="color: blue"&gt;public static string &lt;/span&gt;Action(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;template, &lt;span style="color: blue"&gt;params object&lt;/span&gt;[] args)
 
&lt;span&gt;// 使用方式&lt;/span&gt; 
&lt;span style="color: blue"&gt;private readonly static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;...&amp;gt; ToPostTemplate = (c, blog, post) =&amp;gt; c.Post(blog, post);
&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToPost(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.Action(ToPostTemplate, blog, post);
}&lt;/pre&gt;

&lt;p&gt;我要求为每个Action都指定一个表达式“模板”，它是一个单例，既可以作为解析，又可用于缓存。它也不会创建额外对象，因此可以缓解性能问题。但是，它的缺点也很明显：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;必须单独创建一个表达式，因此使用比较麻烦（无法直接在视图中调用）。 &lt;/li&gt;

  &lt;li&gt;表达式模板的签名非常麻烦，无法利用C#编译器的类型推导能力。 &lt;/li&gt;

  &lt;li&gt;使用params object[]指定Action的参数，仍有些“弱类型”的意味。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此，这个方式其实并不理想。不过，在文章的评论中，&lt;a href="http://www.cnblogs.com/Ivony/"&gt;Ivony...&lt;/a&gt;老大给了我一些提示，然后再讨论中，我们总结出了一个似乎不错的API。这个API首先利用了C#编译器的一个特性：可以将一个方法直接转化为相同签名的委托对象，而不需要使用new进行显式创建。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;HomeController &lt;/span&gt;controller = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;();
&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Blog&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Post&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;ActionResult&lt;/span&gt;&amp;gt; action = controller.Post;&lt;/pre&gt;

&lt;p&gt;如第2行代码，其完整的写法应该使用new关键字，配合委托的类型，把方法包装为一个委托。但是，&lt;a href="http://blog.zhaojie.me/2009/08/from-delegate-to-others.html"&gt;从C# 2.0起编译器就允许我们使用省略的写法&lt;/a&gt;。于是接下来，我们便要设法利用C# 3.0中那微弱的类型推导能力进行API设计了。从&lt;a href="http://blog.zhaojie.me/2009/08/type-inference-bug-in-csharp.html"&gt;以前的一次讨论&lt;/a&gt;中，我们了解了C# 3.0编译器类型推导的一些“盲点”，例如：一个泛型方法只有通过传入的参数，才能确定泛型参数的具体类型。它使得以下代码无法编译通过：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 定义
&lt;/span&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Do&amp;lt;T1, T2&amp;gt;(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, &lt;span style="color: #2b91af"&gt;ActionResult&lt;/span&gt;&amp;gt; action)

&lt;span style="color: green"&gt;// 调用（类型推导失败）
&lt;/span&gt;Do(controller.Post);&lt;/pre&gt;

&lt;p&gt;除非通过其他两个参数来明确T1和T2的类型：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 方法定义
&lt;/span&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Do&amp;lt;T1, T2&amp;gt;(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, &lt;span style="color: #2b91af"&gt;ActionResult&lt;/span&gt;&amp;gt; action, T1 arg1, T2 arg2)

&lt;span style="color: green"&gt;// 调用（编译成功）
&lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
&lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
&lt;span style="color: #2b91af"&gt;HomeController &lt;/span&gt;controller = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;();
Do(controller.Post, blog, post);&lt;/pre&gt;

&lt;p&gt;可惜委托的返回值还是必须指明ActionResult类型，它无法推导出来。不过，这似乎也基本满足我们的需求。那么，我们又如何为Do方法提供controller的类型信息呢？要知道这样的API是行不通的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;Action&amp;lt;TController, T1, T2&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, ...)&lt;/pre&gt;

&lt;p&gt;因为C#编译器不允许在方法调用时指定部分参数（如单独指定TController而让T1，T2由类型推导获得），因此我们必须将Controller类型的指定工作，与剩下的泛型参数分开：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelperExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionOf&lt;/span&gt;&amp;lt;TController&amp;gt; Of&amp;lt;TController&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper) &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: blue"&gt;new&lt;/span&gt;()
    {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionOf&lt;/span&gt;&amp;lt;TController&amp;gt;(helper);
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionOf&lt;/span&gt;&amp;lt;TController&amp;gt;
{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;ActionOf(&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;urlHelper)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_urlHelper = urlHelper;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;m_urlHelper;
}&lt;/pre&gt;

&lt;p&gt;在ActionOf类中已经明确了TController泛型类型，它只要负责推导后续的参数即可，因此我们定义出这样的方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public string &lt;/span&gt;Action&amp;lt;T1, T2&amp;gt;(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;TController, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, &lt;span style="color: #2b91af"&gt;ActionResult&lt;/span&gt;&amp;gt;&amp;gt; action, T1 arg1, T2 arg2)&lt;/pre&gt;

&lt;p&gt;请注意现在Action方法第一个参数的类型：这是一个委托对象，接受TController类型作为参数，返回另一个委托对象——它不需要使用new进行显式创建。这个委托对象返回ActionResult类型，并接受T1，T2两个参数——这两个参数的类型又可以由Action方法的后续参数确定下来。因此，最后的调用方法大概是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;Url.Of&amp;lt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;&amp;gt;().Action(c =&amp;gt; c.Post, blog, post)&lt;/pre&gt;

&lt;p&gt;这行代码可读性也不错，我们可以这样理解：“&lt;font color="#ff0000"&gt;URL of HomeController’s action ‘Post’ with parameter ‘blog’ &amp;amp; ‘post’...&lt;/font&gt;”，因此它也可以算是一个流畅接口（Fluent Interface）。这个调用方式在我看来还是挺美观的，而且有明确的静态类型检查，属于比较理想的API。至于在Action方法内部，我们可以通过传入一个TController类型的对象来得到一个委托，这个委托对象的Method属性便是那个Action的MethodInfo——至于它的参数，便是blog和post了：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionOf&lt;/span&gt;&amp;lt;TController&amp;gt; &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: red"&gt;new()&lt;/span&gt;
{    
    &lt;span style="color: blue"&gt;private static &lt;/span&gt;TController prototype = &lt;span style="color: blue"&gt;new &lt;/span&gt;TController();

    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Action&amp;lt;T1, T2&amp;gt;(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;TController, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, &lt;span style="color: #2b91af"&gt;ActionResult&lt;/span&gt;&amp;gt;&amp;gt; action, T1 arg1, T2 arg2)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;Action(action(prototype).Method, arg1, arg2);
    }

    &lt;span style="color: blue"&gt;public static string &lt;/span&gt;Action(&lt;span style="color: #2b91af"&gt;MethodInfo &lt;/span&gt;methodInfo, &lt;span style="color: blue"&gt;params object&lt;/span&gt;[] args) { ... }
}&lt;/pre&gt;
&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;

&lt;p&gt;在这段代码中，我要求TController类型有个默认的构造函数，这样便于我们构造一个TController来“获取委托”，并得到它的Method信息。在实际使用过程中，我们不能强求每个Controller类型都满足这个条件，因此我打算使用Emit来动态生成TController的子类。使用Emit的好处在于，我们生成的动态类型可以绕开C#编译器的限制，不调用任何基类的构造函数，这样可以创建一个无用的对象而不会触及基类的任何逻辑，恰好满足我们的需求。&lt;/p&gt;

&lt;p&gt;当然，这种流畅接口和Lambda表达式相比还是有些缺点，如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;获得IDE的智能提示效果不佳。 &lt;/li&gt;

  &lt;li&gt;需要为参数数目不同的Action方法准备不同的重载。 &lt;/li&gt;

  &lt;li&gt;对类型要求严格，而构造Lambda表达式可以如普通C#代码那样的“宽松” &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;关于最后一点可能值得用代码示例来说明。如这样的Action方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Detail(&lt;span style="color: blue"&gt;long &lt;/span&gt;id) { ... }&lt;/pre&gt;

&lt;p&gt;而构造URL时：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;int &lt;/span&gt;id = 0;
helper.Action&amp;lt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Detail(id)); &lt;span style="color: green"&gt; // Lambda Expression&lt;/span&gt;
helper.Of&amp;lt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;&amp;gt;().Action(c =&amp;gt; c.Detail, (&lt;span style="color: blue"&gt;long&lt;/span&gt;)id); &lt;span style="color: green"&gt;// Fluent Interface&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;在流畅接口中将id强制转型为long的操作是不可以省略的，因为ActionOf&amp;lt;HomeController&amp;gt;.Action方法需要根据泛型类型来推断出c.Detail的签名。如果传入一个int类型的id，则编译器便会告知我们“c.Detail不符合Func&amp;lt;&lt;font color="#ff0000"&gt;int&lt;/font&gt;, ActionResult&amp;gt;类型”，进而编译失败。而使用Lambda表达式时，C#编译器会自动对id进行“提升（Lift）”操作，把int转化为long。不过这点在实际使用过程中应该不会成为问题。&lt;/p&gt;

&lt;p&gt;使用流畅接口生成URL时还是会创建一些对象，例如ActionOf对象，action委托对象、调用action后得到的另一个委托对象等等，可能还需要包括公用的Action(MethodInfo, params object[])方法所需要的对象数组。不过对象数量还是要比Lambda表达式少一些，而且不像Expression类型的那些工厂方法包含一些逻辑，因此在性能是有所提高的。这里我进行了&lt;a href="http://gist.github.com/224695"&gt;一个简单的性能测试&lt;/a&gt;，得到的结果为：&lt;/p&gt;

&lt;pre class="code"&gt;Lambda Expression
        Time Elapsed:   9,353ms
        CPU Cycles:     20,339,339,141
        Gen 0:          373
        Gen 1:          0
        Gen 2:          0

Fluent Interface
        Time Elapsed:   3,041ms
        CPU Cycles:     6,614,088,228
        Gen 0:          97
        Gen 1:          0
        Gen 2:          0&lt;/pre&gt;

&lt;p&gt;可见，使用流畅接口的方式，在构造URL的第1阶段（构造对象）可以得到超过2/3的性能提升，而如果参数数量多的话差距应该会更为明显。&lt;/p&gt;

&lt;p&gt;您觉得这个方式如何？可以给出您的看法吗？&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html"&gt;各种URL生成方式的性能对比&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;各种URL生成方式的性能对比（结论及分析）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;为URL生成设计流畅接口（Fluent Interface）&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html"&gt;URL生成方式性能优化结果&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/12/route-getvirtualpath-optimization.html"&gt;Route组件GetVirtualPath方法性能优化结果&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html#comments</comments>
      <pubDate>Tue, 03 Nov 2009 01:43:00 GMT</pubDate>
      <lastBuildDate>Tue, 03 Nov 2009 01:43:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>各种URL生成方式的性能对比（结论及分析）</title>
      <link>http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html</link>
      <guid>http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html</guid>
      <description>&lt;p&gt;上次我们设计了一个实验，&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html"&gt;比较三种不同URL生成方式的性能&lt;/a&gt;。您运行了吗？如果运行的话，有没有对结果进行一些的分析呢？现在我们就来详细观察及分析这次试验的结果，并给出我的分析。如果您有一些其他的看法，也请进行一些补充。&lt;/p&gt;  &lt;h1&gt;结论&lt;/h1&gt;  &lt;p&gt;我使用每种方式各生成1000次页面，并输出每生成100次的时候所耗费的时间。每种方式测试三次，并取平均值，结果如下：&lt;/p&gt; &lt;iframe height="370" src="http://spreadsheets.google.com/pub?key=tDG1R80y_XYRHzFLCAjmBtA&amp;amp;single=true&amp;amp;gid=0&amp;amp;output=html&amp;amp;widget=true" frameborder="0" width="100%"&gt;&lt;/iframe&gt;  &lt;p&gt;从中我们可以得出结论，各种方式所消耗的时间大约是：&lt;/p&gt; &lt;img alt="url generation benchmark" src="http://spreadsheets.google.com/pub?key=tDG1R80y_XYRHzFLCAjmBtA&amp;amp;oid=1&amp;amp;output=image" /&gt;   &lt;p&gt;我们能够很轻易的推断出字符串拼接URL的方式最快，使用Lambda表达式生成URL最慢，但是您正确估计出它们之间的差距了吗？我没有，得到这个结果以后也让我吃了一惊，我也没想到Lambda表达式的性能会如此之差，而关键更在于……&lt;/p&gt;  &lt;h1&gt;这个性能可以令人接受吗？&lt;/h1&gt;  &lt;p&gt;上一篇文章里有朋友回复到“这点性能在实际应用程序中可以忽略不计”——这是真的吗？为了更好的进行判断，我们可以简单计算出这三种方式究竟&lt;a href="http://spreadsheets.google.com/pub?key=tDG1R80y_XYRHzFLCAjmBtA&amp;amp;single=true&amp;amp;gid=2&amp;amp;output=html"&gt;每秒可以生成多少页面&lt;/a&gt;，结果如下：&lt;/p&gt; &lt;img alt="pages per second" src="http://spreadsheets.google.com/pub?key=tDG1R80y_XYRHzFLCAjmBtA&amp;amp;oid=2&amp;amp;output=image" /&gt;   &lt;p&gt;我们的测试样本基本上是模拟了链接数量处于“平均”水平的页面，并不过分——要知道真实应用中还会输出其他内容，以及做一些HTML编码之类的工作。从结果中我们可以看出，使用Lambda表达式每秒可以生成不到50次页面。自然，这是在我的性能比较一般的笔记本中“单核”的测试的结果，如果我们使用一台双核的机器来运行一个网站，它每秒大约可以生成100-150次页面。如果您的网站性能大约是50 requests/second请求的话（中到中上水平，不算太高），这大约意味着三分之一到二分之一的运算能力是交给单纯的页面生成。而对于一个性能要求较高的网站来说（100 r/q），页面生成所占用的系统资源则会更多。&lt;/p&gt;  &lt;p&gt;因此我认为，目前使用Lambda表达式生成页面的性能并不能令人满意。要知道单纯的页面生成操作，按照普通观点来说应该是可以忽略不计的。例如直接拼接字符串的作法，单核每秒可以生成超过600次的页面，只占一点点运算资源。而使用Route的方式，每秒超过100次，也勉强可以令人接受——虽然我认为ASP.NET Routing的Route类性能还可以有更进一步的提高，有机会我会尝试一下。&lt;/p&gt;  &lt;h1&gt;性能分析&lt;/h1&gt;  &lt;p&gt;Lambda表达式生成URL的方式性能很难令人接受，但是&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url.html"&gt;它也有许多好处&lt;/a&gt;啊：静态检查，功能内聚，易于开发和测试。如果直接放弃这种做法实在令人心有不甘，那么我们又该怎么办呢？最简单的做法是减少生成链接的次数，这里可以用到&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;中提供的&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html"&gt;页面片断缓存&lt;/a&gt;。使用这种方式可以将生成好的HTML缓存起来，在下一次请求时便可以直接输出，而不需要重新生成链接了，此时性能会比拼接字符串的方式更高。而且，页面片断缓存使用起来并不困难，对于一些不需要及时更新的内容，这个做法再合适不过了。&lt;/p&gt;  &lt;p&gt;只不过，这个方式也只是权宜之计，治标不治本，而且总有一些内容是不适合使用片断缓存的。因此，我们还是要设法优化Lambda表达式生成URL的性能。为此，我们要分析，究竟是什么原因造成了性能问题。那么我们先来回味一下，生成“一个”链接需要经过哪些步骤：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;构造一个表达式树。 &lt;/li&gt;    &lt;li&gt;对表达式树进行解析和运算，获得一个RouteValueDictionary。 &lt;/li&gt;    &lt;li&gt;使用Route配置，根据RouteValueDictionary生成URL。 &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;为此，我们至少可以得出两个结论：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;需要为每个链接构造一个表达式树。 &lt;/li&gt;    &lt;li&gt;使用Lambda表达式生成URL的做法，其实包含了使用Route生成URL的方式。 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;这意味着我们可以得到上面3个步骤所占的比例。于是乎，我们可以将ToPost等方法修改为如下形式在进行试验：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToPost(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post)
{&lt;span style="color: green"&gt;
    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;BlogController&lt;/span&gt;&amp;gt;&amp;gt; expr = c =&amp;gt; c.Post(blog, post);
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.ToPostByRaw(blog, post);
}&lt;/pre&gt;

&lt;p&gt;经试验，修改后的做法所花时间大约是8.0正负0.3秒，由此减去ByRaw所消耗的时间后便是单纯用于构造Lambda表达式的时间。也就是说，仅仅是用来创建表达式树并很快地释放引用，也需要大约&lt;font color="#ff0000"&gt;6.5秒&lt;/font&gt;的时间。看来在计算密集的情况下，生成表达式树的开销也是比较客观的，&lt;a href="http://blog.zhaojie.me/2009/10/get-expression-tree-nodes-count.html"&gt;毕竟有闭包的存在，一个表达式树的节点总是比想象中要略多一些&lt;/a&gt;。此时，我们再将Lambda生成URL所用的总时间（22.6秒），减去创建表达式树的时间（6.5秒），再减去ByRoute所消耗的时间（9.4秒），可以得出第2个步骤所消耗的时间大约是&lt;font color="#ff0000"&gt;6.7秒&lt;/font&gt;。&lt;/p&gt;

&lt;p&gt;现在，我们便大致得出了3个步骤分别消耗的时间：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;构造表达式树：6.5秒，约占&lt;font color="#ff0000"&gt;29%&lt;/font&gt;。 &lt;/li&gt;

  &lt;li&gt;解析和运算表达式树：6.7秒，约占&lt;font color="#ff0000"&gt;30%&lt;/font&gt;。 &lt;/li&gt;

  &lt;li&gt;使用Route生成URL：9.4秒，约占&lt;font color="#ff0000"&gt;41%&lt;/font&gt;。 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;换句话说，除了第3个步骤属于无法回避的性能开销，其他两个步骤所消耗的时间大致相同。那么，您觉得从哪个地方开始优化比较合适呢？对我来说，则似乎有些为难。&lt;/p&gt;

&lt;p&gt;生成Lambda表达式树其实只是创建了一些对象，它的性能其实很高，但是它还是占据了比较明显的开销。此外，解析和运算表达式树的开销和创建表达式树差不多，这意味着我们在这一块已经做的很不错了。事实上，第一次进行性能测试的时侯，我发现在解析和运算表达式树这边耗时特别长，而现在的结果已经是我进行了一些性能优化的结果（主要是避免了大量反射操作），节省了大约70%的时间（要知道原来生成1000次页面需要40-50秒）。在这个阶段，我还使用了&lt;a href="http://blog.zhaojie.me/2009/07/expression-tree-fast-evaluation.html"&gt;FastLambda&lt;/a&gt;类库，否则这部分的时间消耗会有数量级的提升（20倍左右）。&lt;/p&gt;

&lt;p&gt;所以我认为，性能已经很难有明显的提升了。除非我们可以&lt;font color="#ff0000"&gt;避免为每个链接都生成一个表达式树&lt;/font&gt;，并且&lt;font color="#ff0000"&gt;不对表达式树进行计算&lt;/font&gt;。这意味着我们需要做出根本性的，方向性地调整。&lt;/p&gt;

&lt;h1&gt;展望&lt;/h1&gt;

&lt;p&gt;在这里如何设计出又好用，又性能高的API着实让我头疼了一把。例如我想过，是否可以借鉴&lt;a href="http://www.postsharp.org/"&gt;PostSharp&lt;/a&gt;的做法，修改编译后的程序集，把对UrlHelper.Action的调用修改为静态的数据操作（例如ByRoute的形式），这样就避免了表达式树的生成和解析。但是这也和PostSharp有所不同，PostSharp只要分析元数据即可，而它需要分析IL的逻辑，这要麻烦得多。或者，我们可以在运行时hook并替换掉ToPost的实现，这其实也就是前一种做法的“动态”版本——可惜的是，我不知道该如何进行。&lt;/p&gt;

&lt;p&gt;最终我退而求其次，设计了这样一种做法。此时ToPost方法便会修改为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span&gt;// API签名&lt;/span&gt;
&lt;span style="color: blue"&gt;public static string &lt;/span&gt;Action(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;template, &lt;span style="color: blue"&gt;params object&lt;/span&gt;[] args)

&lt;span&gt;// 使用方式&lt;/span&gt;
&lt;span style="color: blue"&gt;private readonly static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;...&amp;gt; ToPostTemplate = (c, blog, post) =&amp;gt; c.Post(blog, post);
&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToPost(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.Action(ToPostTemplate, blog, post);
}&lt;/pre&gt;

&lt;p&gt;由于准备了额外的“模板”，我们便省下了每次都生成表达式树的开销。在第一次调用Action方法时，会根据模板的内容生成与ByRoute静态实现差不多的操作，并根据模板对象进行缓存。这样，最后得到的性能便和ByRoute的做法没有多少区别了。只不过，这个做法需要生成独立的模板，从“美观度”上说实在比不上原来的做法。而且——这个“模板”对象的签名实在比较麻烦。&lt;/p&gt;

&lt;p&gt;您对此有什么看法呢？我们不妨一起讨论一下如何做到“既美观，又高效”。如果您有更理想的做法也请告诉我。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html"&gt;各种URL生成方式的性能对比&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;各种URL生成方式的性能对比（结论及分析）&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html"&gt;为URL生成设计流畅接口（Fluent Interface）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html"&gt;URL生成方式性能优化结果&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/12/route-getvirtualpath-optimization.html"&gt;Route组件GetVirtualPath方法性能优化结果&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html#comments</comments>
      <pubDate>Sun, 01 Nov 2009 16:16:00 GMT</pubDate>
      <lastBuildDate>Sun, 01 Nov 2009 16:16:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>各种URL生成方式的性能对比</title>
      <link>http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html</link>
      <guid>http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html</guid>
      <description>&lt;p&gt;在&lt;a href="http://blog.zhaojie.me/2009/10/several-ways-of-generating-url.html"&gt;上一篇文章&lt;/a&gt;中我们列举了各种URL生成的方式，其中大致可以分为三类：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;直接拼接字符串（方法一及方法二） &lt;/li&gt;    &lt;li&gt;使用Route规则生成URL（方法三） &lt;/li&gt;    &lt;li&gt;使用Lambda表达式生成URL（方法四及方法五） &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;我们可以轻易得知，这3种作法可维护性依次增加，而性能依次减少。不过，我们还是有一个疑问，这个性能究竟相差多少？它是否的确真的可以被忽略？为此，我们还是来进行一次性能对比吧。&lt;/p&gt;  &lt;h1&gt;测试对象&lt;/h1&gt;  &lt;p&gt;为了获得贴近实际的测试结果，我打算以&lt;a href="http://www.cnblogs.com/JeffreyZhao/"&gt;我的博客首页&lt;/a&gt;作为测试对象。您可以发现，这个页面上的链接非常多，我把它分为三个部分：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;文章（Post）列表：主体部分的40篇文章，其中每篇文章包含1个详细页链接以及5个Tag。 &lt;/li&gt;    &lt;li&gt;边栏文章列表：假设边栏列举了120篇文章的链接。 &lt;/li&gt;    &lt;li&gt;归档（Archive）列表：也就是每个月的文章链接，3年共36个链接。 &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;作为一个演示，我也精心准备了四种URL模式，它们分别是：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 博客首页
&lt;/span&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;&amp;quot;Blog.Index&amp;quot;&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;&amp;quot;{blog}&amp;quot;&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;&amp;quot;Blog&amp;quot;&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;&amp;quot;Index&amp;quot; &lt;/span&gt;});

&lt;span style="color: green"&gt;// 标签页
&lt;/span&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;&amp;quot;Blog.Tag&amp;quot;&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;&amp;quot;{blog}/tag/{tag}&amp;quot;&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;&amp;quot;Blog&amp;quot;&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;&amp;quot;Tag&amp;quot; &lt;/span&gt;});

&lt;span style="color: green"&gt;// 按月归档页
&lt;/span&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;&amp;quot;Blog.Archive&amp;quot;&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;&amp;quot;{blog}/archive/{year}/{month}.html&amp;quot;&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;&amp;quot;Blog&amp;quot;&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;&amp;quot;Archive&amp;quot; &lt;/span&gt;});

&lt;span style="color: green"&gt;// 文章详细页
&lt;/span&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;&amp;quot;Blog.Post&amp;quot;&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;&amp;quot;{blog}/archive/{*post}&amp;quot;&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;&amp;quot;Blog&amp;quot;&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;&amp;quot;Post&amp;quot; &lt;/span&gt;});&lt;/pre&gt;

&lt;p&gt;以上代码在Web项目中的&lt;font color="#ff0000"&gt;GlobalApplication.cs&lt;/font&gt;文件中。您可以发现，我完全按照博客园在定制URL的模式。我想说明的是，其实URL Routing完全非常灵活，您可以根据需求使用各种形式的URL，关键只是“规则配置”而已。不过虽然配置了4种Route规则，但是我只实现了BlogController下的一个Action：博客首页（Index），如下：&lt;/p&gt;

&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;RouteName&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;Blog.Index&amp;quot;&lt;/span&gt;)]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Index(
    [&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;BlogBinder&lt;/span&gt;))]&lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: blue"&gt;string &lt;/span&gt;view)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;model = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IndexModel &lt;/span&gt;{ Blog = blog, Posts = GetPosts() };
    &lt;span style="color: blue"&gt;return &lt;/span&gt;View(&lt;span style="color: #a31515"&gt;&amp;quot;Index&amp;quot; &lt;/span&gt;+ view, model);
}

&lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Post&lt;/span&gt;&amp;gt; GetPosts()
{
    ...
}

[&lt;span style="color: #2b91af"&gt;RouteName&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;Blog.Post&amp;quot;&lt;/span&gt;)]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Post(
    [&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;BlogBinder&lt;/span&gt;))]&lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog,
    [&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;PostBinder&lt;/span&gt;))]&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post)
{
    &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;();
}

[&lt;span style="color: #2b91af"&gt;RouteName&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;Blog.Tag&amp;quot;&lt;/span&gt;)]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Tag(
    [&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;BlogBinder&lt;/span&gt;))]&lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog,
    [&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;StringBinder&lt;/span&gt;))]&lt;span style="color: blue"&gt;string &lt;/span&gt;tag)
{
    &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;();
}

[&lt;span style="color: #2b91af"&gt;RouteName&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;Blog.Archive&amp;quot;&lt;/span&gt;)]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Archive(
    [&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;BlogBinder&lt;/span&gt;))]&lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog,
    &lt;span style="color: blue"&gt;int &lt;/span&gt;year,
    &lt;span style="color: blue"&gt;int &lt;/span&gt;month)
{
    &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;();
}&lt;/pre&gt;

&lt;p&gt;&lt;font color="#ff0000"&gt;BlogController.cs&lt;/font&gt;文件处于Web.Controllers项目中。我为每个复杂参数都安排了ModelBinder，具体实现都很简单，您可以下载文末的代码进行浏览。在GetPosts里我将准备40个Post对象，每个Post对象分配5个Tag，这些都将显示在页面上。Index方法的参数view通过Query String进行传递，例如，您可以通过一下三个链接来访问不同的URL生成方式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/jeffz?view=ByRaw：使用拼接字符串的方式生成URL &lt;/li&gt;

  &lt;li&gt;/jeffz?view=ByRoute：使用Route规则生成URL &lt;/li&gt;

  &lt;li&gt;/jeffz：使用Lambda表达式这个“推荐方式”生成URL &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;三种方式&lt;/h1&gt;

&lt;p&gt;在Web.UI项目中的Views目录下有BlogController所使用的三个视图模板，他们使用不同的方式来生成完全一样的内容。例如&lt;font color="#ff0000"&gt;Index.aspx&lt;/font&gt;文件中的定义是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;&amp;lt;!-- 主体文章列表，40篇，各5个Tag --&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Posts&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;post &lt;span style="color: blue"&gt;in &lt;/span&gt;Model.Posts) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &lt;/span&gt;Title: &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ToPost(Model.Blog, post) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(post.Title) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &lt;/span&gt;Tag: 
            &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;tag &lt;span style="color: blue"&gt;in &lt;/span&gt;post.Tags) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ToTag(Model.Blog, tag) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(tag) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt; &lt;/span&gt;| 
            &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;

&lt;/span&gt;&lt;span style="color: green"&gt;&amp;lt;!-- 边栏文章列表，共计120篇 --&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;More post links&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 3; i++) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;post &lt;span style="color: blue"&gt;in &lt;/span&gt;Model.Posts) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;            &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li &lt;/span&gt;&lt;span style="color: red"&gt;style&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="color: red"&gt;display&lt;/span&gt;: &lt;span style="color: blue"&gt;inline&lt;/span&gt;;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;
                &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ToPost(Model.Blog, post) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(post.Title) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt; &lt;/span&gt;|
            &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;

&lt;/span&gt;&lt;span style="color: green"&gt;&amp;lt;!-- 归档列表，3年共计36个链接 --&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Archives&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;year = 2007; year &amp;lt;= 2009; year++) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;month = 1; month &amp;lt;= 12; month++) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;            &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
                &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ToArchive(Model.Blog, year, month) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;year &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;年&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;month &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;月&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;对于&lt;font color="#ff0000"&gt;IndexByRaw.aspx&lt;/font&gt;和&lt;font color="#ff0000"&gt;IndexByRoute.aspx&lt;/font&gt;来说，它们只是把ToPost，ToTag等方法改为对应的ToPostByRaw或ToTagByRoute而已。因此，其实生成URL的关键还在于这些辅助方法。例如ToPost，ToTag和ToArchive三个扩展方法是这样实现的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToPost(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;post)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.Action&amp;lt;&lt;span style="color: #2b91af"&gt;BlogController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Post(blog, post));
}

&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToTag(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: blue"&gt;string &lt;/span&gt;tag)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.Action&amp;lt;&lt;span style="color: #2b91af"&gt;BlogController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Tag(blog, tag));
}

&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToArchive(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: blue"&gt;int &lt;/span&gt;year, &lt;span style="color: blue"&gt;int &lt;/span&gt;month)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.Action&amp;lt;&lt;span style="color: #2b91af"&gt;BlogController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Archive(blog, year, month));
}&lt;/pre&gt;

&lt;p&gt;可见，使用Lambda表达式构造URL的代码非常清晰，简单，直观——因为Action辅助方法会自动从Lambda表达式中提取Controller和Action名，并调用每个参数的RouteBinder实现复杂类型参数的双向转化，它不需要我们关心更多的东西。&lt;/p&gt;

&lt;p&gt;而如果直接拼接字符串，那么它可能就是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToTagByRaw(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: blue"&gt;string &lt;/span&gt;tag)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;blog.Alias + &lt;span style="color: #a31515"&gt;&amp;quot;/tag/&amp;quot; &lt;/span&gt;+ &lt;span style="color: #2b91af"&gt;HttpUtility&lt;/span&gt;.UrlEncode(tag);
}&lt;/pre&gt;

&lt;p&gt;而基于Route构造URL就会显得略麻烦一些：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToTagByRoute(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;blog, &lt;span style="color: blue"&gt;string &lt;/span&gt;tag)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;path = helper.RouteCollection.GetVirtualPathEx(
        helper.RequestContext,
        &lt;span style="color: #a31515"&gt;&amp;quot;Blog.Tag&amp;quot;&lt;/span&gt;,
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary
        &lt;/span&gt;{
            { &lt;span style="color: #a31515"&gt;&amp;quot;controller&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;Blog&amp;quot; &lt;/span&gt;},
            { &lt;span style="color: #a31515"&gt;&amp;quot;action&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;Tag&amp;quot; &lt;/span&gt;},
            { &lt;span style="color: #a31515"&gt;&amp;quot;blog&amp;quot;&lt;/span&gt;, blog.Alias },
            { &lt;span style="color: #a31515"&gt;&amp;quot;tag&amp;quot;&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;HttpUtility&lt;/span&gt;.UrlEncode(tag) }
        });

    &lt;span style="color: blue"&gt;return &lt;/span&gt;path.VirtualPath;
}&lt;/pre&gt;

&lt;p&gt;至于后两种方式的其它几个辅助方法，您可以下载文末的代码进行浏览，它们都在Web.Controllers项目中的&lt;font color="#ff0000"&gt;UrlGenExtensions.cs&lt;/font&gt;文件中。&lt;/p&gt;

&lt;h1&gt;运行测试&lt;/h1&gt;

&lt;p&gt;我们使用BlogController中另一个Action方法：Benchmark进行性能测试。Benchmark方法接受两个参数，一个是循环次数，而另一个则是测试目标：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Benchmark(&lt;span style="color: blue"&gt;int &lt;/span&gt;iteration, &lt;span style="color: blue"&gt;string &lt;/span&gt;view)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;model = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IndexModel
    &lt;/span&gt;{
        Blog = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Blog &lt;/span&gt;{ Alias = &lt;span style="color: #a31515"&gt;&amp;quot;jeffz&amp;quot; &lt;/span&gt;},
        Posts = GetPosts()
    };

    &lt;span style="color: blue"&gt;var &lt;/span&gt;result = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BenchmarkModel
    &lt;/span&gt;{
        Iteration = iteration,
        View = &lt;span style="color: #a31515"&gt;&amp;quot;Index&amp;quot; &lt;/span&gt;+ view
    };

    &lt;span style="color: blue"&gt;var &lt;/span&gt;viewInstance = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebFormView&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;~/Views/Blog/Index&amp;quot; &lt;/span&gt;+ view + &lt;span style="color: #a31515"&gt;&amp;quot;.aspx&amp;quot;&lt;/span&gt;);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;viewContext = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewContext&lt;/span&gt;(
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.ControllerContext,
        viewInstance,
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewDataDictionary&lt;/span&gt;(model),
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TempDataDictionary&lt;/span&gt;());

    &lt;span style="color: green"&gt;// warm up
    &lt;/span&gt;viewInstance.Render(viewContext, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StringWriter&lt;/span&gt;());

    &lt;span style="color: #2b91af"&gt;GC&lt;/span&gt;.Collect();

    &lt;span style="color: blue"&gt;var &lt;/span&gt;watch = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch&lt;/span&gt;();
    watch.Start();

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 1; i &amp;lt;= iteration; i++)
    {
        viewInstance.Render(viewContext, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StringWriter&lt;/span&gt;());

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(i % 100 == 0)
        {
            result.Add(i, watch.Elapsed);
        }
    }

    watch.Stop();

    &lt;span style="color: blue"&gt;return &lt;/span&gt;View(result);
}&lt;/pre&gt;

&lt;p&gt;于是，您可以使用下面的链接观察使用三种方法生成1000次页面所消耗的时间：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/benchmark?iteration=1000&amp;amp;view=ByRaw：使用拼接字符串的方式生成URL&lt;/li&gt;

  &lt;li&gt;/benchmark?iteration=1000&amp;amp;view=ByRoute：使用Route生成URL&lt;/li&gt;

  &lt;li&gt;/benchmark?iteration=1000：使用Lambda表达式生成URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Benchmark方法会每隔100次记录一下结果，因此上面的链接加载完后会出现10条信息——这便是我们得到的结果。&lt;/p&gt;

&lt;h1&gt;结果&lt;/h1&gt;

&lt;p&gt;至于最终的结果以及分析，我打算暂时卖个关子，不多久我就会独立开篇进行说明的。您可以&lt;a href="http://cid-fba4447598b1d752.skydrive.live.com/self.aspx/Public/UrlGenBenchmark.zip" target="blank"&gt;在这里下载到整个解决方案&lt;/a&gt;，代码不多，但也花费了我2个小时进行准备，您可以亲自试验一下。您直接使用上面的Benchmark链接进行观察即可，生成1000次页面已经足以展示一些问题了——不过在此之前，您不妨进行一个预测，猜猜看它们之间究竟有多大的性能差距。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;各种URL生成方式的性能对比&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/several-ways-of-generating-url-benchmark-result.html"&gt;各种URL生成方式的性能对比（结论及分析）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/fluent-interface-for-url-generation.html"&gt;为URL生成设计流畅接口（Fluent Interface）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/url-generation-performance-improvement-result.html"&gt;URL生成方式性能优化结果&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/12/route-getvirtualpath-optimization.html"&gt;Route组件GetVirtualPath方法性能优化结果&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/several-ways-of-generating-url-benchmark.html#comments</comments>
      <pubDate>Thu, 29 Oct 2009 16:31:00 GMT</pubDate>
      <lastBuildDate>Thu, 29 Oct 2009 16:31:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>浅谈URL生成方式的演变</title>
      <link>http://blog.zhaojie.me/2009/10/several-ways-of-generating-url.html</link>
      <guid>http://blog.zhaojie.me/2009/10/several-ways-of-generating-url.html</guid>
      <description>&lt;p&gt;开发Web应用程序的时候，在页面上总会放置大量的链接，而链接的生成方式看似简单，也有许多不同的变化，且各有利弊。现在我们就来看看，在一个ASP.NET MVC应用程序的视图中如果要生成一个链接地址又有哪些做法，它们之间又是如何演变的。&lt;/p&gt;  &lt;h1&gt;目标&lt;/h1&gt;  &lt;p&gt;作为示例，我们总要有个目标URL。我们这里的目标为面向如下Action的URL，也就是一篇文章的详细页：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArticleController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Detail(&lt;span style="color: #2b91af"&gt;Article &lt;/span&gt;article)
    {
        ...
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Article
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;ArticleID { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Title { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;而我们的目标URL则是文章的ID与标题的联合，其中标题里的空格替换为很短横线——把文章的标题放入URL自然是为了SEO。于是乎我们使用这样的Route规则：&lt;/p&gt;

&lt;pre class="code"&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;&amp;quot;Article.Detail&amp;quot;&lt;/span&gt;,                                  &lt;span style="color: green"&gt;// Route name
    &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;article/{article}&amp;quot;&lt;/span&gt;,                               &lt;span style="color: green"&gt;// URL with parameters
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;&amp;quot;Article&amp;quot;&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;&amp;quot;Detail&amp;quot; &lt;/span&gt;}  &lt;span style="color: green"&gt;// Parameter defaults
&lt;/span&gt;);&lt;/pre&gt;

&lt;p&gt;在URL Routing捕获到article之后，它的形式可能是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;10-this-is-the-title&lt;/pre&gt;

&lt;p&gt;我们只考虑这个ID，后面的字符串虽然在URL中，但是完全被忽略。在实际项目中，我们可以编写一个Model Binder从这样一个字符串中提取ID，再获取对应的Article对象。不过我们的现在不关注这个。&lt;/p&gt;

&lt;p&gt;我们的目标只有一个：如何生成URL。&lt;/p&gt;

&lt;h1&gt;方法一：直接拼接字符串&lt;/h1&gt;

&lt;p&gt;这是个最直接，最容易想到的做法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;article &lt;span style="color: blue"&gt;in &lt;/span&gt;Models.Articles) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;/article/&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= article.ArticleID &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;-&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.Encode(article.Title.Replace(' ', '-')) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;
        &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(article.Title) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;这个做法随着ASP.NET的诞生陪伴我们一路走来，已经有7、8个年头了，相信大部分朋友对它都不会陌生。它的&lt;font color="#ff0000"&gt;优点&lt;/font&gt;自然是最为简单，最为直接，几乎没有任何门槛，也不需要任何准备就可以直接使用，而且理论上性能也是最佳的。但是它的&lt;font color="#ff0000"&gt;缺点&lt;/font&gt;也很明显，那就是需要在每个页面，每个地方都重复这样一个字符串。试想，如果我们URL的生成规则忽然有所变化，又会怎么样？我们必须找出所有的生成链接的地方，一个一个改过来。这往往是一个浩大的工程，而且非常容易出错——因为我们根本没有静态检查可以依托。因此，在实际情况下，除非是快速开发的超小型的，随做随抛的实验性项目，一般不建议使用这样的做法。&lt;/p&gt;

&lt;h1&gt;方法二：使用辅助方法&lt;/h1&gt;

&lt;p&gt;为了避免方法一的缺点，我们可以使用一个辅助方法来生成URL：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArticleUrlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToArticle(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Article &lt;/span&gt;article)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;/article/&amp;quot; &lt;/span&gt;+ article.ArticleID + &lt;span style="color: #a31515"&gt;&amp;quot;-&amp;quot; &lt;/span&gt;+ helper.Encode(article.Title.Replace(&lt;span style="color: #a31515"&gt;' '&lt;/span&gt;, &lt;span style="color: #a31515"&gt;'-'&lt;/span&gt;));
    }
}&lt;/pre&gt;

&lt;p&gt;我们把负责生成URL的辅助方法写作UrlHelper的扩展方法，于是我们就可以在页面上这样生成URL了（省略多余标记）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ToArticle(article) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;&lt;/span&gt;...&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;这个做法的&lt;font color="#ff0000"&gt;优点&lt;/font&gt;在于把生成URL的逻辑汇集到了一处，这样如果需要变化的时候只需要修改这一个地方就行了，而且它几乎没有任何副作用，使用起来也非常简单。而它的&lt;font color="#ff0000"&gt;缺点&lt;/font&gt;还是在于有些重复：如果这个URL修改涉及到Route配置的变化（例如从&lt;a href="#"&gt;http://www.domain.com/article/5&lt;/a&gt;变成了&lt;a href="#"&gt;http://articles.domain.com/5&lt;/a&gt;），则ToArticle方法也必须随之修改。也就是说，这个方法对DRY（Don’t Repeat Yourself）原则贯彻地还不够彻底。不过，对于一般项目来说，这个做法也不会有太大问题——这也是构造URL方式的底线。&lt;/p&gt;

&lt;h1&gt;方法三：从Route配置中生成URL&lt;/h1&gt;

&lt;p&gt;对于&lt;a href="http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html"&gt;URL Routing的双向职责&lt;/a&gt;我已经提过无数次了，我们配置了Route规则，那么便可以使用它来生成URL：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToDetail(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Article &lt;/span&gt;article)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;values = &lt;span style="color: blue"&gt;new
    &lt;/span&gt;{
        article = article.ArticleID + &lt;span style="color: #a31515"&gt;&amp;quot;-&amp;quot; &lt;/span&gt;+ helper.Encode(article.Title.Replace(&lt;span style="color: #a31515"&gt;' '&lt;/span&gt;, &lt;span style="color: #a31515"&gt;'-'&lt;/span&gt;))
    };

    &lt;span style="color: blue"&gt;var &lt;/span&gt;path = helper.RouteCollection.GetVirtualPath(
        helper.RequestContext, &lt;span style="color: #a31515"&gt;&amp;quot;Article.Detail&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(values));

    &lt;span style="color: blue"&gt;return &lt;/span&gt;path.VirtualPath;
}&lt;/pre&gt;

&lt;p&gt;由于Route配置知道如何根据Route Value集合里的值生成一个URL，因此我们只要把这个职责交给它即可。一般来说，我们&lt;a href="http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html"&gt;会指定Route规则的名称&lt;/a&gt;，这样节省了遍历尝试每个规则的开销，也不会被冲突问题所困扰。此时，即便是URL需要变化，只要调整Route规则即可——只要保持规则对“值”的需求不变就行了。例如之前提到的URL的变化，我们只要把Route配置调整为：&lt;/p&gt;

&lt;pre class="code"&gt;routes.MapDomain(
    &lt;span style="color: #a31515"&gt;&amp;quot;Article&amp;quot;&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;&amp;quot;http://articles.{*domain}&amp;quot;&lt;/span&gt;,
    innerRoutes =&amp;gt;
    {
        innerRoutes.MapRoute(
            &lt;span style="color: #a31515"&gt;&amp;quot;Detail&amp;quot;&lt;/span&gt;,
            &lt;span style="color: #a31515"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
            &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;&amp;quot;Article&amp;quot;&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;&amp;quot;Detail&amp;quot; &lt;/span&gt;});
    };&lt;/pre&gt;

&lt;p&gt;这个做法的&lt;font color="#ff0000"&gt;优点&lt;/font&gt;在于“自动”与Route配置同步，几乎不需要额外的逻辑。而它的&lt;font color="#ff0000"&gt;缺点&lt;/font&gt;——可能在从性能角度上考虑会有“细微”的差距（在实际应用中是否重要另当别论）……&lt;/p&gt;

&lt;h1&gt;方法四：使用Lambda表达式生成URL&lt;/h1&gt;

&lt;p&gt;我也经常强调使用Lambda表达式生成URL的好处：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.Action&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Detail(article)) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;&lt;/span&gt;...&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;由于在ASP.NET MVC中，一个URL的最终目标归根到底是一个Action，因此如果我们可以更直观地在代码中表现出这一点，则可以进一步提高代码的可读性。这一点在ASP.NET MVC 1.0自带的MvcFutures项目中已经有所体现，只可惜它作的远远不够，几乎没有任何实用价值。不过现在您也可以使用&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;项目进行开发，它提供了完整的使用Lambda表达式生成URL的能力，它相对于MvcFutures里的辅助方法作了各种补充：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/get-action-name-from-expression-tree-by-actionnameattribute.html"&gt;支持ActionNameAttribute&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/make-expression-tree-based-url-construction-faster.html"&gt;提高性能&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/ignore-some-arguments-when-constructing-url-via-expression-tree.html"&gt;允许忽略部分参数&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html"&gt;可指定Route规则的名称&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/bidirectional-conversion-for-action-parameter-with-route-binder.html"&gt;支持Action复杂参数的双向转化&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;使用这种方式，我们需要对Action方法做些简单的修改：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArticleBinder &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IModelBinder&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IRouteBinder
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public object &lt;/span&gt;BindModel(&lt;span style="color: #2b91af"&gt;ControllerContext &lt;/span&gt;controllerContext, &lt;span style="color: #2b91af"&gt;ModelBindingContext &lt;/span&gt;bindingContext)
    {
        ...
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;BindRoute(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: #2b91af"&gt;RouteBindingContext &lt;/span&gt;bindingContext)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;article = (&lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;)bindingContext.Model;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;text = article.ArticleID + &lt;span style="color: #a31515"&gt;&amp;quot;-&amp;quot; &lt;/span&gt;+ &lt;span style="color: #2b91af"&gt;HttpUtility&lt;/span&gt;.UrlEncode(article.Title.Replace(&lt;span style="color: #a31515"&gt;' '&lt;/span&gt;, &lt;span style="color: #a31515"&gt;'-'&lt;/span&gt;));

        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;{ bindingContext.ModelName = text });
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArticleController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    [&lt;span style="color: #2b91af"&gt;RouteName&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;Article.Detail&amp;quot;&lt;/span&gt;)]
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Detail([&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ArticleBinder&lt;/span&gt;))]&lt;span style="color: #2b91af"&gt;Article &lt;/span&gt;article)
    {
        ...
    }
}&lt;/pre&gt;

&lt;p&gt;请注意我们对Action方法标记了RouteNameAttribute，以此指定Route规则的名称（第4点）；同时，ArticleBinder也实现一个新的接口IRouteBinder负责从Article对象转化为Route Value。&lt;/p&gt;

&lt;p&gt;这个做法的&lt;font color="#ff0000"&gt;优点&lt;/font&gt;在于基本上回避了“生成URL”这个工作，而将关注点放在Action方法这个根本的目标上。此外，各种逻辑也很内聚，它们都环绕在Action方法周围，遇到问题也不用四散查询，而将Article对象转化为Route Value的职责也和它的对应操作放在了一起，更容易进行独立的单元测试。此外，使用Lambda表达式生成URL还能获得&lt;a href="http://blog.zhaojie.me/2009/01/compile-aspx-file.html"&gt;编译器的静态检查&lt;/a&gt;，这确保了可以在编译期间解决尽可能多的问题。&lt;/p&gt;

&lt;p&gt;而它的&lt;font color="#ff0000"&gt;缺点&lt;/font&gt;主要是比较复杂，如果您不使用MvcPatch项目的话，可能就需要自行补充许多辅助方法，它们并不那么简单。此外，在视图上的代码页稍显多了一些。还有便是基于表达式树解析的做法多少会有些性能损失，我们下次再来关注这个问题。&lt;/p&gt;

&lt;h1&gt;方法五：简化Lambda表达式的使用&lt;/h1&gt;

&lt;p&gt;第五个方法其实是前者的补充。例如，我们可以再准备这样一个辅助方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArticleUrlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToArticle(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;urlHelper, &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;&amp;gt; action)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;urlHelper.Action&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;(action);
    }
}&lt;/pre&gt;

&lt;p&gt;这样在页面上使用时无须指定ArticleController类了——这类名的确有些长：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ToArticle(c =&amp;gt; c.Detail(article)) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;quot;&amp;gt;&lt;/span&gt;...&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;或者，我们可以结合方法二或三，提供一个额外的辅助方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArticleUrlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static string &lt;/span&gt;ToArticle(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;urlHelper, &lt;span style="color: #2b91af"&gt;Article &lt;/span&gt;article)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;urlHelper.Action&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Detail(article));
    }
}&lt;/pre&gt;

&lt;p&gt;至于最终使用哪个辅助方法，我想问题都不是很大。前者的“准备工作”更为简单，只需为每个Controller准备一个辅助方法就够了，而后者则需要为每个Action提供一个辅助方法，不过它使用起来却更为方便一些。&lt;/p&gt;

&lt;p&gt;这个做法的&lt;font color="#ff0000"&gt;优点&lt;/font&gt;在于继承了Lambda表达式构造URL的优势之外，还简化了它的使用。至于&lt;font color="#ff0000"&gt;缺点&lt;/font&gt;，可能也和Lambda表达式类似吧，例如准备工作较多，性能理论上略差一些。&lt;/p&gt;

&lt;p&gt;第五个方法，也是我在ASP.NET MVC项目中使用的“标准做法”。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;这次我们把“URL生成”这个简单的目标使用各种方法“演变”了一番，您可以选择地使用。这个演变的过程，其实也是一步步发现缺点，再进行针对性改进的过程。我们虽然使用在ASP.NET MVC的视图作为演示载体，但是它的方式和思路并不仅限于此，它也可以用在ASP.NET MVC的其它方面（如在Controller中生成URL），或是其它模型（如WebForms），甚至与Web开发并无关联的应用程序开发上面。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/several-ways-of-generating-url.html#comments</comments>
      <pubDate>Wed, 28 Oct 2009 16:29:00 GMT</pubDate>
      <lastBuildDate>Wed, 28 Oct 2009 16:29:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>对Action方法的参数进行双向转化</title>
      <link>http://blog.zhaojie.me/2009/10/bidirectional-conversion-for-action-parameter-with-route-binder.html</link>
      <guid>http://blog.zhaojie.me/2009/10/bidirectional-conversion-for-action-parameter-with-route-binder.html</guid>
      <description>&lt;p&gt;昨天有朋友忽然告诉我，在&lt;a href="http://g.cn"&gt;G点中国&lt;/a&gt;上搜索&lt;a href="http://www.google.cn/search?hl=zh-CN&amp;amp;source=hp&amp;amp;q=URL+Routing&amp;amp;btnG=Google+%E6%90%9C%E7%B4%A2&amp;amp;aq=f&amp;amp;oq="&gt;URL Routing&lt;/a&gt;时，我的《&lt;a href="http://blog.zhaojie.me/2009/03/fully-leverage-url-routing.html"&gt;请别埋没了URL Routing&lt;/a&gt;》一文排在首位。这不禁让我汗颜，这是因为从现在的角度看起来，这篇文章的内容虽不能算错，但的确也不算是一种非常合适的做法。那篇文章的目的是展示如何利用URL Routing的扩展能力，将URL和Route Values通过Formatter进行双向的转化。这样便可以在Action方法中使用复杂参数的同时，也可以使用复杂参数得到正确的URL。这个目标是好的，可惜当时的思路有些偏差。现在我总结出了更合适的做法，并已经在项目中大量使用，效果不错。&lt;/p&gt; &lt;p&gt;之前提出那种原因，是因为&lt;a href="http://blog.zhaojie.me/2009/03/limitation-of-model-binder.html"&gt;Model Binder是单向的&lt;/a&gt;。也就是说，使用Model Binder把URL Routing的结果转化成Action的参数时不会有任何问题。例如，我们可以定义一个DateTimeModelBinder，接受一个字符串作为格式参数，这样便可以把URL Routing过程中得到的字符串转化为Action方法的DateTime类型参数了：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Date([&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"yyyy-MM-dd"&lt;/span&gt;)]&lt;span style="color: #2b91af"&gt;DateTime &lt;/span&gt;date) { ... }&lt;/pre&gt;
&lt;p&gt;但是，如果我们想要在视图中使用URL Routing来构造URL：&lt;/p&gt;&lt;pre class="code"&gt;Html.ActionLink(&lt;span style="color: #a31515"&gt;"Tomorrow"&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"Date"&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;{ date = date.AddDays(1) })&lt;/pre&gt;
&lt;p&gt;或借助MvcFutures里提供的强类型（表达式树）辅助方法（其实它也是利用了URL Routing）：&lt;/p&gt;&lt;pre class="code"&gt;Html.ActionLink&amp;lt;&lt;span style="color: #2b91af"&gt;DemoController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Date(date.AddDays(1)), &lt;span style="color: #a31515"&gt;"Tomorrow"&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;问题就来了，因为它们得到的结果与我们的期望相距甚远：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="/Demo/Date/01/01/2003%2000:00:00"&amp;gt;&lt;/span&gt;Tomorrow&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;看这链接中Date后面的那部分，多奇妙，多恶心。出现这个问题的原因在于，我们使用Model Binder可以知道如何将一个字符串正确转化为DateTime对象。但是，在构造URL的时候，如果我们利用了URL Routing，那么直接提供的复杂对象就会通过默认的ToString方法来作为URL的一部分——这显然是有问题的，例如这里的DateTime。因为这一点，在ASP.NET MVC应用程序中使用强类型的表达式树来生成URL几乎是一个不可实现的功能。&lt;/p&gt;
&lt;p&gt;当然，我们是程序员，我们可以扩展。因此，我当时扩展了一个FormatRoute，&lt;a href="http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html"&gt;它使用装饰器模式&lt;/a&gt;，封装了一个RouteBase对象，并且在GetRouteData时使用Formatter将RouteValueDictionary中的值直接从字符串转化成复杂类型的对象——并且在GetVirtualPath方法中作一个反向的操作。由于&lt;a href="http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html"&gt;Routing功能是双向的&lt;/a&gt;，因此我们这么做便可以解决上面这个问题。当然，使用FormatRoute之后，生成Action参数的职责就交到了URL Routing阶段里。在一段时间里，我在项目中定下了这样的“规范”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果是从URL中得到的Action参数，那么转化职责交给URL Routing。 
&lt;li&gt;如果是从别处（如Post过来的数据）得到的Action参数，转化职责交给Model Binder。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;这样，我们在视图中便可以使用强类型的表达式树来生成URL了，同时享受静态检查所得到的便利。&lt;/p&gt;
&lt;p&gt;可惜看上去很美，但用起来一般。原因便是——太麻烦了。试想，我们在配置URL Routing的时候，往往会使用同一条Routing规则对应多个不同的URL形式，最终进入不同的Action方法（如默认的{controller}/{action}/{id}）。但是，复杂类型参数的转化逻辑是根据Action不同而有所改变的。因此，如果我们要将转化参数的职责交给URL Routing的话，势必需要为每个Action方法指定一个Routing规则。那么好，这样Routing规则是不是会变得泛滥？如果我要修改Action，是不是还要去修改对应的Routing规则？这不是把相关的逻辑分散了吗？于是我又想出了其他一些方式来弥补这个问题，例如由Controller负责Routing规则的配置等等，这些尝试就不多提了。&lt;/p&gt;
&lt;p&gt;总之，这个方法的目标正确，但是解决方式有些问题。但是，我们又能如何解决呢？既然事情是出在“URL生成”的方式上，那么我们就来改变一下辅助方法的逻辑吧，反正我们已经为它&lt;a href="http://blog.zhaojie.me/2009/09/make-expression-tree-based-url-construction-faster.html"&gt;优化了性能&lt;/a&gt;，使它支持&lt;a href="http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html"&gt;指定的Route名称&lt;/a&gt;，以及可以&lt;a href="http://blog.zhaojie.me/2009/09/ignore-some-arguments-when-constructing-url-via-expression-tree.html"&gt;忽略某些参数&lt;/a&gt;等等。于是我在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;项目中增加了额外的IRouteBinder接口：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IRouteBinder
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;BindRoute(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: #2b91af"&gt;RouteBindingContext &lt;/span&gt;bindingContext);
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteBindingContext
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public object &lt;/span&gt;Model { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;ModelType { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public string &lt;/span&gt;ModelName { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;与Model Binder的功能正好相反，Route Binder的作用是把一个复杂类型的参数转化为一个RouteValueDictionary。在构造URL的辅助方法中会去检查标记在这个参数，或者这个参数类型中的Model Binder（没错，就是ASP.NET MVC本身获取Model Binder的方式），然后如果这个Model Binder还实现了IRouteBinder接口，则会先进行转化再构造URL，否则就和以前一样，直接使用这个参数的值构造URL。&lt;/p&gt;
&lt;p&gt;因此，文章之前的DateTime参数的转化，便可以使用这样的一个Binder来处理：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTimeBinder &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IModelBinder&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IRouteBinder
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;DateTimeBinder(&lt;span style="color: blue"&gt;string &lt;/span&gt;format)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Format = format;
    }

    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Format { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;IModelBinder Members

    &lt;span style="color: blue"&gt;public object &lt;/span&gt;BindModel(&lt;span style="color: #2b91af"&gt;ControllerContext &lt;/span&gt;controllerContext, &lt;span style="color: #2b91af"&gt;ModelBindingContext &lt;/span&gt;bindingContext)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;modelName = bindingContext.ModelName;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;rawValue = bindingContext.ValueProvider[modelName].RawValue;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.ParseExact(rawValue.ToString(), &lt;span style="color: blue"&gt;this&lt;/span&gt;.Format, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;#endregion

    #region &lt;/span&gt;IRouteBinder Members

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;BindRoute(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: #2b91af"&gt;RouteBindingContext &lt;/span&gt;bindingContext)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;modelName = bindingContext.ModelName;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;model = (&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;)bindingContext.Model;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;routeValues = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;();
        routeValues.Add(modelName, model.ToString(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Format));
        &lt;span style="color: blue"&gt;return &lt;/span&gt;routeValues;
    }

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是，这个世界和平了。&lt;/p&gt;
&lt;p&gt;当然，这个做法和之前相比也有缺陷，那是因为IRouteBinder的功能是在辅助方法内调用的，因此如果您绕开辅助方法使用URL Routing构造URL的话，复杂类型的参数便无法得到转化了。不过权衡之下，现在这个做法使用更加便捷，由于把双向的转化逻辑放在同一处，其维护性也很好。经过我目前项目的使用，效果良好。&lt;/p&gt;
&lt;p&gt;最近，我打算在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;中构建一个合适的示例程序，不会太复杂也但也足以用上MvcPatch里的各种功能（例如一个类似博客形式的应用程序）。如果您有合适的题材，也请及时告诉我。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/bidirectional-conversion-for-action-parameter-with-route-binder.html#comments</comments>
      <pubDate>Fri, 23 Oct 2009 01:47:00 GMT</pubDate>
      <lastBuildDate>Fri, 23 Oct 2009 01:47:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>简化DomainRoute的配置</title>
      <link>http://blog.zhaojie.me/2009/10/simplify-domain-route-configuration.html</link>
      <guid>http://blog.zhaojie.me/2009/10/simplify-domain-route-configuration.html</guid>
      <description>&lt;p&gt;昨天有朋友写邮件告诉我说，他正在项目中尝试着使用我提供的&lt;a href="http://blog.zhaojie.me/2009/08/url-routing-with-domain.html"&gt;DomainRoute&lt;/a&gt;组件。我很高兴，这说明我的努力不是在自娱自乐，是对别人有实际帮助的，也有一些朋友会尝试着自行对项目进行扩展，而不总是靠微软提供的食物来过活。不过他说，他发现DomainRoute的配置非常繁琐，需要为每个Route使用WithDomain，提供了大量重复的信息。他说他也在构建了辅助API，不过似乎效果不够好，问我有没有更好的解决方法。其实是有的，因为我在使用DomainRoute的初期也遇到了这个问题，不过现在已经在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;中提供了个人认为比较令人满意解决方案。&lt;/p&gt; &lt;p&gt;DomainRoute使用了&lt;a href="http://en.wikipedia.org/wiki/Decorator_pattern"&gt;装饰器模式&lt;/a&gt;，是在某个RouteBase对象外部再包裹一层，得到一个新的DomainRoute对象。因此它的构造函数是这样的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;DomainRoute(&lt;span style="color: #2b91af"&gt;RouteBase &lt;/span&gt;innerRoute, &lt;span style="color: blue"&gt;string &lt;/span&gt;pattern, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;defaults, ...)
{
    ...
}&lt;/pre&gt;
&lt;p&gt;此外，还提供了一个扩展方法辅助DomainRoute的构造方式：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainRoute &lt;/span&gt;WithDomain(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteBase &lt;/span&gt;route, &lt;span style="color: blue"&gt;string &lt;/span&gt;pattern, ...)
    {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainRoute&lt;/span&gt;(route, pattern, ...);
    }
}
&lt;/pre&gt;
&lt;p&gt;于是在使用的时候：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;route = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Route&lt;/span&gt;(
    &lt;span style="color: #a31515"&gt;"{controller}/{action}/{id}"&lt;/span&gt;,                           &lt;span style="color: green"&gt;// url
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Home" &lt;/span&gt;}),  &lt;span style="color: green"&gt;// defaults
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MvcRouteHandler&lt;/span&gt;());
routes.Add(
    &lt;span style="color: #a31515"&gt;"Default"&lt;/span&gt;,
    route.WithDomain(
        &lt;span style="color: #a31515"&gt;"http://{area}.mysite.com"&lt;/span&gt;,                         &lt;span style="color: green"&gt;// domain
        &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ area = &lt;span style="color: #a31515"&gt;"products" &lt;/span&gt;}));                        &lt;span style="color: green"&gt;// defaults
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这只是一条配置规则而已，的确够繁琐的。在实际项目中，这样的配置大概会出现十几次甚至更多，更重要的是，这些在同一个域名的配置规则，需要为每一个重复一遍配置的信息，这严重违反了DRY原则。最容易想到的做法，可能是将WithDomain方法的调用提取到另一个方法中去，然后反复调用同一个方法，便可以缓解这个状况了。&lt;/p&gt;
&lt;p&gt;不过，和微软在ASP.NET MVC提供的配置Route规则的做法相比，还是有比较大的差距。我们来回忆一下，它是怎么做的呢？&lt;/p&gt;&lt;pre class="code"&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;"Default"&lt;/span&gt;,                                                  &lt;span style="color: green"&gt;// name
    &lt;/span&gt;&lt;span style="color: #a31515"&gt;"{controller}/{action}/{id}"&lt;/span&gt;,                               &lt;span style="color: green"&gt;// url
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Home"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"Index"&lt;/span&gt;, id = &lt;span style="color: #a31515"&gt;"" &lt;/span&gt;});    &lt;span style="color: green"&gt;// defaults
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;多容易，你看它根本没有“构造”Route类，指定MvcRouteHandler对象这样的操作。这就是在告诉系统&lt;a href="http://blog.zhaojie.me/2009/08/from-delegate-to-others-2.html"&gt;“我们要做什么”，而不是“我们该怎么做”&lt;/a&gt;。MapRoute是一个扩展方法，会向RouteCollection对象中（也就是上面的routes）添加Route规则。于是，我们也可以为DomainRoute添加一个类似的辅助方法。那么先来看看它的使用方式吧：&lt;/p&gt;&lt;pre class="code"&gt;routes.MapDomain(
    &lt;span style="color: #a31515"&gt;"Blog"&lt;/span&gt;,                                                     &lt;span style="color: green"&gt;// prefix
    &lt;/span&gt;&lt;span style="color: #a31515"&gt;"http://{userName}.blogs.{*domain}"&lt;/span&gt;,                        &lt;span style="color: green"&gt;// domain
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ area = &lt;span style="color: #a31515"&gt;"Blog" &lt;/span&gt;},                                      &lt;span style="color: green"&gt;// defaults
    &lt;/span&gt;innerRoutes =&amp;gt;
    {
        innerRoutes.MapRoute(
            &lt;span style="color: #a31515"&gt;"Index"&lt;/span&gt;,                                            &lt;span style="color: green"&gt;// name
            &lt;/span&gt;&lt;span style="color: #a31515"&gt;""&lt;/span&gt;,                                                 &lt;span style="color: green"&gt;// url
            &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Blog"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"Index" &lt;/span&gt;});     &lt;span style="color: green"&gt;// defaults

        &lt;/span&gt;innerRoutes.MapRoute(
            &lt;span style="color: #a31515"&gt;"Default"&lt;/span&gt;,
            &lt;span style="color: #a31515"&gt;"{controller}/{action}/{id}"&lt;/span&gt;,
            &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Home"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"Index"&lt;/span&gt;, id = &lt;span style="color: #a31515"&gt;"" &lt;/span&gt;});
    });

routes.MapDomain(
    &lt;span style="color: #a31515"&gt;"API"&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;"http://api.{*domain}"&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ area = &lt;span style="color: #a31515"&gt;"API" &lt;/span&gt;},
    innerRoutes =&amp;gt;
    {
        ...
    });
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;上面的代码表示为当前项目配置了两个域名，而在第一个DomainRoute下面还配置了两条Route规则。MapDomain方法的最后一个参数是一个“Route收集器”，它的作用是收集一些Route规则。这个收集器上面也定义了MapRoute方法，它的使用方式和ASP.NET MVC中自带的做法保持统一。使用了这样的API之后，我们可以看到上面的信息没有任何的重复，也不需要定义额外的辅助方法。&lt;/p&gt;
&lt;p&gt;值得一提的是，虽然这个API的形式从一开始我打算简化DomainRoute配置时就定下了，但是它的内部的实现却有过很大的改变。一开始，我是打算把DomainRoute使用&lt;a href="http://en.wikipedia.org/wiki/Composite_pattern"&gt;组合模式&lt;/a&gt;做成一个容器，其中包含了N个Route配置。但是昨天的文章提到，&lt;a href="http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html"&gt;由于Route规则是有“命名”的，因此不能使用这种方式&lt;/a&gt;。因此，目前的API并不是这样实现的。&lt;/p&gt;
&lt;p&gt;还是以上面的代码为例，我们在MapDomain方法中指定了一个名称“Blog”，它的收集器收集了两个Route规则，分别叫做“Index”和“Default”。因此，最终RouteCollection上同样会放入两个Route对象，分别叫做“Blog.Index”和“Blog.Default”——这就是MapDomain的第一个参数是“前缀（prefix）”而不是一个“名称（name）”。于是，在生成URL的时候，我们就可以通过指定名称的方式来访问到两个Route规则了。此外，如果您将prefix指定为空字符串或null，它便会保留收集器中的Route命名（如上面的Index和Default）。&lt;/p&gt;
&lt;p&gt;关于MapDomain方法的实现，您可以参考MvcPatch中MvcPatch.Extensions项目的RouteCollectionExtensions类。MvcPatch包含我在博客上提过的几乎所有扩展，并且根据平时实际使用情况进行了改进。如果您对直接使用一个“修改版”的ASP.NET MVC框架有所顾虑的话，也可以参考其中的实现，有选择地运用到自己的项目中去。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/simplify-domain-route-configuration.html#comments</comments>
      <pubDate>Thu, 15 Oct 2009 01:57:00 GMT</pubDate>
      <lastBuildDate>Thu, 15 Oct 2009 01:57:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>浅谈Route组件的设计思考与模式</title>
      <link>http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html</link>
      <guid>http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html</guid>
      <description>&lt;p&gt;Route组件虽然可以说是ASP.NET的“门户”，不过至今为止似乎都被微软当作是二等公民。可能是由于自带的Route类功能已经太强，微软官方或社区内都不太关注RouteBase的扩展。不过有一点是正确的，那就是在大部分情况下的确没有必要去扩展RouteBase。事实上，我构建过不少RouteBase类，不过除了&lt;a href="http://blog.zhaojie.me/2009/08/url-routing-with-domain.html"&gt;DomainRoute&lt;/a&gt;之外，其余的都被我放弃了，例如在大半年前写的《&lt;a href="http://blog.zhaojie.me/2009/03/fully-leverage-url-routing.html"&gt;请别埋没了URL Routing&lt;/a&gt;》中所提供的FormatRoute，在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;中也已经有了更好的替代品（过几天便会谈到这一点）。&lt;/p&gt; &lt;p&gt;RouteBase&lt;a href="http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html"&gt;职责明确&lt;/a&gt;：从请求中获取数据，及根据数据生成虚拟路径。它只有两个方法：GetRouteData和GetVirtualPath，扩展起来非常容易，各种“模式”均可以体现出来。例如DomainRoute和FormatRoute都是使用了&lt;a href="http://en.wikipedia.org/wiki/Decorator_pattern"&gt;装饰器模式&lt;/a&gt;，在内部RouteBase的GetRouteData或GetVirtualPath方法的“前后”再加上一些逻辑（例如DomainRoute中的域名匹配或生成）。有趣的是，在几个月前我还写过一个InterceptRoute类：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InterceptRoute &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;RouteBase
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;InterceptRoute(&lt;span style="color: #2b91af"&gt;RouteBase &lt;/span&gt;innerRoute, &lt;span style="color: #2b91af"&gt;IList&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IRouteInterceptor&lt;/span&gt;&amp;gt; interceptors)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.InnerRoute = innerRoute;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Interceptors = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InterceptorCollection&lt;/span&gt;(interceptors);
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteBase &lt;/span&gt;InnerRoute { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InterceptorCollection &lt;/span&gt;Interceptors { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    ...
}&lt;/pre&gt;
&lt;p&gt;在很多时候，能够像一个组件中插入“横切”的逻辑总是很有用的（例如昨天刚提的&lt;a href="http://blog.zhaojie.me/2009/10/my-view-of-nhibernate-4-interceptor.html"&gt;NHibernate Interceptor&lt;/a&gt;），而上面这个便是在Route规则的各方法前后插入各种逻辑。提供这个逻辑的便是IRouteInterceptor对象，它有四个方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PreGetRouteData&lt;/li&gt;
&lt;li&gt;PostGetRouteData&lt;/li&gt;
&lt;li&gt;PreGetVirtualPath&lt;/li&gt;
&lt;li&gt;PostGetVirtualPath&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;从它们的名称上您也一定可以看得出它们是做什么的。从理论上来说，无论是DomainRoute还是FormatRoute，只要是为现有方法补充前/后置逻辑的扩展，都可以通过提供IRouteInterceptor来实现。不过我除了DomainRoute以外，还真没发现其他的使用环境。这个InterceptRoute似乎也是娱乐价值大于实际价值。因此就在这里一提，等以后忽然发现真有用了我们再拿出来遛遛。&lt;/p&gt;
&lt;p&gt;除了装饰器模式/InterceptRoute之外，我还曾经想过构建另一种“结构性”（如InterceptRoute一样，本身不提供实际用途）的Route扩展，那就是利用了&lt;a href="http://en.wikipedia.org/wiki/Composite_pattern"&gt;组合模式&lt;/a&gt;的Route规则。利用组合模式，我们可以将多个RouteBase对象聚合起来，并且在GetRouteData或GetVirtualPath的时候将职责委派给这些对象。事实上，它的职责就好似Routing框架本身所带的RouteCollection一样——当然，之前我们也谈过，RouteCollection的逻辑&lt;a href="http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html"&gt;并不那么单纯&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;假设我们已经有了这样一个CompositeRoute对象收集了一堆Route规则，那么什么时候会需要这样的场景呢？其实DomainRoute就&lt;font color="#ff0000"&gt;可以&lt;/font&gt;是这样的，因为“一个域名下有多个Route规则”简直是天经地义的事情。但其实事情并没有那么简单，个中原因便是我们昨天所谈论的“&lt;a href="http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html"&gt;命名问题&lt;/a&gt;”。&lt;/p&gt;
&lt;p&gt;由于在配置Route规则的时候，我们要为每个Route提供一个名称——但是这个名称只是对RouteCollection才有效果，确切地说，只有RouteTable.Routes这个RouteCollection实例才会用到这一点。如此的话，使用CompositeRoute势必将原本能够有名称的多个Route规则捆绑在了一起，我们在生成URL的时候就无法通过名称定位到特定的Route上了。&lt;/p&gt;
&lt;p&gt;由于RouteCollection中释放接口有限（也不是开源的，这意味着我们无法改造它），这一点几乎无法通过自定义逻辑的方式来改进。因此在我看来，CompositeRoute几乎无法用在任何场景上——DomainRoute当然也不会使用这种设计方式了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html#comments</comments>
      <pubDate>Wed, 14 Oct 2009 01:46:00 GMT</pubDate>
      <lastBuildDate>Wed, 14 Oct 2009 01:46:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>浅谈Route规则名称的作用，及MvcPatch的处理方式</title>
      <link>http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html</link>
      <guid>http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html</guid>
      <description>&lt;p&gt;国庆前的最后一天，我写了《&lt;a href="http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html"&gt;关于ASP.NET Routing的几点内容&lt;/a&gt;》，其中谈论了ASP.NET Routing作用，设计目的，工作流程等等。不过我还有一个比较重要的东西一笔带过了，不知道您注意到了没有，在向ASP.NET Routing的RouteTable.Routes属性（一个RouteCollection对象）中添加Route规则的时候，我们会同时指定一个“名称”，在微软给出的官方“广告”中，似乎看不出这个名称有什么用。但事实上，它的功能非常关键。&lt;/p&gt; &lt;p&gt;那么我们通过一个实际的例子来看一下，Route规则的“名称”到底有什么作用：&lt;/p&gt;&lt;pre class="code"&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;"Article.List"&lt;/span&gt;,                                         &lt;span style="color: green"&gt;// route name
    &lt;/span&gt;&lt;span style="color: #a31515"&gt;"articles/{*searchCriteria}"&lt;/span&gt;,                           &lt;span style="color: green"&gt;// pattern
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Article"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List" &lt;/span&gt;});       &lt;span style="color: green"&gt;// defaults

&lt;/span&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;"Product.List"&lt;/span&gt;,                                         &lt;span style="color: green"&gt;// route name
    &lt;/span&gt;&lt;span style="color: #a31515"&gt;"products/{*searchCriteria}"&lt;/span&gt;,                           &lt;span style="color: green"&gt;// pattern
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Product"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List" &lt;/span&gt;});       &lt;span style="color: green"&gt;// defaults
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;以上两个Route规则，分别是“文章（article）”和“产品（product）”的搜索URL。与微软“广告”中一直使用的{controller}/{action}/{id}不同，我们实际生产过程中，一些如controller或action这样的数据无法从URL中捕获到，因此我们只是“匹配URL”并且“指定默认值”。就“捕获URL”这方面，这样的配置非常完美。但是我也多次强调，Route规则还会负责“生成虚拟路径”。那么这样的配置在这方面会有什么问题呢？请看下面这个数据：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; data = &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Product"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List"&lt;/span&gt;, searchCriteria = &lt;span style="color: #a31515"&gt;"..." &lt;/span&gt;}&lt;/pre&gt;
&lt;p&gt;我们可以使用这个匿名对象来构造一个RouteValueDictionary，其中包含了生成虚拟路径所需要的数据。于是我们可能会调用这个方法来得到结果：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;RouteTable&lt;/span&gt;.Routes.GetVirtualPath(requestContext, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(data));&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;很显然，这个数据是要生成“产品”的搜索路径，但是您猜一下，我们能够得到正确结果吗？&lt;/p&gt;
&lt;p&gt;不行。&lt;/p&gt;
&lt;p&gt;因为RouteCollection的GetVirtualPath方法会依次遍历其中所有Route对象，访问其GetVirtualPath方法，并把第一个非null参数作为结果。如果项目中的配置如以上两个，则这样的数据永远会被Article.List这个规则捕获到——于是生成的永远便是Article.List的虚拟路径了。&lt;/p&gt;
&lt;p&gt;对于这个问题一般有两种解决方法，一是为Route规则添加“约束”：&lt;/p&gt;&lt;pre class="code"&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;"Article.List"&lt;/span&gt;,                                         &lt;span style="color: green"&gt;// route name
    &lt;/span&gt;&lt;span style="color: #a31515"&gt;"articles/{*searchCriteria}"&lt;/span&gt;,                           &lt;span style="color: green"&gt;// pattern
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Article"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List" &lt;/span&gt;},        &lt;span style="color: green"&gt;// defaults
    &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Article"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List" &lt;/span&gt;});       &lt;span style="color: green"&gt;// constraints
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是，这个Route对象在生成虚拟路径的时候，就会关注当前的RouteValueDictionary中包含的值是否符合约束。如果不符合，则直接返回null。如此，RouteCollection便会继续尝试下一个Route规则——可能就是Product.List，于是我们得到了想要的数据。&lt;/p&gt;
&lt;p&gt;第二种解决方法便是在构造URL的时候指定一个名称：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;RouteTable&lt;/span&gt;.Routes.GetVirtualPath(requestContext, &lt;span style="color: #a31515"&gt;"Product.List"&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(data));&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如此，RouteCollection便会直接去找到Product.List所对应的Route对象，并且直接使用它的GetVirtualPath方法获得虚拟路径——即使返回null，也不会尝试其他Route规则。这么做的好处是避免Route规则之间在生成虚拟路径时产生冲突，并且由于避免了便利所造成的无效尝试，于是程序性能也会有些许提高。&lt;/p&gt;
&lt;p&gt;在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;中也有对Route配置名称的支持：在MvcPatch.Extensions.dll中包含一个RouteNameAttribute对象，我们可以用它对某个Action方法进行标记，如：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;RouteName&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"Product.List"&lt;/span&gt;)]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;List(&lt;span style="color: blue"&gt;string &lt;/span&gt;searchCriteria)
{
    ...
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是，如果您在View中使用这样的方法来构造URL：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Url.Action&amp;lt;&lt;span style="color: #2b91af"&gt;ProductController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.List(...)) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;MvcPatch便会查看这个Action方法是否有RouteName标记。如果有，则通过直接指定名称的方式去构造虚拟路径，否则便还是传统的“遍历”方式。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html#comments</comments>
      <pubDate>Tue, 13 Oct 2009 02:00:00 GMT</pubDate>
      <lastBuildDate>Tue, 13 Oct 2009 02:00:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>关于ASP.NET Routing的几点内容</title>
      <link>http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html</link>
      <guid>http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html</guid>
      <description>&lt;p&gt;好吧，我承认这个标题有些八股。&lt;/p&gt; &lt;p&gt;在之前的文章中，有一些朋友会问我一些关于ASP.NET Routing的内容。这个组件的重要性越来越大，ASP.NET MVC，ASP.NET Dynamic Data都用到了ASP.NET Routing。事实上，在ASP.NET 4.0中还会出现对ASP.NET WebForms的支持。可惜的是，目前对于ASP.NET Routing的文档和描述内容都很少。因此，有的时候一些朋友可能无法理解我一些扩展的设计思路。现在我打算详细解释一下有关ASP.NET Routing中最常见的几个问题。&lt;/p&gt; &lt;h1&gt;ASP.NET Routing的作用究竟是什么&lt;/h1&gt; &lt;p&gt;ASP.NET Routing的作用有两个：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;从请求中捕获数据。  &lt;li&gt;从数据生成虚拟路径。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;可见，ASP.NET Routing的功能是&lt;font color="#ff0000"&gt;双向&lt;/font&gt;的。值得注意的是，这两个操作严格说来并不对称。因为第1点是从“请求”中捕获数据，而数据源并不一定是一个URL，可能是Server Variables，可能是Header；与此对应，从数据生成的虚拟路径，则一定是生成一个URL，一个字符串。&lt;/p&gt; &lt;p&gt;平时使用ASP.NET Routing的时候，我们会大量利用ASP.NET Routing组件中的Route类，它的作用是从Virtual Path中获取数据，并提供一些如默认值，约束等高级功能。但是，对于ASP.NET Routing组件，或者说它的“引擎”使用的是一个抽象的类型“RouteBase”。而Route只是RouteBase的一个实现罢了。&lt;/p&gt; &lt;p&gt;在之前的文章中，我曾经提出过其他一些扩展，如&lt;a href="http://blog.zhaojie.me/2009/03/fully-leverage-url-routing.html"&gt;FormatRoute&lt;/a&gt;或&lt;a href="http://blog.zhaojie.me/2009/08/url-routing-with-domain.html"&gt;DomainRoute&lt;/a&gt;。&lt;/p&gt; &lt;h1&gt;为什么Route不使用正则表达式&lt;/h1&gt; &lt;p&gt;ASP.NET Routing中自带的Route类会根据我们指定的“路径模板”，从请求的虚拟路径中捕获数据。而这个“模板”是类似这种形式的：&lt;/p&gt;&lt;pre class="code"&gt;{controller}/{action}/{id}&lt;/pre&gt;
&lt;p&gt;于是根据当前请求的虚拟路径，便可从中捕获出controller，action和id三个值了。不过有朋友说，为什么不使用正则表达式来捕获数据呢？例如：&lt;/p&gt;&lt;pre class="code"&gt;(?&amp;lt;controller&amp;gt;\w+)/(?&amp;lt;action&amp;gt;\w+)/(?&amp;lt;id&amp;gt;\d+)&lt;/pre&gt;
&lt;p&gt;使用命名捕获组的正则表达式来虚拟路径，自然也可以得到controller，action和id的值了。此外，使用正则表达式的另一个好处是可以严格约束路径中的字符，例如上面的正则表达式将controller和action限制为单词字符，而将id限制为数字，这样不满足这种形式的路径便无法获得匹配了。不过问题出在哪里呢？&lt;/p&gt;
&lt;p&gt;这里的问题便是：ASP.NET Routing的工作是双向的，而如果使用正则表达式，捕获数据自然没话说，但是从正则表达式来生成一个字符串就不容易了，由于正则表达式的匹配形式非常广，很多时候甚至根本无法逆向得到一个字符串。因此现在，如果您要对某个值进行约束，只能为Route对象提供一个约束条件了：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"\w+"&lt;/span&gt;, action=&lt;span style="color: #a31515"&gt;"\w+"&lt;/span&gt;, id=&lt;span style="color: #a31515"&gt;"\d+" &lt;/span&gt;}&lt;/pre&gt;
&lt;p&gt;当然，如果您需要的话，可以实现一个正则表达式匹配规则的子集，使生成字符串的工作变得可行，那么也是没有问题的。&lt;/p&gt;
&lt;h1&gt;ASP.NET Routing工作流程&lt;/h1&gt;
&lt;p&gt;ASP.NET Routing的工作有两方面，处理请求和生成虚拟路径。处理请求时的流程已经在&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-routing-request-processing.html"&gt;之前的文章&lt;/a&gt;里详细谈过了，这里再谈一下ASP.NET Routing生成虚拟路径的做法。&lt;/p&gt;
&lt;p&gt;ASP.NET Routing在生成虚拟路径时还是使用RouteCollection类型上的方法，自然我们是像RouteTable.Routes属性中注册的Routing规则，那么自然也是从RouteTable.Routes属性中获取虚拟路径。RouteCollection类型中有一个GetVirtualPath方法，返回一个VirtualPathData对象，其中包含一个VirtualPath属性，便是最终得到的虚拟路径。&lt;/p&gt;
&lt;p&gt;GetVirtualPath方法有两个重载，第一个是：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;VirtualPathData &lt;/span&gt;GetVirtualPath(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values)&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;RequestContext对象是当前请求的上下文，而RouteValueDictionary包含的则是用于构造虚拟路径的数据。与处理请求的方式相同，RouteCollection类型的GetVirtualPath方法也会&lt;font color="#ff0000"&gt;依次&lt;/font&gt;调用每个RouteBase对象的GetVirtualPath方法，并返回第一个不为null的结果（事实上还会经过处理，且看下文）。如果一个RouteBase对象都无法匹配当前的提供的数据，则RouteCollection的GetVirtualPath返回null。&lt;/p&gt;
&lt;p&gt;GetVirtualPath方法的另一个重载则会提供一个name参数：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;VirtualPathData &lt;/span&gt;GetVirtualPath(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: blue"&gt;string&lt;/span&gt; name, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values)&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;name参数的作用是“指定”一个Routing规则，也就是一个特定的RouteBase对象。如果这个RouteBase对象返回null，则GetVirtualPath方法直接返回null。指定name的优势在于代码可读性高（开发人员可以直接找到对应的Routing策略）、性能有一定优势（无需遍历每个Routing规则），以及Routing规则之间不会产生冲突。&lt;/p&gt;
&lt;h1&gt;ASP.NET Routing支持域名吗？&lt;/h1&gt;
&lt;p&gt;不支持，从设计上就不支持。&lt;/p&gt;
&lt;p&gt;这一点从RouteCollection的GetVirtualPath方法实现中可以看到，这个方法在遍历每个RouteBase对象并得到第一个不为null的结果后，它不是立即返回，还要使用GetUrlWithApplicationPath方法进行处理：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static string &lt;/span&gt;GetUrlWithApplicationPath(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
{
    &lt;span style="color: blue"&gt;string &lt;/span&gt;str = requestContext.HttpContext.Request.ApplicationPath ?? &lt;span style="color: blue"&gt;string&lt;/span&gt;.Empty;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(!str.EndsWith(&lt;span style="color: #a31515"&gt;"/"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;StringComparison&lt;/span&gt;.OrdinalIgnoreCase))
    {
        str = str + &lt;span style="color: #a31515"&gt;"/"&lt;/span&gt;;
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;requestContext.HttpContext.Response.ApplyAppPathModifier(str + url);
}&lt;/pre&gt;
&lt;p&gt;url参数的含义是“虚拟路径”，GetUrlWithApplicationPath方法的作用则是为其加上“应用程序目录”。例如，我们一个站点可能不是部署在“/”下，而是部署在“/MvcApp”这个“子站点”中。这样，虽然RouteBase的GetVirtualPath返回的是虚拟路径“Home/Index/5”，但是RouteCollection的GetVirtualPath方法在使用GetUrlWithApplicationPath处理之后，最终返回的路径便是/MvcApp/Home/Index/5这样的字符串了。&lt;/p&gt;
&lt;p&gt;因此可以这么说，ASP.NET Routing从设计上就不支持域名的概念，虽然我们可以扩展RouteBase对象，但是我们无法扩展RouteCollection的GetVirtualPath方法。换句话说，即使我们的&lt;a href="http://blog.zhaojie.me/2009/08/url-routing-with-domain.html"&gt;DomainRoute&lt;/a&gt;可以返回类似“http://www.cnblogs.com/Home”这样的URL，被RouteCollection的GetVirtualPath方法一鼓捣又会在之前加一条斜杠——因此，在&lt;a href="http://MvcPath.codeplex.com"&gt;MvcPatch&lt;/a&gt;的MvcPatch.Routing项目中，我为RouteCollection提供了一个名为GetVirtualPathEx的扩展方法，如果它发现URL以http或https开头，则不会拼接上应用程序目录。至于其他情况下，则和原来的GetVirtualPath方法行为一致。&lt;/p&gt;
&lt;p&gt;如果您要使用DomainRoute，那么请在需要的使用GetVirtualPathEx而不是原来的GetVirtualPath方法。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html#comments</comments>
      <pubDate>Wed, 30 Sep 2009 02:44:00 GMT</pubDate>
      <lastBuildDate>Wed, 30 Sep 2009 02:44:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>ASP.NET Routing对请求的处理方式</title>
      <link>http://blog.zhaojie.me/2009/09/aspnet-routing-request-processing.html</link>
      <guid>http://blog.zhaojie.me/2009/09/aspnet-routing-request-processing.html</guid>
      <description>&lt;p&gt;原本这是《关于ASP.NET Routing的几点内容》一文中的一节，不过等写完这节之后发现这块内容已经比较完整了，而且它本身也是独立和最为常见的部分，因此我把它提取出来单独成文。至于那片文章的其他部分我会再修改一下，明天发布。希望这些内容会对您理解ASP.NET Routing工作方式，以及阅读ASP.NET Routing的代码有所帮助。&lt;/p&gt; &lt;p&gt;首先，如果您需要在项目中使用在ASP.NET Routing的功能，则需要在web.config文件中配置一个HttpModule：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;"&lt;span style="color: blue"&gt;UrlRoutingModule&lt;/span&gt;" &lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;"&lt;span style="color: blue"&gt;System.Web.Routing.UrlRoutingModule, System.Web.Routing, ...&lt;/span&gt;" &lt;span style="color: blue"&gt;/&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;其次，您应该在Application_Start中向RouteCollection类型的RouteTable.Routes集合中添加一系列RouteBase对象，并为每个RouteBase对象指定一个独立的名称（大小写无关）。当然，您也可以在运行时动态添加或删除内容（RouteCollection对象是线程安全的），只不过我们平时不太会去这么做而已。值得注意的是，RouteCollections里的RouteBase对象，它们的顺序是非常重要的。&lt;/p&gt;
&lt;p&gt;UrlRouteModule会监听ASP.NET Request Pipelines的PostResolveRequestCache事件，在这个事件中UrlRouteModule会将当前的HttpContext作为参数调用RouteTable.Routes集合的GetRouteData方法。在RouteCollection的GetRouteData方法中，又会&lt;font color="#ff0000"&gt;依次&lt;/font&gt;将HttpContext传入每一个RouteBase对象的GetRouteData方法，如果中途某个RouteBase对象返回了一个非null的结果，则这个结果便会直接返回给UrlRouteModule。&lt;/p&gt;
&lt;p&gt;如果UrlRouteModule调用RouteTable.Routes.GetRouteData方法得到了null，则“一切都像没有发生过”。如果GetRouteData方法得到了结果——一个RouteData对象，此时RouteData.Values便会包含请求中捕获到的数据。RouteData中另一个重要的成员便是RouteData.RouteHandler属性，它返回一个IRouteHandler对象。IRouteHandler接口中只有一个方法GetHttpHandler，它接受RequestContext作为参数，并返回一个IHttpHandler对象。如ASP.NET MVC框架在利用ASP.NET Routing时，便会使用MvcRouteHandler来返回一个MvcHandler对象。&lt;/p&gt;
&lt;p&gt;不过，UrlRouteModule在得到了IRouteHandler对象之后，并不会直接调用其GetHttpHandler方法，而是判断它是不是ASP.NET Routing自带的StopRoutingHandler类型。StopRoutingHandler是个特殊的IRouteHandler对象，它的作用只是告诉UrlRouteModule，虽然某个规则匹配成功了，但是——也还是当什么都没发生过吧。因此，如果我们想要“跳过”一些形式的请求，往往则需要将“忽略”功能放在其他所有规则之前。如：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static void &lt;/span&gt;RegisterRoutes(&lt;span style="color: #2b91af"&gt;RouteCollection &lt;/span&gt;routes)
{
    routes.IgnoreRoute(&lt;span style="color: #a31515"&gt;"{resource}.axd/{*pathInfo}"&lt;/span&gt;);
    routes.IgnoreRoute(&lt;span style="color: #a31515"&gt;"scripts/{*pathInfo}"&lt;/span&gt;);
    routes.IgnoreRoute(&lt;span style="color: #a31515"&gt;"images/{*pathInfo}"&lt;/span&gt;);

    routes.MapRoute(
        &lt;span style="color: #a31515"&gt;"Default"&lt;/span&gt;,                                              &lt;span style="color: green"&gt;// Route name
        &lt;/span&gt;&lt;span style="color: #a31515"&gt;"{controller}/{action}/{id}"&lt;/span&gt;,                           &lt;span style="color: green"&gt;// URL with parameters
        &lt;/span&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Home"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"Index"&lt;/span&gt;, id = &lt;span style="color: #a31515"&gt;"" &lt;/span&gt;}  &lt;span style="color: green"&gt;// Parameter defaults
    &lt;/span&gt;);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;IgnoreRoute是定义在ASP.NET MVC中，基于RouteCollection类型的扩展方法。它会向RouteCollection中添加一个Route对象，而这个Route对象在匹配成功时返回的RouteData对象，其RouteHandler属性便为一个StopRoutingHandler，于是余下的Routing规则也不会继续匹配了——这一点和RouteBase对象返回null不同，因为如果返回null，则余下的规则还会依次匹配。如果返回了一个包含StopRoutingHander的RouteData，则剩下的Routing规则全部跳过。&lt;/p&gt;
&lt;p&gt;如果UrlRouteModule得到的IRouteHandler对象不是StopRoutingHandler，则便会通过其GetHttpHandler方法获得那个IHttpHandler对象。这个IHttpHandler对象会被放入HttpContext的Items集合中。至此，Request Pipeline的PostResolveRequestCache事件便结束了。&lt;/p&gt;
&lt;p&gt;UrlRouteModule还会监听PostMapRequest事件，此时Module便会查找HttpContext.Items集合的特定位置中是否包含一个IHttpHandler对象，如果存在，则会将这个对象设为当前HttpContext对象的Handler属性的值。于是当ASP.NET继续执行下去时，便会调用这个Handler的ProcessRequest方法来处理请求了。&lt;/p&gt;
&lt;p&gt;如果这个IHttpHandler对象是MvcHttpHandler，那么它便会从RouteData中获取一些数据，构造Controller对象，执行Action等等。如果它是一个DynamicDataHandler，或是WebForm的HttpHandler，那么剩下的便是各自的模型的处理方式了。&lt;/p&gt;
&lt;p&gt;因此，ASP.NET Routing是一个通用的组件，它不涉及到任何具体的请求处理方式。如果您需要，也可以自己基于它进行开发——如&lt;a href="http://code.google.com/p/fubumvc/"&gt;FubuMvc项目&lt;/a&gt;就是这么做的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/aspnet-routing-request-processing.html#comments</comments>
      <pubDate>Tue, 29 Sep 2009 07:09:00 GMT</pubDate>
      <lastBuildDate>Tue, 29 Sep 2009 07:09:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>使用Model Binder绑定Action参数字段时的取舍问题</title>
      <link>http://blog.zhaojie.me/2009/09/exclude-field-when-bind-action-parameters.html</link>
      <guid>http://blog.zhaojie.me/2009/09/exclude-field-when-bind-action-parameters.html</guid>
      <description>&lt;p&gt;刚才在看代码的时候忽然发现了一件可能会成为问题的情况，而这个情况还挺隐蔽的。因此，我原本写到一半的东西就暂时停下，顺延至明天，而现在先来谈谈这个问题。这个问题就是在使用DefaultModelBinder在绑定字段时的取舍问题。而您在使用ASP.NET MVC的时候不妨也检查一下，看看有没有这方面的情况。&lt;/p&gt; &lt;p&gt;ASP.NET MVC的一个便利之处就在于，它可以将Route Value，Form或Query String里的数据自动构造成Actoin方法的参数。例如我们ProductController中有个叫做Create的Action方法：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;AcceptVerbs&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;HttpVerbs&lt;/span&gt;.Post)]
&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;BaseController
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Create(&lt;span style="color: #2b91af"&gt;Product &lt;/span&gt;product)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;db = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DataContext&lt;/span&gt;();
        db.InsertOnSubmit(product);
        db.SubmitChanges();
    }
}&lt;/pre&gt;
&lt;p&gt;这是一段使用LINQ to SQL向数据库中插入数据的方法，很简单，您一定也看得懂。ASP.NET MVC的DefaultModelBinder类会根据Product类型的字段，从Post过来的数据中获取一些值，然后构造一个Product对象。例如Product类型是这样定义的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Product
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public virtual int &lt;/span&gt;ProductID { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public virtual string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public virtual float &lt;/span&gt;Price { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public virtual int &lt;/span&gt;ViewCount { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;四个字段：ID、名称、价格以及浏览量。当用户访问/Product/Create页面时（不是刚才Post Only的Action，而是用于显示创建页的Action），客户端便会获得这样的form表单：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;form &lt;/span&gt;&lt;span style="color: red"&gt;action&lt;/span&gt;&lt;span style="color: blue"&gt;="/Product/Action" &lt;/span&gt;&lt;span style="color: red"&gt;method&lt;/span&gt;&lt;span style="color: blue"&gt;="post"&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="text" &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;="Name" /&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="text" &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;="Price" /&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="submit" /&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;form&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;当然，实际情况下这个表单是不会那么赤裸裸的，会有文字说明，会有校验逻辑等等。不过它的含义总归是向/Product/Action这个URL中Post两个字段：Name和Price，最后在服务器端执行得到的结果便是创建了一个指定了Name和Price的对象，它的ViewCount是0——为什么是0？因为客户端没有提供数据咯。&lt;/p&gt;
&lt;p&gt;问题就在这里，既然客户端没有提供数据，于是ViewCount是0，但如果客户端提供了数据又该怎么办呢？例如，用户完全有能力在客户端多加一个字段，变成这样：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;form &lt;/span&gt;&lt;span style="color: red"&gt;action&lt;/span&gt;&lt;span style="color: blue"&gt;="/Product/Action" &lt;/span&gt;&lt;span style="color: red"&gt;method&lt;/span&gt;&lt;span style="color: blue"&gt;="post"&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="text" &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;="Name" /&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="text" &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;="Price" /&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="hidden" &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;="ViewCount" &lt;/span&gt;&lt;span style="color: red"&gt;value&lt;/span&gt;&lt;span style="color: blue"&gt;="1000" /&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="submit" /&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;form&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如此创建出的对象，它的ViewCount自然就直接是1000了。您可能会问，那么用户为什么会知道是ViewCount这个字段？我想，他应该可以从网站其他地方获取蛛丝马迹的，例如某个元素的id，例如某次请求所提交的参数等等。于是，他就可以这么一试了。&lt;/p&gt;
&lt;p&gt;写到这儿，我忽然又想到，有些朋友在写form的时候可能会偷一下懒，省略了form的action属性，或者这样构造一个表单：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;using &lt;/span&gt;(Html.BeginForm()) { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    ...
    
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; }; &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如此，form便会提交给当前URL。这样做的问题在于，如果用户通过/Product/Create?ViewCount=1000这样的URL来访问创建页面，这样提交后的数据也会自动填写上ViewCount。&lt;/p&gt;
&lt;p&gt;当然这个问题很容易解决，例如在创建Product的Create方法中可以主动为product参数的属性设置默认值，或者使用ASP.NET MVC自带的机制：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;AcceptVerbs&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;HttpVerbs&lt;/span&gt;.Post)]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Create([&lt;span style="color: #2b91af"&gt;Bind&lt;/span&gt;(Exclude=&lt;span style="color: #a31515"&gt;"ViewCount"&lt;/span&gt;)]&lt;span style="color: #2b91af"&gt;Product &lt;/span&gt;product)
{
    ...
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;您可以为product参数加上BindAttribute标记，指定哪些字段不需要绑定（或哪些需要），这样即使客户端提交了某些字段，DefaultModelBinder也会忽略这些数据了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/exclude-field-when-bind-action-parameters.html#comments</comments>
      <pubDate>Mon, 28 Sep 2009 05:57:00 GMT</pubDate>
      <lastBuildDate>Mon, 28 Sep 2009 05:57:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>在视图中使用递归生成树状结构</title>
      <link>http://blog.zhaojie.me/2009/09/rendering-tree-like-structure-recursively.html</link>
      <guid>http://blog.zhaojie.me/2009/09/rendering-tree-like-structure-recursively.html</guid>
      <description>&lt;p&gt;在开发过程中往往会有一个需求，就是将一个树状的数据结构在视图中表示出来。例如最传统的多级分类，系统中有一系列根分类，每个分类中又带有一些子分类，而我们的目标便是在页面上生成一个由ul和li嵌套组成的HTML结构。这个问题看似简单，但是如何让实现变的轻松、易于使用也是一个值得讨论的问题。这次就来谈谈这部分的情况。&lt;/p&gt; &lt;h1&gt;实现目标&lt;/h1&gt; &lt;p&gt;首先来明确一下实现目标。例如我们有一个Category对象，表示一个类别：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; Children { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;
&lt;p&gt;然后我们准备一个嵌套的数据结构：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Categories()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;model = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;
    {
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
        &lt;/span&gt;{
            Name = &lt;span style="color: #a31515"&gt;"Category 1"&lt;/span&gt;,
            Children = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;
            {
                &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
                &lt;/span&gt;{
                    Name = &lt;span style="color: #a31515"&gt;"Category 1 - 1"&lt;/span&gt;,
                    Children = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;()
                },
                &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
                &lt;/span&gt;{
                    Name = &lt;span style="color: #a31515"&gt;"Category 1 - 2"&lt;/span&gt;,
                    Children = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;()
                },
            }
        },
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
        &lt;/span&gt;{
            Name = &lt;span style="color: #a31515"&gt;"Category 2"&lt;/span&gt;,
            Children = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;
            {
                &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
                &lt;/span&gt;{
                    Name = &lt;span style="color: #a31515"&gt;"Category 2 - 1"&lt;/span&gt;,
                    Children = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;()
                },
                &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
                &lt;/span&gt;{
                    Name = &lt;span style="color: #a31515"&gt;"Category 2 - 2"&lt;/span&gt;,
                    Children = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;()
                },
            }
        },
    };

    &lt;span style="color: blue"&gt;return &lt;/span&gt;View(model);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;自然还会有一个Model类型为List&amp;lt;Category&amp;gt;的视图：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;#64; &lt;/span&gt;&lt;span style="color: #a31515"&gt;Page &lt;/span&gt;&lt;span style="color: red"&gt;Language&lt;/span&gt;&lt;span style="color: blue"&gt;="C#" &lt;/span&gt;&lt;span style="color: red"&gt;Inherits&lt;/span&gt;&lt;span style="color: blue"&gt;="System.Web.Mvc.ViewPage&amp;lt;List&amp;lt;Category&amp;gt;&amp;gt;" &lt;/span&gt;&lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;...&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;而我们的目标，便是要在视图中显示出这样的HTML：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Category 1
        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Category 1 - 1 &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Category 1 - 2 &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Category 2
        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Category 2 - 1 &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Category 2 - 2 &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;那么我们又该怎么做呢？&lt;/p&gt;
&lt;h1&gt;使用局部视图&lt;/h1&gt;
&lt;p&gt;如果在平时让我们处理这种数据结构，很明显会使用递归。但是，在视图模板中表示递归是非常困难的，因此我们会借助局部视图。例如：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;#64; &lt;/span&gt;&lt;span style="color: #a31515"&gt;Control &lt;/span&gt;&lt;span style="color: red"&gt;Language&lt;/span&gt;&lt;span style="color: blue"&gt;="C#" &lt;/span&gt;&lt;span style="color: red"&gt;Inherits&lt;/span&gt;&lt;span style="color: blue"&gt;="ViewUserControl&amp;lt;List&amp;lt;Category&amp;gt;&amp;gt;" &lt;/span&gt;&lt;span style="background: #ffee62"&gt;%&amp;gt;

&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;if &lt;/span&gt;(Model.Count &amp;gt; 0) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;Model) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    
            &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
                &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(cat.Name) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.RenderPartial(&lt;span style="color: #a31515"&gt;"CategoryTree"&lt;/span&gt;, cat.Children); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;            &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    
        &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这个局部视图的作用便是生成我们想要的HTML片段。在局部视图内部还会调用自身来生成下一级的HTML。在主视图中生成局部视图也很容易：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.RenderPartial(&lt;span style="color: #a31515"&gt;"CategoryTree"&lt;/span&gt;, Model); &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这就实现了递归，也是实现这一功能最易于理解的方式。只可惜这种做法比较麻烦，需要定义额外的局部视图。这种局部视图往往只是为一个主视图服务的，它会和主视图的前后环境相关，分离开去在维护上就会有些不便了。&lt;/p&gt;
&lt;h1&gt;在页面中定义委托&lt;/h1&gt;
&lt;p&gt;我们知道，&lt;a href="http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html"&gt;在运行时ASP.NET页面会被编译为一个类&lt;/a&gt;，而其中的各种标记，或内嵌的代码都会被作为一个方法里定义或执行的局部变量。如果说我们要在一个方法内“定义”另一个方法，自然只能是使用匿名方法的特性来构造一个委托了。这个委托为了可以“递归”调用，就必须这么写：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;&amp;gt; renderCategories = &lt;span style="color: blue"&gt;null&lt;/span&gt;; &lt;span style="color: green"&gt;// 先设为null &lt;/span&gt;&lt;span style="background: #ffee62"&gt;%&amp;gt;
&amp;lt;%&lt;/span&gt; renderCategories = (categories) =&amp;gt; { &lt;span style="color: green"&gt;// 再定义 &lt;/span&gt;&lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;if &lt;/span&gt;(categories.Count &amp;gt; 0) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;categories) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        
                &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
                    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(cat.Name) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; renderCategories(cat.Children); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;

&amp;lt;%&lt;/span&gt; }; &lt;span style="background: #ffee62"&gt;%&amp;gt;

&amp;lt;%&lt;/span&gt; renderCategories(Model); &lt;span style="color: green"&gt;// 最后再调用，即生成HTML &lt;/span&gt;&lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这段代码的确可以生成HTML，但是我不喜欢。我不喜欢的原因倒不是因为这是我眼中的“伪递归”，而是因为这在页面将“定义”与“执行”分开了。事实上，在我们看到HTML标记及逻辑控制的地方并没有在“同时”生成内容，这只是在“定义”。生成内容的时机其实是在最后对renderCategories委托的调用，这容易造成一定误导，因为最后的“生成”可能会遗漏，而定义和生成之间可能会插入一些其他内容。&lt;/p&gt;
&lt;p&gt;这种做法的优势，就是在于不用额外分离出一个局部视图，它直接写在主视图中，易于维护，也相对易于理解。&lt;/p&gt;
&lt;h1&gt;使用Lambda表达式构建递归方法&lt;/h1&gt;
&lt;p&gt;“定义”与“执行”分离的一个重要原因，还是因为Lambda表达式无法定义递归函数。否则，我们就可以直接定义一个递归执行的委托，并在最后跟上Invoke或直接调用即可。&lt;/p&gt;
&lt;p&gt;因此，其实这里就正是&lt;a href="http://blog.zhaojie.me/2009/08/recursive-lambda-expressions.html"&gt;使用Lambda表达式编写递归函数&lt;/a&gt;的用武之地。例如，我们补充一个类似的Fix方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt; Fix&amp;lt;T&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt;, &lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt;&amp;gt; f)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;x =&amp;gt; f(Fix(helper, f))(x);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是在视图中便可以：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Fix&amp;lt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;&amp;gt;(render =&amp;gt; categories =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;if &lt;/span&gt;(categories.Count &amp;gt; 0) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;categories) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        
                &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
                    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(cat.Name) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; render(cat.Children); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;

&amp;lt;%&lt;/span&gt; })&lt;font color="#ff0000"&gt;.Invoke(Model)&lt;/font&gt;; &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;不过严格说来，它还是“定义”与“执行”分离的，只是我们现在可以把它们写在一块儿。此外，Fix方法对于模板中的HTML生成实在没有什么意义。&lt;/p&gt;
&lt;h1&gt;提供一个Render方法辅助递归&lt;/h1&gt;
&lt;p&gt;Fix方法对页面生成没有什么作用，不过如果有一个可以辅助递归的Render方法便有意义多了：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt; Fix&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt;, &lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt;&amp;gt; f)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;x =&amp;gt; f(Fix(f))(x);
    }

    &lt;span style="color: blue"&gt;public static void &lt;/span&gt;Render&amp;lt;T&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;helper, T model, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt;, &lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt;&amp;gt; f)
    {
        Fix(f)(model);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是，我们在页面中就可以这么写：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Render(Model, render =&amp;gt; categories =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;if &lt;/span&gt;(categories.Count &amp;gt; 0) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;categories) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        
                &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
                    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(cat.Name) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; render(cat.Children); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;

&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;您是否觉得这么做难以理解？我不这么认为，因为从语法上来说，这种HTML生成方式是很简单的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Render(参数, 用于递归的方法 =&amp;gt; 当前参数 =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    ...&lt;span style="background: #ffee62"&gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; 递归调用 &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    ...&lt;span style="background: #ffee62"&gt;

&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;至于背后的原理？关心那些做什么。&lt;/p&gt;
&lt;h1&gt;性能&lt;/h1&gt;
&lt;p&gt;可惜，根据&lt;a href="http://blog.zhaojie.me/2009/09/recursive-lambda-expressions-benchmark.html"&gt;性能比较&lt;/a&gt;，使用Fix构造递归的做法，比使用SelfApplicable的做法要慢上许多。虽然我认为这里不会是性能的关键，但如果您实在觉得无法接受的话，也可以利用SelfApplicable来构造递归的HTML呈现方式。其辅助方法为：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public delegate void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SelfApplicable&lt;/span&gt;&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;SelfApplicable&lt;/span&gt;&amp;lt;T&amp;gt; self, T arg);

&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static void &lt;/span&gt;Render&amp;lt;T&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;helper, T model, &lt;span style="color: #2b91af"&gt;SelfApplicable&lt;/span&gt;&amp;lt;T&amp;gt; f)
    {
        f(f, model);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是在视图中：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Render(Model, (render, categories) =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;if &lt;/span&gt;(categories.Count &amp;gt; 0) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;categories) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        
                &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
                    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Encode(cat.Name) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; render(render, cat.Children); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;                &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;li&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        
            &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;        &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;ul&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;

&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;同样，我们只要记住这么做的“语法”就可以了。&lt;/p&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;相比之下，我喜欢最后两种做法。因为他们直接构造了“HTML生成”的功能，且“内置”了递归。如果使用一个额外的局部视图，虽然“朴素”但使用较为麻烦。使用“伪递归”的方式，从概念上看这不太像是在生成HTML，程序构造的痕迹（先声明，再定义，最后调用）过于明显了。&lt;/p&gt;
&lt;p&gt;您喜欢哪种做法呢？如果您遇到了我这样的需求，您会怎么做呢？&lt;/p&gt;
&lt;p&gt;最后我想进行一个小调查：&lt;font color="#ff0000"&gt;您满意WebForm的页面作为视图模板引擎吗？您平时最喜欢使用什么视图模板引擎，为什么呢？&lt;/font&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/rendering-tree-like-structure-recursively.html#comments</comments>
      <pubDate>Sun, 27 Sep 2009 05:45:00 GMT</pubDate>
      <lastBuildDate>Sun, 27 Sep 2009 05:45:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>在ASP.NET MVC中使用IIS级别的URL Rewrite</title>
      <link>http://blog.zhaojie.me/2009/09/aspnet-mvc-iis-level-url-rewrite.html</link>
      <guid>http://blog.zhaojie.me/2009/09/aspnet-mvc-iis-level-url-rewrite.html</guid>
      <description>&lt;p&gt;大约一年半前，我在博客上写过一系列关于&lt;a href="http://blog.zhaojie.me/2008/01/url-rewrite-1.html"&gt;URL Rewrite的文章&lt;/a&gt;（&lt;a href="http://blog.zhaojie.me/2008/01/url-rewrite-2.html"&gt;2&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2008/01/url-rewrite-3.html"&gt;3&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2008/01/url-rewrite-4.html"&gt;4&lt;/a&gt;），把ASP.NET平台上进行URL Rewrit的方式和各自地特点进行了较为详细的描述。应该来说，已经讲的非常具体，可以应对90%的情况。其实IIS Rewrite的原理非常容易理解，进行一些简单的变化和推断之后，便可以得出一些问题的原因和解决方案。现在我们就来看一个真实案例：在ASP.NET MVC中使用IIS级别的URL Rewrite。&lt;/p&gt; &lt;p&gt;在当时的文章中我谈到，&lt;a href="http://blog.zhaojie.me/2008/01/url-rewrite-2.html"&gt;URL Rewrite分有IIS级别和ASP.NET两种级别&lt;/a&gt;，并且&lt;a href="http://blog.zhaojie.me/2008/01/url-rewrite-4.html"&gt;各有各的特点和限制&lt;/a&gt;。在ASP.NET MVC中我们常用的方式是ASP.NET级别的URL Routing，它的作用是从URL中捕获数据并交给程序使用（当然还有“构造”的功能，稍候再谈）。因此，在ASP.NET MVC中我们往往不需要使用ASP.NET级别的URL Rewrite。而如今使用IIS级别的URL Rewrite，也正是因为有某些特殊问题无法回避才“不得已而为之”的。&lt;/p&gt; &lt;p&gt;以下涉及到的URL都以http://51programming.com为例，这个域名已经被我泛解析为127.0.0.1，如果您需要的话可以用它来做实验。&lt;/p&gt; &lt;p&gt;在许多年前，一个URL的Path就是普通的路径，而动态的参数，如查询路径，是通过Query String提供的，例如：&lt;/p&gt;&lt;pre class="code"&gt;http://51programming.com/products?keywords=helloworld&lt;/pre&gt;
&lt;p&gt;为了避免混淆，在这里我们先来澄清一些概念。什么是URL，什么是Path，而什么是QueryString。例如在上面的地址，这三者分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL：http://51programming.com/products?keywords=helloworld 
&lt;li&gt;Path：http://51programming.com/products 
&lt;li&gt;Query String：keywords=helloworld&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;后来SEO兴起之后，有人说这样的“动态地址”不利于搜索引擎中的权重优化，因此建议把关键字作为Path的一部分。于是就出现了这样的URL：&lt;/p&gt;&lt;pre class="code"&gt;http://51programming.com/products/helloworld&lt;/pre&gt;
&lt;p&gt;这么看来问题并不大，但是您要知道，关键字往往是由用户输入的，可能会输入特殊字符。例如，如果用户输入了“200%”作为关键字，则两种形式下的URL就分别是：&lt;/p&gt;&lt;pre class="code"&gt;http://51programming.com/products?keywords=200%25
http://51programming.com/products/200%25&lt;/pre&gt;
&lt;p&gt;如果您尝试一下便可以知道，第一个URL可以正常访问，而第二个URL便会引发Bad Request异常：&lt;/p&gt;&lt;img src="http://img.zhaojie.me/blog/UrlRewrite/5.jpg"&gt; 
&lt;p&gt;这是因为URL的Path部分出现了特殊字符，而这种字符只能出现在Query String中。&lt;/p&gt;
&lt;p&gt;看到这个画面，您还意识到了什么信息？在定位问题的原因，以及设法解决问题的时候，首先要明确的是到底是哪里出现了问题。例如看到这个画面，您应该清楚地意识到一点：这是ASP.NET抛出的异常，换句话说，IIS并没有把它当作是非法的URL，它还是老老实实地将URL交给ASP.NET ISAPI处理。因此，我们便可以动用IIS级别的URL Rewrite，在进入ASP.NET执行引擎之前，就把URL替换成可接受的形式：&lt;/p&gt;&lt;pre class="code"&gt;RewriteRule  ^/products/([^\?]*)\?(.+)    /products?$2&amp;amp;keywords=$1     [I,L,U]
RewriteRule  ^/products/([^\?]*)          /products?keywords=$1     [I,L,U]&lt;/pre&gt;
&lt;p&gt;第一行应对的是带有Query String的情况，而第二行则是没有Query String的情况。这里用到的组件是&lt;a href="http://www.codeplex.com/IIRF"&gt;IIRF（Ionic's Isapi Rewrite Filter）&lt;/a&gt;，这是一款开源产品，一年半前的文章里我推荐的也是这个，现在它已经有了升级。它的功能便是在进入ASP.NET ISAPI之前，就将URL重写为其他形式：&lt;/p&gt;&lt;img src="http://img.zhaojie.me/blog/UrlRewrite/1.jpg"&gt; 
&lt;p&gt;原本在第3步会出现的Bad Request，由于已经在第2步被URL Rewrite成合法的形式。因此剩余的处理也就没有任何问题了。&lt;/p&gt;
&lt;p&gt;这些内容在一年半前的文章内已经提过，不过现在既然有了ASP.NET MVC，则事情又变得更为复杂。因为ASP.NET Routing除了“匹配”URL的功能之外，还担负着“组装”URL的职责。因此，让ASP.NET Routing能够识别出Rewrite后的URL不难，但是如何同时让它又可以“组装”出Rewrite前的URL，这就需要一些小技巧了。例如以下的Route配置只能识别出URL输入（/products?keywords=xxx）但不能组装出我们需要的URL（/products/xxx）：&lt;/p&gt;&lt;pre class="code"&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;"Product.List"&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;"products"&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Product"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List"&lt;/span&gt;&lt;span style="color: #a31515"&gt; &lt;/span&gt;});
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;因此，我们必须这么做：&lt;/p&gt;&lt;pre class="code"&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;"Product.List"&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;"products/{*keywords}"&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Product"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List"&lt;/span&gt;, &lt;span style="color: red"&gt;keywords&lt;/span&gt; = &lt;span style="color: #a31515"&gt;"" &lt;/span&gt;});
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;请注意我们让keywords匹配Path后端全部内容，而由于我们又提供了keywords的默认值，因此即使是“/products”这样的Path输入，也能正确匹配到这条Route规则——只不过此时的Route Value中的keywords字段已经不是用户输入的内容了（因为用户输入的/products/xxx，已经被重写为/products?keywords=xxx）。换句话说，如果有如下的Action，那么它的keywords参数则永远是空字符串：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;List(&lt;span style="color: blue"&gt;string&lt;/span&gt; keywords) { ... }&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;幸好，ASP.NET MVC中存在Model Binder机制，我们可以编写一个Model Binder来指定这个参数的获取位置：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FromQueryBinder &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IModelBinder
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public object &lt;/span&gt;BindModel(&lt;span style="color: #2b91af"&gt;ControllerContext &lt;/span&gt;controllerContext, &lt;span style="color: #2b91af"&gt;ModelBindingContext &lt;/span&gt;bindingContext)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;controllerContext.HttpContext.Request.QueryString[bindingContext.ModelName];
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;再将其运用到List的keywords参数上去：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;List(
    [&lt;span style="color: #2b91af"&gt;ModelBinder&lt;/span&gt;(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;FromQueryBinder&lt;/span&gt;))]&lt;span style="color: blue"&gt;string &lt;/span&gt;keywords)
&lt;/pre&gt;
&lt;p&gt;由于参数名是keywords，因此bindingContext.ModelName也是keywords，于是从Query String中便可以取到我们需要的内容了。至于在进行URL生成的时候，我们还是可以之间一样添加一个keywords字段到Route Value中去，于是在我们先前配置的Route规则中便会组装成合适的Path了（即/products/xxx）。&lt;/p&gt;
&lt;p&gt;在这个例子中，我们让keywords匹配Path后端全部内容，但是如果是Path中间某一段需要有特殊字符怎么办呢？其实也一样，只是在进行URL Rewrite的时候，需要在最终重写的时候填写一个“假”的值就可以了，如这样的Route规则：&lt;/p&gt;&lt;pre class="code"&gt;routes.MapRoute(
    &lt;span style="color: #a31515"&gt;"Product.List"&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;"products/{keywords}/page"&lt;/span&gt;,
    &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Product"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List"&lt;/span&gt;&lt;span style="color: #a31515"&gt; &lt;/span&gt;});
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;而IIS级别的URL Rewrite重写的规则就可以是：&lt;/p&gt;&lt;pre class="code"&gt;RewriteRule  ^/products/([^/]*)/(.*)     /products/useless-segement/$2?keywords=$1     [I,L,U]&lt;/pre&gt;
&lt;p&gt;这样，如果用户输入/products/xxx/2就会被重写成/products/useless-token/2?keywords=xxx——事实上，在第一个示例中我们也可以这么做，只是我“不习惯”增加一个伪造的值而已。&lt;/p&gt;
&lt;p&gt;以上解决方案可以在IIS 6与IIS 7的Classic Mode中正常使用，只可惜在IIS 7的Intergrated Mode中，可能是由于ASP.NET接管了IIS的部分逻辑，因此会很早抛出“IIS级别”，而不是“ASP.NET级别”的Bad Request异常。如果您遇到了这种方式，就必须通过以下三个步骤来摆脱这个麻烦的问题了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置AllowRestrictedChars：&lt;a href="http://support.microsoft.com/kb/820129"&gt;KB820129&lt;/a&gt;（让IIS 7接受特殊字符）&lt;/li&gt;
&lt;li&gt;设置VerificationCompatibility：&lt;a href="http://support.microsoft.com/default.aspx?scid=kb;EN-US;826437"&gt;KB826437&lt;/a&gt;中除了“安装.NET 1.1 SP1”以外的步骤（让ASP.NET接受特殊字符）&lt;/li&gt;
&lt;li&gt;将ASP.NET页面的ValidateRequest设为False&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实您只要经过了这三步修改，对于目前这个案例，即使不用IIS级别的URL Rewrite应该也没有问题了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/aspnet-mvc-iis-level-url-rewrite.html#comments</comments>
      <pubDate>Wed, 23 Sep 2009 07:03:00 GMT</pubDate>
      <lastBuildDate>Wed, 23 Sep 2009 07:03:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>片段缓存的实际应用、延迟加载及Eazy类库</title>
      <link>http://blog.zhaojie.me/2009/09/how-to-use-fragment-cache-lazy-load-and-library-eazy.html</link>
      <guid>http://blog.zhaojie.me/2009/09/how-to-use-fragment-cache-lazy-load-and-library-eazy.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html"&gt;片段缓存&lt;/a&gt;（&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-2-more-friendly-api.html"&gt;二&lt;/a&gt;，&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-3-rendering-principle.html"&gt;三&lt;/a&gt;）已经实现完整了，但好像还没有提到如何在项目中进行实际应用，那么现在就来谈一谈这方面。之前也有朋友提出，这个片段缓存到底省下的是什么啊？好像数据都是在Controller中获取的，视图的生成不会带来多少开销啊，难道节省的只是拼接HTML字符串的时间吗？这其实就涉及到片段缓存在实际项目中该如何使用的问题了。&lt;/p&gt; &lt;p&gt;上周日的幻灯片中我提到Ruby on Rails中的Fragment Caching一般是这样使用的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #a31515"&gt;class&lt;/span&gt; BlogController &amp;lt; ApplicationController
  &lt;span style="color: #a31515"&gt;def&lt;/span&gt; list
    &lt;span style="color: #a31515"&gt;unless&lt;/span&gt; read_fragment(:action =&amp;gt; 'list' )
      &amp;#64;articles = Article.find_recent
    &lt;span style="color: #a31515"&gt;end&lt;/span&gt;
  &lt;span style="color: #a31515"&gt;end&lt;/span&gt;
&lt;span style="color: #a31515"&gt;end&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;以及它的视图模板：&lt;/p&gt;&lt;pre class="code"&gt;&amp;lt;% cache do %&amp;gt;   &lt;span style="color: green"&gt;&amp;lt;!- Here's the content we cache -&amp;gt;&lt;/span&gt;
  &lt;span style="color: #a31515"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &amp;lt;% for article in &amp;#64;articles -%&amp;gt;
      &lt;span style="color: #a31515"&gt;&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;&amp;lt;%= h(article.body) %&amp;gt;&lt;span style="color: #a31515"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &amp;lt;% end -%&amp;gt;
  &lt;span style="color: #a31515"&gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&amp;lt;% end %&amp;gt;&lt;/pre&gt;
&lt;p&gt;以上是一个Controller和一个名为list的Action方法，再list方法中回去检查片段缓存是否存在，如果不存在才去加载&amp;#64;articles字段。视图模板中的逻辑也类似，如果缓存不存在才去访问&amp;#64;article字段。这样，读取&amp;#64;acticle的开销便节省下来了。在我之前公布的ASP.NET MVC片段缓存实现中，还无法在Controller中操作缓存，近期打算加上这方面的功能。&lt;/p&gt;
&lt;p&gt;可是这样的做法可能会产生一个问题：在并发情况高的环境下，可能视图会访问到没有初始化的&amp;#64;articles字段。因为在Action方法中缓存还没有过期，但是就在正式生成视图内容，缓存过期了——于是就引起了错误。不过，其实我在想到“片段缓存”时，第一反应并不是这种做法，而是使用“延迟加载”。&lt;/p&gt;
&lt;p&gt;使用延迟加载，也就是说在Action方法中虽然不会加载某个字段，但是还是会给这个字段“打一个桩（stub）”。如果视图中不妨问这个字段自然无妨，但在需要访问的话，也可以正常获取到数据。于是这种做法既可以省下开销，又不会出现问题。&lt;/p&gt;
&lt;p&gt;不过使用延迟加载并非完全没有代价，它要求资源回收的时机不能太早。据我所知，有些朋友会使用Action Filter，在OnActionExecuted时回收所有资源（如数据库连接），这样在视图中自然无法获取数据了。因此，如果您使用延迟加载，请务必在OnResultExecuted时回收资源。&lt;/p&gt;
&lt;p&gt;那么，我们该如何使用延迟加载呢？最容易的做法自然是&lt;a href="http://blog.zhaojie.me/2009/09/simple-over-complex.html"&gt;稍微修改一下你的Model&lt;/a&gt;，保持接口不变即可。不过现在您也可以关注一下&lt;a href="http://Eazy.codeplex.com"&gt;Eazy&lt;/a&gt;类库。&lt;/p&gt;
&lt;p&gt;Eazy类库是我前一段时间设想中的“延迟辅助类库”，名字来源于Easy + Lazy，目前托管在CodePlex中。有了这个类库的帮助，您就无需对自己的类库“小动干戈”了。于是，您就可以在Action方法使用这样的代码来设置字段的延迟功能：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;List()
{ 
    &lt;span style="color: blue"&gt;var &lt;/span&gt;model = &lt;span style="color: #2b91af"&gt;LazyBuilder&lt;/span&gt;.Create&amp;lt;&lt;span style="color: #2b91af"&gt;Model&lt;/span&gt;&amp;gt;()
        .Setup(m =&amp;gt; m.Articles, () =&amp;gt; &lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;.FindRecent())
        .Instance;

    &lt;span style="color: blue"&gt;return &lt;/span&gt;View(model);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;LazyBuilder.Create方法会创建Model类型的实例，然后使用Setup方法可以为某个属性指定一个委托，而这个委托便会在第一次访问这个属性时执行。LazyBuilder的实现机制非常清晰，只是使用Emit在运行时动态创建目标类型的子类而已。例如Model类型：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Model
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public virtual &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;&amp;gt; Articles { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;便会为它生成如下的子类：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Model$LazyProxy &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Model
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;&amp;gt; Articles
    {
        &lt;span style="color: blue"&gt;get
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Articles$LazyLoader != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
            {
                &lt;span style="color: blue"&gt;base&lt;/span&gt;.Articles = &lt;span style="color: blue"&gt;this&lt;/span&gt;.Articles$LazyLoader();
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.Articles$LazyLoader = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
            }

            &lt;span style="color: blue"&gt;return base&lt;/span&gt;.Articles;
        }
        &lt;span style="color: blue"&gt;set
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;base&lt;/span&gt;.Articles = &lt;span style="color: blue"&gt;value&lt;/span&gt;;
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Articles$LazyLoader = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
        }
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;&amp;gt;&amp;gt; Articles$LazyLoader = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这段写法是我认为&lt;a href="http://blog.zhaojie.me/2009/09/standard-lazy-proxy.html"&gt;实现延迟加载最理想的方式&lt;/a&gt;了，最重要的是它保留了属性原有的逻辑，只是在加载时机上做了文章。此外，在不需要延迟加载的情况下，属性的行为也不会改变。&lt;/p&gt;
&lt;p&gt;不过Eazy项目其实才创建了2天，目前只能通过指定泛型类型来构造对象，这意味着这个类型必须有默认的构造函数。根据我的设想，它以后还会支持其他的构造方式，例如：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;builder = &lt;span style="color: #2b91af"&gt;LazyBuilder&lt;/span&gt;.Create(() =&amp;gt; &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Model&lt;/span&gt;(1, 2) { Time = &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now });
builder.Setup(m =&amp;gt; m.Articles, () =&amp;gt; &lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;.FindRecent());
&lt;span style="color: blue"&gt;var &lt;/span&gt;model = builder.Instance;
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;由于完全使用Emit，因此不会带来反射的开销，性能是很有保障的。例如LazyBuilder.Create&amp;lt;Model&amp;gt;的消耗和new Model()相比也就是多了些方法调用而已。目前Eazy项目还比较简陋，例如代码中不会抛出恰当的异常，单元测试也不够完整。此外，我还在考虑是否要对只读属性或接口提供延迟加载的支持。&lt;/p&gt;
&lt;p&gt;当然，如果您有什么想法也请告诉我，这里先谢过了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/how-to-use-fragment-cache-lazy-load-and-library-eazy.html#comments</comments>
      <pubDate>Tue, 22 Sep 2009 06:54:00 GMT</pubDate>
      <lastBuildDate>Tue, 22 Sep 2009 06:54:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>适合ASP.NET MVC的视图片断缓存方式（下）：页面输出原则</title>
      <link>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-3-rendering-principle.html</link>
      <guid>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-3-rendering-principle.html</guid>
      <description>&lt;p&gt;上一篇文章里已经把Html.Cache打造成了非常具有可用性的API，需要缓存时我们只需在页面上做一个标记即可：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Cache(&lt;span style="color: #a31515"&gt;"cache_key"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now.AddSeconds(10), () =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;
 
&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;article &lt;span style="color: blue"&gt;in &lt;/span&gt;Model.Articles) { &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt; 
        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;p&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;article.Body &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;p&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;标记内部的写法和普通视图的写法相同，您可以for/foreach/if，也可以&amp;lt;%= %&amp;gt;，或者使用RenderPartial等其他辅助方法输出内容，都会被一并缓存下来。只可惜，上次文章末尾我提到有些效果是有前提的。&lt;/p&gt;
&lt;p&gt;这个前提就是：某些RenderPartial和其他一些辅助方法的实现需要进行修改。好吧，再说的直接一些：如果您使用标准的ASP.NET MVC，就无法使用RenderPartial的功能。我认为造成这种问题的原因是ASP.NET MVC框架在实现时没有遵守页面内容输出的准则。所以我建议您使用&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;项目进行ASP.NET MVC开发。&lt;/p&gt;
&lt;p&gt;不过现在，我们还是来讨论一下准则吧。下面有些内容涉及到ASP.NET WebForm页面的输出方式，如果您遇到了不理解的地方，可以去看一下&lt;a href="http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html"&gt;这篇文章&lt;/a&gt;，它是我为“页面片段缓存”原理介绍而写的“铺垫”。&lt;/p&gt;
&lt;p&gt;在普通情况下，一个ASP.NET页面输出时是向一个封装了Response.Output的HtmlTextWriter中写入内容的：&lt;/p&gt;&lt;img src="http://img.zhaojie.me/blog/168980/o_mvc-fragment-cache-1.jpg"&gt; 
&lt;p&gt;而我们的片段缓存实现为了“捕获”某个缓存块输出的内容，则在HtmlTextWriter与Response.Output之间又插入了一个RecordWriter：&lt;/p&gt;&lt;img src="http://img.zhaojie.me/blog/168980/o_mvc-fragment-cache-2.jpg"&gt; 
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;那么，在缓存命中的时候，我们的Cache方法把缓存中的内容写到什么地方去了呢？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static void &lt;/span&gt;Cache(
    &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;htmlHelper,
    ...)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;content = ...

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(content == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        ...
    }
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        htmlHelper.&lt;font color="#ff0000"&gt;Output&lt;/font&gt;.Write(content);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;Output是什么？如果您观察ASP.NET MVC的源代码，您会发现HtmlHelper并没有这个属性。这是我在MvcPatch中暴露出来的一个TextWriter，它便是当前正用于页面输出的HtmlTextWriter对象。因此，我这里提出一个原则：&lt;font color="#ff0000"&gt;如果您是在向页面输出内容，请务必将所有内容通过页面的Writer输出&lt;/font&gt;。&lt;/p&gt;
&lt;p&gt;在原来的ASP.NET MVC实现中，由于无法从HtmlHelper中获得页面的Writer，因此如果需要输出内容，则只能通过Response.Write方法，或由Response.Output输出内容了。根据上图可知，如果我们直接从Response.Output输出，那么这部分内容是无法被RecordWriter捕获的。这意味着什么呢？这意味着，如果我们上面不是通过HtmlHelper.Output，而是直接向Response.Output输出，在Html.Cache嵌套的情况下，内层缓存块的输出无法被外层缓存块捕获到。因此，如果内层缓存命中，而外层重新生成内容，则会发现内层缓存块的内容被没有被外层记录下来。&lt;/p&gt;
&lt;p&gt;我们可以想的再远一些。我们这种TextWriter的嵌套其实是一种什么模式呢？应该算是&lt;a href="http://en.wikipedia.org/wiki/Decorator_pattern"&gt;装饰器模式&lt;/a&gt;吧。装饰器模式要求我们所有的输出都从链条的顶部输入，这样所有的“装饰”作用才会生效。如果我们获取了其中的某一个环节，直接从这个环节输入参数，那么自然是失败的。这意味着……假如又有另外一个组件在“行使”它的扩展权力呢？如果又有另一个组件，它在我们的RecordWriter外层又进行了包装呢？我们的片断缓存解决方案是一种扩展，作为扩展方案，不应该破坏其他组件正常扩展的能力。因此，我们需要从页面的Writer中输出内容。&lt;/p&gt;
&lt;p&gt;一个很好的反例就是ASP.NET MVC框架，您看RenderPartial方法的输出目标是什么：Response.Output。还有FormExtensions及MvcForm对象的输出目标是什么：还是Response.Output。这意味着，ASP.NET MVC框架的做法直接破坏了视图的扩展能力。也直接放倒了我们的片断缓存实现。因此，我最终构建了MvcPatch项目，因为在这一点上（以及其他一些方面，之前也有所提及）使用扩展的方式实在是无法进行修补的。&lt;/p&gt;
&lt;p&gt;所以国外社区有种调侃称，微软产品是好的，但是他们自己不知道该如何用好自己的产品。例如我一直说的WebForms的滥用，还有这里ASP.NET MVC实现。前者更像是一种商业策略，而后者可能……就令人摸不着头脑了。&lt;/p&gt;
&lt;p&gt;我没有说“微软的确不知道如何用好自己的产品”。因为从ASP.NET MVC的代码中可以发现，好像他们并非不知道我刚提出的页面输出原则。证据在于，他们已经在ViewPage中留有一个“入口”了：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewPage &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Page&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IViewDataContainer&lt;/span&gt;
    ...

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlTextWriter &lt;/span&gt;Writer
    {
        &lt;span style="color: blue"&gt;get&lt;/span&gt;;
        &lt;span style="color: blue"&gt;private set&lt;/span&gt;;
    }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Render(&lt;span style="color: #2b91af"&gt;HtmlTextWriter &lt;/span&gt;writer)
    {
        Writer = writer;
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;base&lt;/span&gt;.Render(writer);
        }
        &lt;span style="color: blue"&gt;finally
        &lt;/span&gt;{
            Writer = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;看看这段代码在做什么？这段代码重写了Render方法，将外部传入的HtmlTextWriter对象保留了起来！这意味着ViewPage.Writer属性获得的便是当前正在输出的HtmlTextWriter对象！也就是说，ASP.NET MVC似乎在建议您说，如果您非要在页面上使用Response.Output输出的话，现在就改成Writer的输出吧：&lt;/p&gt;&lt;pre class="code"&gt;&lt;strike&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Response.Write(&lt;span style="color: #a31515"&gt;"Hello World"&lt;/span&gt;); &lt;/strike&gt;&lt;span style="background: #ffee62"&gt;&lt;strike&gt;%&amp;gt;&lt;/strike&gt;
&amp;lt;%&lt;/span&gt; Writer.Write(&lt;span style="color: #a31515"&gt;"Hello World"&lt;/span&gt;); &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;不知道是可惜还是可笑，如果您在代码中对Writer属性使用Find All References，您会发现除了在ViewMasterPage或ViewUserControl中继续暴露Writer属性之外，就再也没有使用过了……那么RenderPartial在做什么？FormExtensions在做什么？谁知道……我同样不知道的是，如果微软自己没有这个“意识”，那么又为什么要主动保留Render时的Writer呢？&lt;/p&gt;
&lt;p&gt;不管这些了。我们最后总结一下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果您在使用WebForm模型，请像ViewPage那样保留当前Writer，并且向Writer内输出，不要搞Response.Write/Output。&lt;/li&gt;
&lt;li&gt;如果您在编写视图的辅助方法，请向HtmlHelper.Output输出，而不是Reponse.Write/Output。&lt;/li&gt;
&lt;li&gt;如果您发现其他项目在使用Response.Write/Output，请将它修改成页面的Writer输出。&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;嗯？您说HtmlHelper没有Output属性？没关系，下载代码以后自己修改编译一下，或直接使用&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;吧。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html"&gt;适合ASP.NET MVC的视图片断缓存方式（上）：起步&lt;/a&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-2-more-friendly-api.html"&gt;适合ASP.NET MVC的视图片断缓存方式（中）：更实用的API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;适合ASP.NET MVC的视图片断缓存方式（下）：页面输出原则&lt;/li&gt;&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-3-rendering-principle.html#comments</comments>
      <pubDate>Tue, 22 Sep 2009 03:05:00 GMT</pubDate>
      <lastBuildDate>Tue, 22 Sep 2009 03:05:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>适合ASP.NET MVC的视图片断缓存方式（中）：更实用的API</title>
      <link>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-2-more-friendly-api.html</link>
      <guid>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-2-more-friendly-api.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html"&gt;上一篇文章中&lt;/a&gt;我们提出了了片断缓存的基本方式，也就是构建HtmlHelper的扩展方法Cache，接受一个用于生成字符串的委托对象。在缓存命中时，则直接返回缓存中的字符串片断，否则则使用委托生成的内容。因此，缓存命中时委托的开销便节省了下来。不过这个方法并不实用，如果您要缓存大片的HTML，还需要准备一个Partial View，再用它来生成网页片段：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Cache(..., () =&amp;gt; Html.Partial(&lt;span style="color: #a31515"&gt;"MyPartialViewToCache"&lt;/span&gt;)) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;但是在实际开发过程中，我们最乐于看到的使用方法，应该只是使用某个标记来“围绕”一段现有的代码。也就是说，我们希望的API使用方式可能是这样的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Cache(&lt;span style="color: #a31515"&gt;"cache_key"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now.AddSeconds(10), () =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;article &lt;span style="color: blue"&gt;in &lt;/span&gt;Model.Articles) { &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt; 
        &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;p&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;article.Body &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;p&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;我们可以从这种“表现形式”上推断出这个Cache方法的签名：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static void &lt;/span&gt;Cache(
    &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;htmlHelper,
    &lt;span style="color: blue"&gt;string &lt;/span&gt;cacheKey,
    &lt;span style="color: #2b91af"&gt;CacheDependency &lt;/span&gt;cacheDependencies,
    &lt;span style="color: #2b91af"&gt;DateTime &lt;/span&gt;absoluteExpiration,
    &lt;span style="color: #2b91af"&gt;TimeSpan &lt;/span&gt;slidingExpiration,
    &lt;span style="color: #2b91af"&gt;Action &lt;/span&gt;action)
{
    ...
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;与前一个扩展相比，最后一个委托参数变成了Action，而不是Func&amp;lt;string&amp;gt;。这是因为ASP.NET页面在编译时，会将页面Cache块中的代码，编译为内容的输出方式——这点在&lt;a href="http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html"&gt;之前的文章中&lt;/a&gt;已经有过比较详细的描述。不过有一点还是与之前相同的，我们要省下的是action委托的开销。也就是说，如果缓存命中，则不执行action。缓存没有命中，则执行action，获得action生成的字符串，加入缓存并输出。&lt;/p&gt;
&lt;p&gt;看似比较简单，但这里有个问题：如之前的Func&amp;lt;string&amp;gt;参数，我们执行后自然可以获得一个字符串作为结果。但是现在是个action，执行后它又把内容输出到什么地方去，我们又该如何得到这里生成的字符串呢？根据&lt;a href="http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html"&gt;页面输出行为&lt;/a&gt;，我们可以推断出页面上的内容是被写入一个HtmlTextWriter中的。那么，这个HtmlTextWriter又是如何生成的呢？&lt;/p&gt;
&lt;p&gt;它是根据Page类型的CreateHtmlTextWriter方法生成的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;protected virtual &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlTextWriter &lt;/span&gt;CreateHtmlTextWriter(System.IO.&lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;tw) { ... }&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;在页面准备生成内容之前，Page会调用其CreateHtmlTextWriter来包装一个TextWriter，这个TextWriter一般即是由Response.Output暴露出来的HttpWriter对象。CreateHtmlTextWriter方法生成的HtmlTextWriter，便会交给Page的Render方法用于输出页面内容了。这便是我们的入手点，我们可以趁此机会在HtmlTextWriter和CreateHtmlTextWriter之间“插入”一个组件。这个组件除了将外部传入的数据传入内部的TextWriter以外，还有着“纪录”内容的功能：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RecordWriter &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;TextWriter
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;RecordWriter(&lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;innerWriter)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_innerWriter = innerWriter;
    }

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;m_innerWriter;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;StringBuilder&lt;/span&gt;&amp;gt; m_recorders = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;StringBuilder&lt;/span&gt;&amp;gt;();

    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Encoding &lt;/span&gt;Encoding
    {
        &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_innerWriter.Encoding; }
    }

    &lt;span style="color: blue"&gt;public override void &lt;/span&gt;Write(&lt;span style="color: blue"&gt;char &lt;/span&gt;value) { ... }

    &lt;span style="color: blue"&gt;public override void &lt;/span&gt;Write(&lt;span style="color: blue"&gt;string &lt;/span&gt;value)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(value != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_innerWriter.Write(value);

            &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_recorders.Count &amp;gt; 0)
            {
                &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;recorder &lt;span style="color: blue"&gt;in this&lt;/span&gt;.m_recorders)
                {
                    recorder.Append(value);
                }
            }
        }
    }

    &lt;span style="color: blue"&gt;public override void &lt;/span&gt;Write(&lt;span style="color: blue"&gt;char&lt;/span&gt;[] buffer, &lt;span style="color: blue"&gt;int &lt;/span&gt;index, &lt;span style="color: blue"&gt;int &lt;/span&gt;count) { ... }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;AddRecorder(&lt;span style="color: #2b91af"&gt;StringBuilder &lt;/span&gt;recorder)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_recorders.Add(recorder);
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;RemoveRecorder(&lt;span style="color: #2b91af"&gt;StringBuilder &lt;/span&gt;recorder)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_recorders.Remove(recorder);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;一个TextWriter有数十个可以覆盖的成员，但是一般情况下我们只需&lt;a href="http://blog.zhaojie.me/2009/09/how-to-create-your-own-text-writer.html"&gt;覆盖其中三个Write方法&lt;/a&gt;就可以了。以上代码用Write(string)作为示例，可以看出，如果RecordWriter中添加了Recorder之后，便会将外界写入的内容再交给Recorder一次。换句话说，如果我们希望纪录页面上写入Writer的内容，只要在RecordWriter里添加Recorder就可以了。当然，在此之前我们需要为视图页面“开启”缓存功能：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 定义在CacheExtensions中&lt;/span&gt;
&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;CreateCacheWriter(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;htmlHelper, &lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;writer)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;recordWriter = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RecordWriter&lt;/span&gt;(writer);
    htmlHelper.SetRecordWriter(recordWriter);
    &lt;span style="color: blue"&gt;return &lt;/span&gt;recordWriter;
}

&lt;span style="color: green"&gt;// 定义在视图页面（aspx）中&lt;/span&gt;
&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;script &lt;/span&gt;&lt;span style="color: red"&gt;runat&lt;/span&gt;&lt;span style="color: blue"&gt;="server"&amp;gt;
    protected override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlTextWriter &lt;/span&gt;CreateHtmlTextWriter(System.IO.&lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;tw)
    {
        &lt;span style="color: blue"&gt;return base&lt;/span&gt;.CreateHtmlTextWriter(Html.CreateCacheWriter(tw));
    }
&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;script&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;当然，在实际开发过程中不会在aspx中重写CreateHtmlTextWriter方法，我们往往会将其放在视图页面的共同基类中。例如在我的项目中，我就为所有的视图“开启”了这种纪录功能。由于在没有缓存的情况下这层薄薄的封装只是在做一个“转发”功能，因此不会带来性能问题。&lt;/p&gt;
&lt;p&gt;此时，新的Cache方法便非常直观了：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static void &lt;/span&gt;Cache(
    &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;htmlHelper,
    &lt;span style="color: blue"&gt;string &lt;/span&gt;cacheKey,
    &lt;span style="color: #2b91af"&gt;CacheDependency &lt;/span&gt;cacheDependencies,
    &lt;span style="color: #2b91af"&gt;DateTime &lt;/span&gt;absoluteExpiration,
    &lt;span style="color: #2b91af"&gt;TimeSpan &lt;/span&gt;slidingExpiration,
    &lt;span style="color: #2b91af"&gt;Action &lt;/span&gt;action)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;cache = htmlHelper.ViewContext.HttpContext.Cache;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;content = cache.Get(cacheKey) &lt;span style="color: blue"&gt;as string&lt;/span&gt;;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;writer = htmlHelper.GetRecordWriter();

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(content == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;recorder = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StringBuilder&lt;/span&gt;();
        writer.AddRecorder(recorder);

        action();

        writer.RemoveRecorder(recorder);
        content = recorder.ToString();
        cache.Insert(cacheKey, content, cacheDependencies, absoluteExpiration, slidingExpiration);
    }
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        htmlHelper.Output.Write(content);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如果缓存没有命中，则我们会向RecordWriter中添加一个Recorder，然后再执行action委托，这样action中的所有内容便会被纪录下来。action执行完毕后，我们再摘除Recorder即可。现在Cache方法已经可用了，例如：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;

&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Cache(&lt;span style="color: #a31515"&gt;"now"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now.AddSeconds(5), () =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;
&lt;/span&gt;    
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;那么，Html.Cache能否嵌套呢？答案也是肯定的。&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;

&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Cache(&lt;span style="color: #a31515"&gt;"now"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now.AddSeconds(&lt;font color="#ff0000"&gt;5&lt;/font&gt;), () =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;
    
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.Cache(&lt;span style="color: #a31515"&gt;"inner_now"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now.AddSeconds(&lt;font color="#ff0000"&gt;10&lt;/font&gt;), () =&amp;gt; { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    
        &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.RenderPartial(&lt;span style="color: #a31515"&gt;"CurrentTime"&lt;/span&gt;); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    
    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; }); &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;外层缓存块5秒后过期，内存缓存块10秒钟过期，因此在某一时刻（如第一次刷新后7秒后），您会发现页面上会出现这样的结果：&lt;/p&gt;&lt;pre class="code"&gt;2009/9/21 15:36:10 
2009/9/21 15:36:08 
2009/9/21 15:36:03&lt;/pre&gt;
&lt;p&gt;我们的RecordWriter支持同时拥有多个recorder，您可以根据上面得出的结果来理解内外层循环是以何种顺序向RecordWriter添加Recorder的，这并不困难。&lt;/p&gt;
&lt;p&gt;从代码中我们也可以发现，Cache块内部也可以直接使用Html.RenderPartial。您也可以在Cache块内部使用各种辅助方法，它们的结果会被一并缓存下来。&lt;/p&gt;
&lt;p&gt;不过它们还是有“前提”的，至于这个前提是什么，我们下次在讨论吧。如果您想先睹为快，可以关注&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;项目。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html"&gt;适合ASP.NET MVC的视图片断缓存方式（上）：起步&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;适合ASP.NET MVC的视图片断缓存方式（中）：更实用的API&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-3-rendering-principle.html"&gt;适合ASP.NET MVC的视图片断缓存方式（下）：页面输出原则&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-2-more-friendly-api.html#comments</comments>
      <pubDate>Mon, 21 Sep 2009 07:49:00 GMT</pubDate>
      <lastBuildDate>Mon, 21 Sep 2009 07:49:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>幻灯片：Web开发中的缓存</title>
      <link>http://blog.zhaojie.me/2009/09/slides-web-cache.html</link>
      <guid>http://blog.zhaojie.me/2009/09/slides-web-cache.html</guid>
      <description>&lt;p&gt;这是我昨天在&lt;a href="http://www.broadview.com.cn/open%20party/6/openparty-6.htm"&gt;博文视点Open Party上海站&lt;/a&gt;上关于Web开发中缓存的简单讲座。原本博文视点的朋友们希望我讲一下ASP.NET MVC方面的话题（估计看我最近一直在搞这个），但是我觉得其他平台一直用的是MVC框架，而ASP.NET MVC作为“后来者”也没有什么出彩的地方，所以最终选择这个稍微“通用”些的话题。&lt;/p&gt; &lt;div style="text-align: left; width: 425px" id="__ss_2026379"&gt;&lt;a style="margin: 12px 0px 3px; display: block; font: 14px helvetica,arial,sans-serif; text-decoration: underline" title="Web开发中的缓存" href="http://www.slideshare.net/jeffz/web-2026379"&gt;Web开发中的缓存&lt;/a&gt;&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=web-090920104457-phpapp02&amp;amp;stripped_title=web-2026379" /&gt;&lt;param name="allowFullScreen" value="true" /&gt;&lt;param name="allowScriptAccess" value="always" /&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=web-090920104457-phpapp02&amp;amp;stripped_title=web-2026379" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt; &lt;div style="font-family: tahoma,arial; height: 26px; font-size: 11px; padding-top: 2px"&gt;View more &lt;a style="text-decoration: underline" href="http://www.slideshare.net/"&gt;documents&lt;/a&gt; from &lt;a style="text-decoration: underline" href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt; &lt;p&gt;缓存是系统性能的关键，可以说每一个高性能的系统都在这方面下过苦功。不过似乎在这一块的总结还不太多，大部分的“讨论”都是在讲，例如，缓存多么重要，Memcached多么重要，但几乎没有人讲过在某个常见场景下的解决方案“案例”，更加没有提出过如“缓存模式”这样的东西。如&lt;a href="http://www.zhuangbiaowei.cn/"&gt;庄表伟&lt;/a&gt;同学&lt;a href="http://www.zhuangbiaowei.cn/?p=181"&gt;所说那样&lt;/a&gt;：“经典的GoF的设计模式，其实只解决了（甚至只能说部分解决了）可重用性的需求。”。因此可以这么说，我们在经验的总结上还缺了很大一块。&lt;/p&gt; &lt;p&gt;不如，现在就留下你的经验吧——或者只是你的疑惑，甚至只是一个你思考过但没有解决的问题。可能别人看了就能想到：“哎，这当初我不也遇到过吗？当时我是如此如此，这般这般……”&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/slides-web-cache.html#comments</comments>
      <pubDate>Sun, 20 Sep 2009 16:32:00 GMT</pubDate>
      <lastBuildDate>Sun, 20 Sep 2009 16:32:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/discussion/">思考讨论</category>
      <title>从ASP.NET的PHP执行速度比较谈起</title>
      <link>http://blog.zhaojie.me/2009/09/aspnet-php-benchmark-and-more.html</link>
      <guid>http://blog.zhaojie.me/2009/09/aspnet-php-benchmark-and-more.html</guid>
      <description>&lt;p&gt;上星期我在InfoQ发表了&lt;a href="http://www.infoq.com/cn/news/2009/09/aspnet-php-benchmark"&gt;一篇新闻&lt;/a&gt;，对&lt;a href="http://misfitgeek.com/"&gt;Joe Stagner&lt;/a&gt;在博客上发表的三篇关于ASP.NET与PHP性能对比的文章进行了总结。写新闻其实挺不爽的，因为不能夹杂个人的看法，只能平铺直叙陈述事实。当然，如果像&lt;a href="http://news.cnblogs.com/n/49128/"&gt;某些新闻&lt;/a&gt;那样“换一种说法”是可以骗过一些“不明真相的群众”，但是这就有违道德了。因此，在客观陈述完新闻内容之后，我只能选择把自己的感想、评论等内容放在自己的博客上。&lt;/p&gt; &lt;p&gt;Joe Stagner的背景挺特殊，它是PHP的老用户，在ASP.NET出现之前就是PHP的重量级开发人员了。后来不知哪一天开始他加入了微软，我们就可以在一些如介绍ASP.NET AJAX的文章、视频中看到他。这次他又涉及了一个敏感话题：性能比较。要知道每次这种比较都会惹来一阵争论……我不想用“口水战”来形容，我认为它和“争论”的性质不同。Joe也承认，每次他说PHP好话就会被微软的同事指责，而说.NET好话就要被PHP阵营说是微软的托。&lt;/p&gt; &lt;p&gt;我深信优秀的技术人员都是有信仰的，都有技术倾向性。因此如Joe夹在中间的人的确比较尴尬。但是我认为，有信仰，和“客观”是不冲突的。信仰涉及到倾向性，而客观则意味着有倾向性之后的办事方式。&lt;/p&gt; &lt;p&gt;至于比较结果，您可以关注一下&lt;a href="http://www.infoq.com/cn/news/2009/09/aspnet-php-benchmark"&gt;新闻内容&lt;/a&gt;，总体来说，ASP.NET从纯粹的执行效率上来说是大幅领先于PHP的。这并不令人惊讶，一个是编译为机器码的执行方式，一个是解析执行（即时缓存了op-code也并不是机器码），性能自然天差地远。&lt;/p&gt; &lt;p&gt;Joe也知道会引发争论，因此他在文章后面写道：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;我知道某些人会被这个测试结果激怒，欢迎发表评论及反对意见，但是如果你无法保持礼貌的话，我会删除你的评论并阻止你的IP。&lt;/p&gt; &lt;p&gt;如果你不喜欢，并拒绝接受这个结果——那么你也来测试一下。用数据支持你的观点，使用我的代码或你自己的，然后围绕事实再来争论&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;自然，Joe也给出了测试代码及测试环境的描述。&lt;/p&gt; &lt;p&gt;文章的&lt;a href="http://misfitgeek.com/blog/aspnet/php-versus-asp-net-ndash-windows-versus-linux-ndash-who-rsquo-s-the-fastest/#comments"&gt;评论&lt;/a&gt;自然是很有看头的，说法很多，有许多人说VS的IDE好，有人说PHP永远只能写出半专业的程序。Joe一一&lt;a href="http://misfitgeek.com/blog/aspnet/comments-on-my-recent-benchmarks/"&gt;进行了回应&lt;/a&gt;，我个人认为回应的还是很客观的，紧紧围绕在测试的中心。他表示，虽然VS非常优秀，但是PHP也有很好的IDE。而能否写出专业的程序是看人，而不是由PHP决定的，有丑陋的PHP程序，也能写出如C++一般工整美观的代码。&lt;/p&gt; &lt;p&gt;从表面上看，ASP.NET在性能上胜出PHP一大截，作为ASP.NET的忠实用户我应该非常乐意接受这个“结果”。但是，我并不关注这个，因为这种运行时上纯粹的速度对于一个Web应用程序来说实在微不足道。例如Joe的&lt;a href="http://misfitgeek.com/blog/aspnet/php-linux-windows-asp-net-performance-ndash-redux/"&gt;第三篇文章&lt;/a&gt;里列举出的PHP高手的应对方式：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;ASP.NET在性能上的领先不会对我有什么影响。PHP是我的最爱，我的应用程序已经足够快了。  &lt;li&gt;没错，ASP.NET在基础性能上是比较快，但是我的应用程序可以通过优秀的页面实现和JavaScript实践把这部分性能补回来。  &lt;li&gt;我在进行Drupal开发，我对PHP最熟悉，因此我宁愿多花一些硬件来保持更好的开发效率。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;以及InfoQ上的一条评论：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;在WEB上便捷的开发，与各种系统之间灵活的搭配，像胶水一样将各种不同的物件拼装起来呈现给Web,(python在这方面也做得非常棒)。这才是PHP能有今天的本质原因。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;之前我批评Java是一种&lt;a href="http://blog.zhaojie.me/2009/04/why-i-do-not-like-java.html"&gt;不思进取的低生产力语言&lt;/a&gt;，回应很多。其中也有类似的说法，如说Java&lt;font color="#ff0000"&gt;平台&lt;/font&gt;上开源多，项目多等等。没错，这我在对Java&lt;font color="#ff0000"&gt;语言&lt;/font&gt;发起非难之前就已经反复强调了。我同意这个观点——但是，如果是这样的“结论”肯定是站不住脚的：“因为Java&lt;font color="#ff0000"&gt;平台&lt;/font&gt;开源多，项目多，因此Java&lt;font color="#ff0000"&gt;语言&lt;/font&gt;并不是不思进取的语言”。这就好比有人说“他熟悉PHP，PHP的项目多，因此PHP的性能比ASP.NET快”一样，把两种东西混淆起来了。&lt;/p&gt;&lt;p&gt;奇怪的是，Joe没有让人不要用PHP，我也没有让人不要用Java平台（不过我现在一直建议别人用Scala代替Java语言）。&lt;/p&gt; &lt;p&gt;许多国内技术人员总有这样的“毛病”，见不得自己使用东西有一点点“瑕疵”。既然我是Java平台开发人员，我使用Java语言，我就见不得别人说Java语言一丁点不好。其实做技术就是做权衡，“没有银弹”就意味着没有技术是完美无缺的，我们选择技术是在优势和劣势之间进行平衡后的结果。只有承认了缺点，认识到缺点，才能吸取其他技术的长处，来作出更好的权衡。&lt;/p&gt; &lt;p&gt;因此我一直认可的是，SQL Server的确贵，数据存储就用*nix平台上的吧。选择多，性能多。&lt;/p&gt; &lt;p&gt;当然，这不是国内技术人员的毛病，这是一个广泛的问题。很巧，李笑来老师昨天写了&lt;a href="http://www.lixiaolai.com/index.php/archives/7461.html"&gt;一篇博文&lt;/a&gt;，似乎刚好谈论了这方面的问题。文章很短，就全文摘录了：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;无论是谁，一生中总是在不停地“选择”（姑且不论所谓选择之中有多少是真实的有多少是幻象而已）。每个人都在尽量在众多选项中选择“最好”的那个。一生有2&lt;sup&gt;n&lt;/sup&gt;选择，可&lt;a href="http://www.lixiaolai.com/index.php/archives/131.html"&gt;最终只有一条路属于自己&lt;/a&gt;。  &lt;p&gt;走在自己正在走的那条路上，人们对其它可能性可以抱有两种态度：“好奇”——通常因对现状不满而表现为“后悔”；或者“自负”——更多人最终选择的是这个，因为另外一个选择所表现出来的“后悔”通常被认为是负面情绪，而与之相反的“无怨无悔”好像更加理直气壮更加毅然决然（尽管并不总是正确）。  &lt;p&gt;时间长了，人们就不知不觉把“我一直在尽量选最好的”和“我选的就是好的”等同起来，进而外演为“与我选的不一样的选择就是不好的”，再进一步演化为“既然选的是‘不好’的，那他要么是笨，要么是‘坏’，反正跟我不一样！”  &lt;p&gt;根源就在这里了。以自我为中心也好，过度自恋也好，沙文主义、大男子主义、狭隘民族主义也罢，都大抵上如此。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;还有一个问题，之前也多次谈过，国内技术人员太容易轻视一个问题。例如在这篇新闻中不止一个用户觉得这个测试很无聊。为什么“无聊”？可能是觉得“这点性能不是关键”因此这个实验没有意义吧。我想说，其实最无聊的不是做实验的人，而是纠结与实验结果的人。或者说，无聊的不是实验，而是纠结于实验结果。与此相比，我反而强烈怀疑，轻易认为这个实验无聊的人，倒是真正只关注与“谁快谁慢”，而不关注过程和结果所表达出的内容。不去推测，不去思考。那么以后到了需要决策的时候，决策所需的依据从哪里来呢？应该都已经被“无聊”走了吧。&lt;/p&gt; &lt;p&gt;Joe的这几篇文章，以及我总结的时候，都刻意的详细列出了测试的过程和“运行性能”以外的结果。除了保持公正，客观之外，因为它们也是重要的数据。例如，我现在知道了在Windows上访问MySQL的驱动程序实现很差，而不同平台上访问PostgreSQL性能则相差无几。我知道，在Windows上进行大文件复制，受ACL影响性能较差。而如果关注Windows上运行PHP情况的朋友们则可以获得更多信息。&lt;/p&gt; &lt;p&gt;我不知道是不是国内技术人员的普遍水平较高，总是容易感觉国外的一些讨论无聊。例如有人&lt;a href="http://www.infoq.com/cn/news/2009/06/java-without-primitives"&gt;讨论Java的原生类型&lt;/a&gt;时有人回复“是不是经济危机老美太闲了”。而上次有人和别人讨论ppt的缩写是怎么来的，就给软件最初的编写者写了一封英文信求证，老外非常详细解答了这个问题，还纠正了简称和缩写的差异。作者把信贴出来写了篇blog，原站上评论都是赞同。转帖评论齐刷刷都是骂该作者闲的蛋疼。&lt;/p&gt; &lt;p&gt;我不知道这算是学术态度的问题，还是只能说“对工作没有热情，对生活没有好奇”？在《原生类型》那片文章里，有朋友回复的好：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;西方人这种看似无聊的争论才让他们达到了今天的高度，争论是不是一切量都可以用有理数表示，争论什么是运动，争论地球是宇宙的中心还是太阳是宇宙的中心，争论撒旦是如何诞生的，争论行星是如何运行。而我们很只注重实用，所以无法达到西方在理论上的高度，永远只能等别人争论完了，出结果了。我们拿来用用，仅此而已。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;不过，嫌外国人无聊的人，往往也是嫌国内没有技术含量的人。是不是很奇怪？&lt;/p&gt; &lt;p&gt;同样，Erlang之父Joe Armstrong写&lt;a href="http://www.sics.se/~joe/bluetail/vol1/v1_oo.html"&gt;Why OO Sucks&lt;/a&gt;；&lt;a href="http://www.codemonkeyism.com/"&gt;Stephan Schmidt&lt;/a&gt;写Clojure vs Scala（&lt;a href="http://codemonkeyism.com/scala-vs-clojure/"&gt;上&lt;/a&gt;，&lt;a href="http://codemonkeyism.com/clojure-scala-part-2/"&gt;下&lt;/a&gt;），Java平台语言Groovy创始人James Strachan认为&lt;a href="http://macstrac.blogspot.com/2009/04/scala-as-long-term-replacement-for.html"&gt;Scala是Java未来的替代品&lt;/a&gt;，他和JRuby的核心维护者James Gosling、Charles Nutter对Scala vs. Java的话题&lt;a href="http://www.infoq.com/cn/news/2009/07/scala-replace-java"&gt;讨论的不亦乐乎&lt;/a&gt;。这些都是在批评一个事物，或是在进行“语言比较”这一“无聊”的话题。那么他们是不是也都闲得慌了？&lt;/p&gt; &lt;p&gt;我在想，如果把他们的文章翻译过来，匿个名，或者让&lt;a href="http://www.cnblogs.com/jirigala/"&gt;吉日嘎拉&lt;/a&gt;这样的“众矢之的”来发表，会不会被人指责不懂OO，不懂Java？您别说，我还真见过这样的事情，谁让国内翻译转载常常不留出处呢？&lt;/p&gt; &lt;p&gt;其实这又是个逻辑问题了，这近似于《&lt;a href="http://www.yeeyan.com/articles/view/65452/28581"&gt;常见逻辑谬误&lt;/a&gt;》一文提到“人身攻击及‘你也一样’”，也就是指并不关心问题本身，而是把论据转移到“对方”身上。其实，一个命题是否正确，和它是由哪个人提出的有联系吗？我们一直指责某些人“屁股决定脑袋”，可别人真的用脑袋说话了，我们每次还是盯着别人的屁股看。&lt;/p&gt; &lt;p&gt;如果用博客园里常见的现象就是，如果一个人在说比较微软技术和其他技术，而“恰好”那人又在说微软好话，又“恰好”那人是MVP。那么好，肯定会出现许多人说MVP是微软的托。我承认，有些MVP因为个人利益而会作微软的托。例如您现在在Google上搜索“MVP TFS”的第一条便是一则&lt;a href="http://flux88.com/blog/a-deleted-response-to-a-tfs-blog-post/"&gt;不光彩的事件&lt;/a&gt;。Ben Scheirman在一个TFS的MVP博客里回复说“即使有钱，也会用免费工具，因为更好用”，结果这条回复被删除了。对方私下写信说，这是因为他在用TFS咨询赚钱。&lt;/p&gt; &lt;p&gt;这不正说明MVP是托吗？不过这显然是MVP的个人行为，而不是MVP的群体做法。原因很简单，因为Ben自己也是个MVP，所以现在是“一个MVP说微软产品不好，其回复被另一个MVP删除了”。那么您说，MVP是否是微软的托？还有，您说我是不是微软的托？&lt;/p&gt; &lt;p&gt;同样道理，Oracle认证工程师说Oracle好，Rails爱好者说Rails好，是不是都是托呢？开源爱好者是不是开源的托？我还是认为，优秀的技术人员一定是有信仰的，也是有倾向性的。但是，他们的说得东西本身是否正确，并不以他们的倾向性而转移，那些东西的正确性是客观确定的。&lt;/p&gt; &lt;p&gt;我认为，如果社区要蓬勃发展，就要接受这种倾向性，并尽力保持客观的头脑，经常“&lt;a href="http://www.lixiaolai.com/index.php/archives/7367.html"&gt;换一种方式想问题&lt;/a&gt;”。我们要的不是和谐，而是争论，对客观事物的争论，而不是对个人的人身攻击。我认为，这方面我们做的还很不够，要继续努力。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/aspnet-php-benchmark-and-more.html#comments</comments>
      <pubDate>Fri, 18 Sep 2009 04:14:00 GMT</pubDate>
      <lastBuildDate>Fri, 18 Sep 2009 04:14:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>适合ASP.NET MVC的视图片断缓存方式（上）：起步</title>
      <link>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html</link>
      <guid>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html</guid>
      <description>&lt;p&gt;说到网站性能优化，没有什么比“缓存”更重要了。即便是某些朋友口中念念不忘的“静态页”，说到底也只是缓存了整张页面内容而已。但是，显然这样大粒度的缓存策略，在如今“牵一发而动全身”的Web 2.0站点中几乎是无法使用的。试想，在Twitter中的某个名人被数十万人订阅，那么他发一条消息，难道此时网站要去修改数十万用户的静态页面？因此，我们需要粒度更小的缓存。而比“整页缓存”粒度小一号的缓存，便是所谓“视图片断缓存”了。&lt;/p&gt; &lt;p&gt;视图片断缓存非常重要，因为它缓存的也是页面内容，这表示它比更低级别的缓存更有效率，也比静态页等整页内容缓存的适用面要大得多。在ASP.NET WebForm模型中提供了控件级别的缓存，我们可以为控件标记输出缓存策略，这样控件便不会每次都完整执行一遍。当然这个策略还不够灵活，因为它缓存的最小单元是“控件”，而不是页面中任意的部分。因此我在一年多前提出了一个&lt;a href="http://blog.zhaojie.me/2008/07/cachepanel.html"&gt;CachePanel&lt;/a&gt;，由它包装的页面内容都可以被缓存，无论其内部是控件还是普通输出的内容。在实际生产过程中，CachePanel起到了非常重要的作用，许多场景下只要在页面中包裹一个&amp;lt;ext:CachePanel runat="server" /&amp;gt;，性能立即就有了质的飞跃。&lt;/p&gt; &lt;p&gt;只可惜，在如今ASP.NET MVC的时代无法直接使用CachePanel这样的服务器端控件。因为CachePanel需要服务器端代码的配合，而ASP.NET MVC中的页面只是“视图模板”，除了呈现之外就不应该有其他职责。因此，我们必须提出一种脱离于后端代码的“标记”方式，将视图中的内容片断进行随意地缓存。在&lt;a href="http://api.rubyonrails.org/classes/ActionController/Caching/Fragments.html"&gt;Rails&lt;/a&gt;或&lt;a href="http://docs.djangoproject.com/en/dev/topics/cache/#template-fragment-caching"&gt;Django&lt;/a&gt;中都有类似的特性，但ASP.NET MVC甚至在2.0的Road Map中还没有包含这一功能，于是我们只能自己动手丰衣足食。不过有了ASP.NET WebForm作为强大的视图引擎，加这样的功能简直是举手之劳：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CacheExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static string &lt;/span&gt;Cache(
        &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;htmlHelper,
        &lt;span style="color: blue"&gt;string &lt;/span&gt;cacheKey,
        &lt;span style="color: #2b91af"&gt;CacheDependency &lt;/span&gt;cacheDependencies,
        &lt;span style="color: #2b91af"&gt;DateTime &lt;/span&gt;absoluteExpiration,
        &lt;span style="color: #2b91af"&gt;TimeSpan &lt;/span&gt;slidingExpiration,
        &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;object&lt;/span&gt;&amp;gt; func)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;cache = htmlHelper.ViewContext.HttpContext.Cache;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;content = cache.Get(cacheKey) &lt;span style="color: blue"&gt;as string&lt;/span&gt;;

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(content == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            content = func().ToString();
            cache.Insert(cacheKey, content, cacheDependencies, absoluteExpiration, slidingExpiration);
        }

        &lt;span style="color: blue"&gt;return &lt;/span&gt;content;
    }
}&lt;/pre&gt;
&lt;p&gt;我们为HtmlHelper增加了一个Cache扩展方法，接受一些缓存参数（缓存键，绝对过期时间，偏移过期时间），以及一个生成缓存内容的Func&amp;lt;object&amp;gt;委托。Cache方法的逻辑非常简单：首先根据缓存键来获取内容，如果存在则直接返回，否则即调用委托对象获得新内容，并将其放入缓存。这样在缓存命中的情况下，委托的开销便可以节省下来了。&lt;/p&gt;
&lt;p&gt;例如，我们可以使用这样的代码进行测试：&lt;/p&gt;&lt;pre class="code"&gt;Before Rendering:
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;

&lt;/span&gt;Rendering:
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Cache(&lt;span style="color: #a31515"&gt;"Now"&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now.AddSeconds(60), &lt;span style="color: #2b91af"&gt;Cache&lt;/span&gt;.NoSlidingExpiration,
    () =&amp;gt; { System.Threading.&lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.Sleep(5000); &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now; }) &lt;span style="background: #ffee62"&gt;%&amp;gt;

&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;

&lt;/span&gt;After Rendering:
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;在实际情况中，我们是不会在代码中调用Thread.Sleep方法的，不过这里我们需要模拟一段开销，因此通过暂停当前线程来实现时间消耗。于是我们第一次打开页面：&lt;/p&gt;&lt;pre class="code"&gt;Before Rendering: 2009/9/17 16:52:37 
Rendering: 2009/9/17 16:52:42 
After Rendering: 2009/9/17 16:52:42&lt;/pre&gt;
&lt;p&gt;从结果中可以看出，Before Rendering和After Rendering相差了5秒钟，这就是Thread.Sleep(5000)的效果。但是如果您在60秒以内再次刷新页面，便可以看到缓存的效果：&lt;/p&gt;&lt;pre class="code"&gt;Before Rendering: 2009/9/17 16:52:55 
Rendering: 2009/9/17 16:52:42 
After Rendering: 2009/9/17 16:52:55&lt;/pre&gt;
&lt;p&gt;可以看出，Rendering阶段显示的还是刚才的时间，而Before Rendering和After Rendering是即时更新的。此外，由于Cache方法将Thread.Sleep(5000)的开销节省了下来，因此Before Rendering和After Rendering两个阶段打印出的时间完全相同。&lt;/p&gt;
&lt;p&gt;怎么样，简单吧。不过您应该会感到疑惑，这不是我们想要的结果啊，我们想缓存的是页面上的一个片断，但是现在必须将被缓存的内容作为一个完整的字符串输出，那么我们又该如何实现呢？难道我们要这么写吗？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Cache(..., () =&amp;gt; &lt;span style="color: #a31515"&gt;"&amp;lt;span style=\"color:red;\"&amp;gt;" &lt;/span&gt;+ Model.Title + &lt;span style="color: #a31515"&gt;"&amp;lt;/span&amp;gt;"&lt;/span&gt;) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;当然不可能这样。如果只是这样的话，那么这个Cache的可用性毫无疑问会非常低。因此，我们还需要寻找更好的解决方案——关于这点，我们下次再聊。而目前的Cache方法，最方便的输出大端页面内容的做法则是将内容放在一个Partial View中，然后使用Html.Partial方法输出内容：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.Cache(..., () =&amp;gt; Html.Partial(&lt;span style="color: #a31515"&gt;"MyPartialViewToCache"&lt;/span&gt;)) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;请注意，我们这里使用的是&lt;a href="http://blog.zhaojie.me/2009/09/standard-webformview-patch-and-mvcpatch-project.html"&gt;扩展后的Partial方法&lt;/a&gt;，而不是自带的RenderPartial。之前我们谈过&lt;a href="http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html"&gt;WebForm页面的输出方式&lt;/a&gt;，而RenderPartial方法是直接向Response.Output输出页面内容，因此我们无法将其捕捉为一个字符串。不过，之前文章中的Partial方法是“山寨”版本，而符合“标准”的Partial方法实现已经包含在&lt;a href="http://mvcpatch.codeplex.com/"&gt;MvcPatch&lt;/a&gt;项目中。如果您感兴趣的话，可以获取它的源代码并编译。我这段时间在一部分一部分地将以前项目中较为通用的扩展及修改提取至MvcPatch中，希望可以使MvcPatch成为一个可复用的强大组件。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;适合ASP.NET MVC的视图片断缓存方式（上）：起步&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-2-more-friendly-api.html"&gt;适合ASP.NET MVC的视图片断缓存方式（中）：更实用的API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-3-rendering-principle.html"&gt;适合ASP.NET MVC的视图片断缓存方式（下）：页面输出原则&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/aspnet-mvc-fragment-cache-1.html#comments</comments>
      <pubDate>Thu, 17 Sep 2009 09:19:00 GMT</pubDate>
      <lastBuildDate>Thu, 17 Sep 2009 09:19:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>ASP.NET WebForm页面内容输出方式</title>
      <link>http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html</link>
      <guid>http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html</guid>
      <description>&lt;p&gt;这次我们谈的话题是“Web Form页面上输出内容的方式”。这其实是一个非常旧的话题了，因为本文的内容甚至可以运用于ASP.NET 1.1之上。不过这个话题的适用范围很广，因为即使是目前最新的ASP.NET MVC框架，它的默认视图引擎依旧是基于ASP.NET WebForm的（如Page，Control，MasterPage）。甚至说，由于ASP.NET MVC框架的特性，我们会遇到更多在页面上“直接输出”内容的情况。因此，这个话题在ASP.NET MVC应用中可能由为重要。&lt;/p&gt; &lt;p&gt;那么就拿ASP.NET MVC举例吧。假如，我们在页面上生成一个Partial View，我们可以这么做：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.RenderPartial(&lt;span style="color: #a31515"&gt;"MyPartialView"&lt;/span&gt;); &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;然而，在前一篇文章中我们提出了一个新的方法Partial，它返回一个字符串，它可以在页面上这样使用：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Html.Partial(&lt;span style="color: #a31515"&gt;"MyPartialView"&lt;/span&gt;) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;一个aspx页面会被编译成Page类的一个子类，这个子类的主要“功能”是覆盖了基类的Render方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyPage &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Page
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Render(&lt;span style="color: #2b91af"&gt;HtmlTextWriter &lt;/span&gt;writer)
    {
        ...
    }
}&lt;/pre&gt;
&lt;p&gt;我们平时在aspx页面中编写的大量内容，其实都会变成操作writer的代码。例如使用writer.Write方法输出内容，或者把writer交给子控件的Render方法用于生成内容。那么，以上两种页面上的标记分别又是如何操作writer的呢？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= &lt;em&gt;expression&lt;/em&gt; &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;首先是&amp;lt;%= %&amp;gt;标记。&amp;lt;%= %&amp;gt;标记内包含的是一个“表达式”，因此它不能以分号结尾。表达式内部的数据就会直接写入writer。例如这样的标记：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;在编译过后就成为：&lt;/p&gt;&lt;pre class="code"&gt;writer.Write(&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now)&lt;/pre&gt;
&lt;p&gt;与&amp;lt;%= %&amp;gt;标记不同，&amp;lt;% %&amp;gt;标记中间其实包含的是“语句”。语句自然可以有多行，自然每行最后需要有分号，这就像我们平时写C#代码那样。不过实际上，语句的功能其实并不是为了“输出内容”，而是用来“控制逻辑”。例如，您在页面上写了这样的代码：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;, &lt;span style="color: blue"&gt;bool&lt;/span&gt;&amp;gt; odd = i =&amp;gt; i % 2 != 0; &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这样就相当于您在Render方法内部声明了一个局部变量odd，它的类型是一个Func&amp;lt;int, bool&amp;gt;委托。而如果您编写这样的代码：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 10; i++) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;span&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;i + 1 &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;span&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;则生成的Render方法中就会包含：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 10; i++)
{
    writer.Write(&lt;span style="color: #a31515"&gt;"&amp;lt;span&amp;gt;"&lt;/span&gt;);
    writer.Write(i + 1);
    writer.Write(&lt;span style="color: #a31515"&gt;"&amp;lt;/span&amp;gt;"&lt;/span&gt;);
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如果是写在页面上的普通HTML标记，编译后就被当作普通字符串来处理了。有些朋友一直谈“客户端控件”等等，其实如果一个元素上没有runat="server"标记，ASP.NET只是把它们当作普通字符串处理，并不会有任何“HTML元素”的概念。当然，上面的代码表现的是“意图”，事实上在编译过后aspx页面中的空格和换行等字符也会包含在输出的内容中&lt;sup&gt;1&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;那么，既然&amp;lt;% %&amp;gt;中包含的是用来控制逻辑的语句，本身不是用来表示输出的，那么为什么刚才代码中的Html.RenderPartial方法也会生成页面内容呢？那是因为RenderPartial方法直接向当前HttpContext.Response.Output里写入字符了。很多朋友经常使用Response.Write来输出内容，其实在Write方法内部就是输出到Output中。&lt;/p&gt;
&lt;p&gt;事实上，即使我们的页面中使用了HtmlTextWriter来输出内容，但它内部也是封装了Output所暴露出的TextWriter中。为了验证，您可以在代码中设置断点并观察Render方法的writer参数，在“正常情况下”可以发现writer.InnerWriter属性是一个HttpWriter对象，这是个TextWriter的子类，也是ASP.NET中定义的内部类型。&lt;/p&gt;
&lt;p&gt;这便是ASP.NET页面输出的细节。那么请问，以下两种输出方式的区别是什么呢？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;&lt;span style="color: #a31515"&gt;"Hello World" &lt;/span&gt;&lt;span style="background: #ffee62"&gt;%&amp;gt;
&amp;lt;%&lt;/span&gt; Response.Write(&lt;span style="color: #a31515"&gt;"Hello World"&lt;/span&gt;); &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;从效果上看，两者没有任何区别。但是实际上前者是使用页面的HtmlTextWriter对象输出的，而后者则直接向Response.Output里输出内容。这个区别看似不重要，但其实它会涉及到我们很多开发过程中可用的实践方式。在今后的文章中，我会提出生成页面内容的一些准则，解释这些准则的原因，并指出ASP.NET MVC本身是如何破坏这些设计准则的。&lt;/p&gt;
&lt;p&gt;自然，修改版本的ASP.NET MVC会发布在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;项目中。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注1：&lt;/strong&gt;由&lt;a href="http://www.cnblogs.com/XmNotes/"&gt;小城故事&lt;/a&gt;同学提醒，严格说来，页面中的纯文本会被作为一个Literal控件处理，一段连续的纯文本作为一个Literal控件。在输出时，Literal控件的Render方法会将纯文本输出至HtmlTextWriter中。其效果就等同于writer.Write(...)方式的纯文本输出。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/where-does-aspnet-page-render-to.html#comments</comments>
      <pubDate>Wed, 16 Sep 2009 06:41:00 GMT</pubDate>
      <lastBuildDate>Wed, 16 Sep 2009 06:41:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>Get Ready for Real World ASP.NET MVC</title>
      <link>http://blog.zhaojie.me/2009/09/get-ready-for-real-world-aspnet-mvc-at-teched-2009.html</link>
      <guid>http://blog.zhaojie.me/2009/09/get-ready-for-real-world-aspnet-mvc-at-teched-2009.html</guid>
      <description>&lt;p&gt;&lt;a href="http://www.microsoft.com/china/teched/2009/"&gt;2009年的TechED大会即将在北京举行&lt;/a&gt;（11/5 - 11/7），其中有我的一个Session，我将其命名为“Real World ASP.NET MVC”，希望可以分享一下自己在ASP.NET MVC方面的使用体会。&lt;/p&gt; &lt;p&gt;最近我一直在总结ASP.NET MVC使用的方方面面，将我之前提出的一些“最佳实践”给具体化，此外我也在构建&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;这样的“补丁”、“扩展”及“示例”。有些朋友提出，我写的这一系列文章过于松散，话题之间关联不大。其实刚好相反，每个话题之间都是有联系的，我是把我解决问题过程中的思路细节表现出来。等一个阶段过后，我会整理一下我的思维脉络，把这些话题完整的串联起来。也算是对近期工作的一个总结。&lt;/p&gt; &lt;p&gt;此外，我还打算分享一下在进行ASP.NET MVC开发时的一些周边支持，它可能并不是在使用ASP.NET MVC框架，但是可以让系统构建过程更为“敏捷”，在TechED上的Session也主要是朝这个方向前进的。&lt;/p&gt; &lt;p&gt;而现在，希望大家可以给我一点帮助。例如，您在使用ASP.NET MVC开发Web应用程序时有没有遇到什么困难？您认为ASP.NET MVC哪方面还无法让人满意？各个方面都可以。&lt;/p&gt; &lt;p&gt;我会选择合适的话题来补充TechED的内容，也可以用其他方式（主要是博客）来谈下自己的感想。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/get-ready-for-real-world-aspnet-mvc-at-teched-2009.html#comments</comments>
      <pubDate>Wed, 16 Sep 2009 05:37:00 GMT</pubDate>
      <lastBuildDate>Wed, 16 Sep 2009 05:37:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>WebFormView的标准修改办法及MvcPatch项目</title>
      <link>http://blog.zhaojie.me/2009/09/standard-webformview-patch-and-mvcpatch-project.html</link>
      <guid>http://blog.zhaojie.me/2009/09/standard-webformview-patch-and-mvcpatch-project.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2009/09/webviewengine-bug-always-render-to-current-context.html"&gt;上一篇文章&lt;/a&gt;中我提到WebFormView的实现破坏了IView对象设计思路，它会把视图内容直接生成至HttpContext.Current而不是Render方法指定的TextWriter中。目前，WebFormView.Render的调用方只有两个：ViewResult.ExecuteResult方法还有HtmlHelper.RenderPartial方法，但是这两者原本的目的地就是当前的HttpContext，因此在平时使用时WebFormView的错误实现并不会造成问题。&lt;/p&gt; &lt;p&gt;但是，如果我们在构建一个面向AJAX请求的Action，此时View的内容可能只是输出的一部分，甚至我们要对内容进行过滤/编码等额外操作。此时，我们就希望指定一个TextWriter用于收集内容——但是WebFormView自然无法做到。之前我提出了一种非常临时，非常山寨，非常简陋，绕弯，但是可行，或者说是可以“表现出解决问题的方法”的代码，修改一下便能说明问题：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static string &lt;/span&gt;Partial(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;htmlHelper, &lt;span style="color: blue"&gt;string &lt;/span&gt;partial)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;viewInstance = &lt;span style="color: #2b91af"&gt;BuildManager&lt;/span&gt;.CreateInstanceFromVirtualPath(partial, &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;object&lt;/span&gt;));
        &lt;span style="color: blue"&gt;var &lt;/span&gt;control = viewInstance &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewUserControl&lt;/span&gt;;

        control.ViewContext = htmlHelper.ViewContext;
        control.ViewData = htmlHelper.ViewData;

        &lt;span style="color: #2b91af"&gt;Page &lt;/span&gt;page = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewPage&lt;/span&gt;();
        page.Controls.Add(control);

        &lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;writer = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StringWriter&lt;/span&gt;();
        htmlHelper.ViewContext.HttpContext.Server.Execute(page, writer, &lt;span style="color: blue"&gt;false&lt;/span&gt;);

        &lt;span style="color: blue"&gt;return &lt;/span&gt;writer.ToString();
    }
}&lt;/pre&gt;
&lt;p&gt;这个HtmlHelper的扩展方法Partial，和HtmlHelper自带的RenderPartial功能比较接近，不过Partial是将视图内容直接生成一个字符串并返回，RenderPartial方法是直接输出至当前HttpContext。因此它们在视图中的使用方式是不同的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; Html.RenderPartial(&lt;span style="color: #a31515"&gt;"MyPartialView"&lt;/span&gt;)&lt;font color="#ff0000"&gt;;&lt;/font&gt; &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;
&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;font color="#ff0000"&gt;=&lt;/font&gt; Html.Partial(&lt;span style="color: #a31515"&gt;"MyPartialView"&lt;/span&gt;) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;RenderPartial以&amp;lt;%开头，末尾有分号。而Partial以&amp;lt;%=开头，末尾没有分号。关于视图中的各种输出方式，我最近在阅读ASP.NET源代码时有更深的了解，下次我们再详谈。不过目前，我们还是专注于WebFormView的修改。&lt;/p&gt;
&lt;p&gt;WebFormView目前问题的主要原因，是由于ViewPage和ViewUserControl两个类中缺乏合适的接口：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewPage &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Page&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IViewDataContainer &lt;/span&gt;{
    ...
    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;RenderView(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;viewContext) {
        ViewContext = viewContext;
        InitHelpers();
        &lt;span style="color: green"&gt;// Tracing requires Page IDs to be unique.
        &lt;/span&gt;ID = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToString();
        ProcessRequest(&lt;span style="color: #2b91af"&gt;HttpContext&lt;/span&gt;.Current);
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewUserControl &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;UserControl&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IViewDataContainer &lt;/span&gt;{
    ...
    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;RenderView(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;viewContext) {
        viewContext.HttpContext.Response.Cache.SetExpires(&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now);
        &lt;span style="color: green"&gt;// 这是ViewPage的子类，专用于生成独立的ViewUserControl内容&lt;/span&gt;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;containerPage = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewUserControlContainerPage&lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
        &lt;span style="color: green"&gt;// Tracing requires Page IDs to be unique.
        &lt;/span&gt;ID = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToString();

        &lt;span style="color: green"&gt;// 其中会执行ViewUserControlContrainerPage的RenderView方法&lt;/span&gt;
        RenderViewAndRestoreContentType(containerPage, viewContext);
    }
}&lt;/pre&gt;
&lt;p&gt;可见，在ViewPage和ViewUserControl中各有一个RenderView方法，它们只包含一个ViewContext参数，但是却没有输出目的地。因此，最终它们使用HttpContext.Current这个邪恶的、臭名昭著的静态属性来生成内容。现在想起来，我当时在搞&lt;a href="http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action.html"&gt;异步Action&lt;/a&gt;时，遭遇异常而不得不手动保持HttpContext就是这个原因造成的。于是我们目前修改的方式，便是为ViewPage和ViewUserControl增加一个额外的TextWriter参数：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewPage &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Page&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IViewDataContainer &lt;/span&gt;{
    ...
    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;RenderView(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;viewContext, &lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;writer) {
        ViewContext = viewContext;
        InitHelpers();
        &lt;span style="color: green"&gt;// Tracing requires Page IDs to be unique.
        &lt;/span&gt;ID = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToString();
        &lt;strike&gt;ProcessRequest(&lt;span style="color: #2b91af"&gt;HttpContext&lt;/span&gt;.Current);&lt;/strike&gt;
        viewContext.HttpContext.Server.Execute(&lt;span style="color: blue"&gt;this&lt;/span&gt;, writer, &lt;span style="color: blue"&gt;false&lt;/span&gt;);
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewUserControl &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;UserControl&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IViewDataContainer &lt;/span&gt;{
    ...
    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;RenderView(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;viewContext, &lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;writer) {
        viewContext.HttpContext.Response.Cache.SetExpires(&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;containerPage = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewUserControlContainerPage&lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
        &lt;span style="color: green"&gt;// Tracing requires Page IDs to be unique.
        &lt;/span&gt;ID = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToString();

        RenderViewAndRestoreContentType(containerPage, viewContext, writer);
    }
}&lt;/pre&gt;
&lt;p&gt;至于其他“顺其自然”的修改就不值一提了。&lt;/p&gt;
&lt;p&gt;在我看来，这种问题可能不是ASP.NET MVC的设计问题（Design Issue），但是这也是它的内部实现的低级错误。对于此类问题，如果使用扩展的方式进行修改会显得沉重而麻烦，需要各种扩展和配置才能使用。之前项目中使用的便是基于“外部扩展”来回避“内部错误”的办法，而目前已经换成自行修改编译过的System.Web.Mvc.dll了。这个修改版本目前已经发布在&lt;a href="http://www.codeplex.com/"&gt;CodePlex&lt;/a&gt;中的&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;项目中，如果您感兴趣可以获取它的源代码并编译使用。&lt;/p&gt;
&lt;p&gt;目前，MvcPatch包含两个修改，一个自然就是目前WebViewEngine的问题，而另一个便是之前提过的&lt;a href="http://blog.zhaojie.me/2009/08/asp-net-mvc-defaultcontrollerfactory-thread-unsafe.html"&gt;DefaultControllerFactory线程安全问题&lt;/a&gt;，以后我会补充更多设计方面的修改和扩展。在使用MvcPatch的时候，除了让您的项目引用正确的程序集之外，还必须将web.config文件中各类型的名称指向修改正确。因为使用ASP.NET MVC的模板创建项目时，它的web.config会使用GAC中注册的强类型的ASP.NET MVC 1.0程序集。如果修改不正确，在使用MvcPatch的程序集时便会遇到错误。&lt;/p&gt;
&lt;p&gt;因此我们也可以发现，使用MvcPatch的好处在于，我们不需要使用外部扩展的方式来构建workaround，但是它也有缺点，那就是一些依赖于ASP.NET MVC 1.0程序集的项目无法和我们一起使用了。好在目前看起来这些项目都是些开源产品，如&lt;a href="http://www.telerik.com/products/aspnet-mvc.aspx"&gt;Telerik Extensions for ASP.NET MVC&lt;/a&gt;，我们可以下载它们的源代码，基于MvcPatch的程序集编译后再使用。&lt;/p&gt;
&lt;p&gt;您别嫌麻烦，这就是享受开源的优势时需要付出的小小代价。&lt;/p&gt;
&lt;p&gt;最后再谈一件事情。昨天晚上写完文章之后，我想到这种“补丁版本”并不是长久之计，因此在CodePlex上给&lt;a href="http://aspnet.codeplex.com/"&gt;ASP.NET项目&lt;/a&gt;提了一个Issue：&lt;a href="http://aspnet.codeplex.com/WorkItem/View.aspx?WorkItemId=4249"&gt;WebFormView总是输出至HttpContext.Current而不是指定的TextWriter&lt;/a&gt;。今天早上发现已经有了ASP.NET团队成员回复，他们表示内部的代码库中已经修改了这个问题，将会体现在ASP.NET MVC 2的Preview 2版本中。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/standard-webformview-patch-and-mvcpatch-project.html#comments</comments>
      <pubDate>Tue, 15 Sep 2009 04:11:00 GMT</pubDate>
      <lastBuildDate>Tue, 15 Sep 2009 04:11:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>应该算是WebFormView的一个Bug</title>
      <link>http://blog.zhaojie.me/2009/09/webviewengine-bug-always-render-to-current-context.html</link>
      <guid>http://blog.zhaojie.me/2009/09/webviewengine-bug-always-render-to-current-context.html</guid>
      <description>&lt;p&gt;最近需要搞一些重要的功能，结果又遇到了意料外的障碍。于是又仔细地看了看ASP.NET和ASP.NET MVC的源代码，又发现了以前不曾知道的一些细节。您最多说ASP.NET WebForms模型不一定适合某些Web应用程序的开发，但是我想没有人可以否认ASP.NET中设计的巧妙——以及复杂程度。其实ASP.NET为我们留下了不少切入点，但几乎没什么书会提到这些切入点，我们只能从微软自己的框架中一探究竟。&lt;/p&gt; &lt;p&gt;不过这次我想谈的是ASP.NET MVC框架中的一个Bug，这个Bug在一般情况下不会出现问题，但是这的确违反了ASP.NET MVC自身的设计。这个问题就出在WebFormView对象的实现上。&lt;/p&gt; &lt;p&gt;WebFormView是一个视图对象的实现。而在ASP.NET MVC中，任何视图都需要实现一个IView接口：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IView &lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Render(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;viewContext, &lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;writer);
}
&lt;/pre&gt;
&lt;p&gt;Render方法的目的自然是根据ViewContext对象中的数据，将视图内容输出至TextWriter中。例如在HtmlHelper的RenderPartial方法，便是将一个Partial View输出至Response中：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;{
    ...
&lt;span style="color: blue"&gt;    internal virtual void &lt;/span&gt;RenderPartialInternal(
&lt;span style="color: blue"&gt;        string &lt;/span&gt;partialViewName,
&lt;span style="color: #2b91af"&gt;        ViewDataDictionary &lt;/span&gt;viewData,
&lt;span style="color: blue"&gt;        object &lt;/span&gt;model,
&lt;span style="color: #2b91af"&gt;        ViewEngineCollection &lt;/span&gt;viewEngineCollection) {

        ...

        &lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;newViewContext = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewContext&lt;/span&gt;(...);
        &lt;span style="color: #2b91af"&gt;IView &lt;/span&gt;view = FindPartialView(newViewContext, partialViewName, viewEngineCollection);
        view.Render(newViewContext, ViewContext.HttpContext.Response.Output);
    }
}&lt;/pre&gt;
&lt;p&gt;虽然我认为这里的做法是不太妥当的（这点下次再谈），但是这的的确确地表现了Render方法的设计意图。只可惜在WebFormView中，Render方法却违背了这一设计：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebFormView &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IView &lt;/span&gt;{
    ...
&lt;span style="color: blue"&gt;    public virtual void &lt;/span&gt;Render(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;viewContext, &lt;span style="color: #2b91af"&gt;TextWriter &lt;/span&gt;writer) {
        ...
        &lt;span style="color: blue"&gt;object &lt;/span&gt;viewInstance = ...;
        ...

        &lt;span style="color: #2b91af"&gt;ViewUserControl &lt;/span&gt;viewUserControl = viewInstance &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewUserControl&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(viewUserControl != &lt;span style="color: blue"&gt;null&lt;/span&gt;) {
            RenderViewUserControl(viewContext, viewUserControl);
            &lt;span style="color: blue"&gt;return&lt;/span&gt;;
        }

        ...
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;RenderViewUserControl(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;context, &lt;span style="color: #2b91af"&gt;ViewUserControl &lt;/span&gt;control) {
        ...

        control.ViewData = context.ViewData;
        control.RenderView(context);
    }
}&lt;/pre&gt;
&lt;p&gt;对于Partial View，WebFormView会加载合适的ViewUserControl实例，并调用其RenderView方法生成内容……但是，我们的writer参数到哪里去了？没错，对writer参数Find All Reference就会发现，&lt;font color="#ff0000"&gt;这个参数根本没有用到&lt;/font&gt;。既然在这里就已经抛弃了我们指定writer，那么接下来的逻辑再怎么搞也就“那么一回事儿”了。&lt;/p&gt;
&lt;p&gt;如果您感兴趣阅读代码的话，会发现事实上最终这个对象被放入了一个新建的ViewPage对象中，然后调用ViewPage的RenderView方法生成视图内容：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewPage &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Page&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IViewDataContainer &lt;/span&gt;{
    ...
&lt;span style="color: blue"&gt;    public virtual void &lt;/span&gt;RenderView(&lt;span style="color: #2b91af"&gt;ViewContext &lt;/span&gt;viewContext) {
        ViewContext = viewContext;
        InitHelpers();
        &lt;span style="color: green"&gt;// Tracing requires Page IDs to be unique.
        &lt;/span&gt;ID = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToString();
        ProcessRequest(&lt;span style="color: #2b91af"&gt;HttpContext&lt;/span&gt;.Current);
    }
}&lt;/pre&gt;
&lt;p&gt;瞧到这个HttpContext.Current了吗？也就是说，无论RenderView方法何时调用，永远是向HttpContext.Current输出内容。这个设计很不合理，但是修改起来还是非常简单的，例如以下几行代码就可以得到差不多的效果：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlExtensions
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static void &lt;/span&gt;Partial(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlHelper &lt;/span&gt;htmlHelper, &lt;span style="color: blue"&gt;string &lt;/span&gt;partial)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;viewInstance = &lt;span style="color: #2b91af"&gt;BuildManager&lt;/span&gt;.CreateInstanceFromVirtualPath(partial, &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;object&lt;/span&gt;));
        &lt;span style="color: blue"&gt;var &lt;/span&gt;control = viewInstance &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewUserControl&lt;/span&gt;;

        control.ViewContext = htmlHelper.ViewContext;
        control.ViewData = htmlHelper.ViewData;

        &lt;span style="color: #2b91af"&gt;Page &lt;/span&gt;page = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewPage&lt;/span&gt;();
        page.Controls.Add(control);

        htmlHelper.ViewContext.HttpContext.Server.Execute(
            page,
            htmlHelper.ViewContext.HttpContext.Response.Output,
            &lt;span style="color: blue"&gt;false&lt;/span&gt;);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;但是我不喜欢这种做法，因为它没有遵循ASP.NET MVC既定的模型。ASP.NET MVC的确可以扩展，但如果需要按照标准扩展的话，我们作的事情就多了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;继承WebFormView，覆盖RenderView方法。&lt;/li&gt;
&lt;li&gt;继承WebFormViewEngine，覆盖CreatePartialView方法，返回刚创建的新类。&lt;/li&gt;
&lt;li&gt;在Application Start时，使用新的ViewEngine类替换ASP.NET MVC原有的视图引擎。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;但是在实际情况中，我会选择使用使用第三种方法：下载ASP.NET MVC的源代码，改写，编译。既然它是MS-PL的授权协议，为什么不自己动手打一些Patch呢？事实上，我也打算使用这种方法来修补ASP.NET MVC的Bug或Design Issue，并发布一个临时的新项目，就叫作……MvcPatch如何？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/webviewengine-bug-always-render-to-current-context.html#comments</comments>
      <pubDate>Mon, 14 Sep 2009 07:33:00 GMT</pubDate>
      <lastBuildDate>Mon, 14 Sep 2009 07:33:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/essential/">重中之重</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/discussion/">思考讨论</category>
      <title>一份值得阅读的幻灯片：微软对PHP支持的改进，及其它一些胡言乱语</title>
      <link>http://blog.zhaojie.me/2009/09/php-on-windows-and-more.html</link>
      <guid>http://blog.zhaojie.me/2009/09/php-on-windows-and-more.html</guid>
      <description>&lt;p&gt;这里有一份我觉得值得推荐给大家的幻灯片（&lt;a href="http://cid-fa447a56f6b57df5.skydrive.live.com/self.aspx/PHP/WordCamp2009PHPonWindows.pdf"&gt;下载链接&lt;/a&gt;）。这个幻灯片是不久前举办的&lt;a href="http://2009.wordcampchina.org/"&gt;WordCamp China 2009&lt;/a&gt;上微软&lt;a href="http://blogs.msdn.com/cqwang/"&gt;王超群&lt;/a&gt;的演讲，演讲主题是《熟悉的陌生人：微软对PHP的新支持使WordPress在IIS7上雄起》。WordCamp是业界著名的PHP大会，不过我这里推荐这个幻灯片不是为了为了推广PHP，而是为了说明一些其他问题——当然也有关于技术的有价值的内容，这些您看了幻灯片和我的文章之后就会明白了。&lt;/p&gt; &lt;p&gt;对了，之前我已经在InfoQ上&lt;a href="http://www.infoq.com/cn/news/2009/08/php-on-iis-7"&gt;写了一篇新闻报道了这次事件&lt;/a&gt;。不过新闻归新闻，目的是客观说明情况而不是发表个人观点。不过博客是个人地盘，我就打算在此畅所欲言了。&lt;/p&gt; &lt;p&gt;说到PHP，可能最容易让人想起的就是著名的LAMP架构（Linux + Apache + MySQL + PHP），而这全开放的平台似乎和“封闭”的微软技术距离比较远。不过微软其实一直没有放弃对PHP的“追求”，这次演讲谈的就是微软在这方面的努力，以及取得的成果。我推荐这个幻灯片的主要原因之一，便是它写的非常有水平，尤其是开头一部分。在幻灯片一开始阐述了微软对待开源的态度，希望和PHP的同志们拉近一些距离：&lt;/p&gt;&lt;a href="http://img.zhaojie.me/blog/168980/o_this-is-not-your-fathers-ms.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/168980/r_this-is-not-your-fathers-ms.png" width="450"&gt;&lt;/a&gt;&lt;p&gt;嗯，就是这个观点：“微软已经不是当年的微软了”，它变了，变得怎么样了呢？变得拥抱开源了：“开源的朋友们，不要看到“微软”两个字就心生厌恶，来，抱一个”。此外还引用了微软首席软件架构师Ray Ozzie（如果我没有认错的话）的言论：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;I think any company these days, any technology provider, even Microsoft, has to find the right balance of being a contributor and user of open source.  &lt;p&gt;我认为如今任何的公司及技术提供商，即使是微软，也必须在开源的用户和贡献者中寻找一个合适的平衡点。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;如果说Ray Ozzie可能还是在“王婆卖瓜”的话，那么Linux创建者、开源领袖Linus Torvald&lt;a href="http://www.linux-mag.com/cache/7439/1.html"&gt;最近发表的话语&lt;/a&gt;应该更有“参考价值”：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Oh, I’m a big believer in “technology over politics” ... There are “extremists” in the free software world, but that’s one major reason why I don’t call what I do “free software” any more. &lt;font color="#ff0000"&gt;I don’t want to be associated with the people for whom it‟s about exclusion and hatred.&lt;/font&gt;  &lt;p&gt;哦，我强烈认为“技术高于政治”……自由软件世界中有一些“极端主义者”，这也是我不再把我做的事情称作“自由软件”的主要原因。&lt;font color="#ff0000"&gt;我不想和那些有排斥和憎恶心态的人产生关系。&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;这句话在有人反对“微软为Linux提交GPL代码以提高Hyper-V的兼容性”时说的。Linus的意思再明确不过了：“技术就是就是技术，不要有门户之见”。顺便一提，开源软件（Open Source）和自由软件（Free Software）是有很大区别的，不应该混为一谈。就我个人而言，我喜欢“开源”而不喜欢“自由”。我也不喜欢FSF&lt;a href="http://news.cnblogs.com/n/48730/"&gt;觉得微软永远是错的&lt;/a&gt;，不喜欢它&lt;a href="http://www.cnbeta.com/articles/92227.htm"&gt;搞某些东西的方式&lt;/a&gt;。不过肯定也有人喜欢，每个人都可以有自己的观点。&lt;/p&gt; &lt;p&gt;好像有点说歪了。其实我也不知道微软是不是只是“口头”上讨好开源界，但是我至少看出，微软在想办法证明自己——不管这个证明是不是有什么陷阱，有什么“不可告人的秘密”。在这方面，微软至少在摆事实，讲道理，而不是随口说“我就是拥抱开源”。我觉得，在中学里学写议论文的时候，大家都应该已经知道就被告知论述的基本方式是“摆事实”，“讲道理”。可是从平时讨论的时候，我却又觉得根本不是那么一回事情。说句得罪人的话，我觉得许多朋友&lt;a href="http://blog.zhaojie.me/2009/04/be-a-professional-arguer.html"&gt;缺乏必要的逻辑思维能力&lt;/a&gt;，会产生非常多的&lt;a href="http://www.yeeyan.com/articles/view/65452/28581"&gt;逻辑谬误&lt;/a&gt;出来，但是却都还&lt;a href="http://www.lixiaolai.com/index.php/archives/7356.html"&gt;坚持自己是正确的&lt;/a&gt;。搞到最后，吵起架来彼彼皆是。而且往往在这个时候，无数匿名兄弟就跳出来发挥自己的聪明才智，各种讽刺挖苦谩骂的奇思妙语一个接一个，令人宛如置身于天上“猫扑”，人间“天涯”。&lt;/p&gt; &lt;p&gt;其实坚持自己是正确的不要紧，我也喜欢坚持自己的意见，谁不希望自己是正确的呢？但是要拿出理论根据来。其实作为技术从业人员，在许多问题上拿出根据来非常简单，例如写一个程序便知&lt;a href="http://blog.zhaojie.me/2009/05/generic-performance-test.html"&gt;泛型会不会降低性能&lt;/a&gt;。如果有些问题很难用数据说明问题（如数据获取太难），那么至少也要找出一些别人的看法来支持自己的观点。如果一味地“我认为”，“我觉得”效果自然就大打折扣了。因此，我现在也经常会有意地在文章中引用自己或其它的人的说法，至少可以让自己有“底气”一些，而不会感觉像是在扯淡。&lt;/p&gt; &lt;p&gt;我个人比较反感“光说不练”的人，我希望看到的不是“个人说法”，而是多个人的观点，或者是实际效果。例如前几天有个匿名朋友在我博客上留言，偏要坚持“静态方法的调用会阻塞其它线程访问”，我说“你试试看就知道了”，他也原样复制一遍发回给我，针锋相对，不屈不饶。不知道他最后是否认为他达到了论证“我在误导初学者，为自己的书作广告”的目的（我写过书吗？），至少我当时，既无奈，又窝火。&lt;/p&gt; &lt;p&gt;既然谈到了技术，那就再谈远一些。这位认为“静态方法的调用会阻塞其它线程访问”的朋友证明了他自己在某个方面学的有问题，而我前几天发表的&lt;a href="http://blog.zhaojie.me/2009/09/double-check-failure.html"&gt;Double Check文章中&lt;/a&gt;，有许多朋友提出“怎么能lock在一个实例字段上”，“应该lock在一个静态字段才能产生效果”。换句话说，在这些朋友看来，一个实例方法是不会产生线程安全问题的——虽然&lt;a href="http://blog.zhaojie.me/2009/08/asp-net-mvc-defaultcontrollerfactory-thread-unsafe.html"&gt;ASP.NET MVC框架的DefaultControllerFactory就出现了这样的毛病&lt;/a&gt;。这说明了在多线程开发方面，社区的整体意识还处于一个非常薄弱的程度——希望我这么说不会引起各位的不满。&lt;/p&gt; &lt;p&gt;此外，昨天我意识到原来很多朋友用了很久的for却无法写出其等价的while写法，又想起之前发现公司里的大部分同事不知道C#中使用&amp;#64;开头的字符串表示法中如何包含一个双引号。这应该都是基础中的基础，应该人人知道，不是吗？但事实就是让我感到惊讶，许多朋友认为自己不停地在学东西，ASP.NET MVC、Sliverlight，WPF一个一个地学，但是最后最普通的东西却不知道。结果工作找不好，薪水拿不高，最后一崩溃又开始叫嚷着这个行业没前途。那么，有没有想过换一种思路来学习，例如，不要随意轻视一些东西？&lt;/p&gt; &lt;p&gt;说到这里，我又想起其它一些事情，那就是我觉得如今社区里的“风气”出现了比较严重的问题。例如最近不止一次发生&lt;a href="http://www.cnblogs.com/MichaelTao/archive/2009/08/04/1536983.html"&gt;围攻面试者的“群体性事件”&lt;/a&gt;，每次有人发表面试对方的题目之后，就有大量朋友上前表示不满，对于简单的题目认为太基础了，考不出能力。对于困难的题目，就认为它太难，钻牛角尖，“又不是在招算法研究人员”。总之我发现，似乎只要是遇到了回答不出的问题，都会遭到许多人的反对。似乎社区中许多朋友都认为自己怀才不遇，“我来面试你，你也过不了”，都认为“千里马常有，而伯乐不常有”。有朋友问我，你面试别人时问些什么？我难以启齿啊，因为我问的&lt;a href="http://blog.zhaojie.me/2009/08/from-delegate-to-others.html"&gt;往往也是那些问题&lt;/a&gt;，可是一下子就被鄙视了。其实没有什么东西是简单的，有时候问问题并不是要得到结果，而是想从中看出一些细节来，例如一个人的思考方式和深度——我问某某和某某在写法上的区别，不是在搞“茴香豆”，是由其他目的。不知您是否相信，我在面试时会让对方谈一下最喜欢的体育运动的规则？&lt;/p&gt; &lt;p&gt;最近似乎认为我“无聊”的朋友也多了起来，认为我写的东西&lt;a href="http://blog.zhaojie.me/2009/08/recursive-lambda-expressions.html"&gt;没有什么实际意义&lt;/a&gt;，认为我写这些东西都是在“炫耀”。其实，我的文章都是源于实际工作中的想法，我认为对于实际工作是非常有帮助的。我不喜欢《XX编程三百例》式的文章，我认为那是“鱼”而不是“渔”。我不知道那些朋友希望我写什么样的文章才算有实际意义，但是我想最有意义的应该是提高您的能力，而正是如此，我认为您更应该关注我在成长过程中想到的这些问题。&lt;font color="#ff0000"&gt;轻易&lt;/font&gt;鄙视这个鄙视那个，就好像鄙视面试者那样，这可不好。鄙视一个东西不要紧，但关键是，你思考了没有？不要鄙视了半天，但是&lt;a href="http://www.lixiaolai.com/index.php/archives/7382.html"&gt;到最后也不知道发生了什么事，盲目地忙碌&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;思考很重要，例如&lt;a href="http://www.lixiaolai.com/index.php/archives/7367.html"&gt;换一种思考方式就会得到不同凡响的结果&lt;/a&gt;。至少，这也是锻炼逻辑的一种有效方式。想要验证自己想清楚了没，我认为最好的方式就是写一篇文章。如果您想明白了，一定能把它说清楚。反过来说也一样，如果您说不清楚，基本上就是因为您没有想明白。&lt;/p&gt; &lt;p&gt;说了好多，越扯越远，喝口水，回来继续吧。&lt;/p&gt; &lt;p&gt;PPT的正题自然是讲述微软在PHP平台上做出的努力和取得的成果，其中列举了之前与康盛创想合作进行的&lt;a href="http://cid-fa447a56f6b57df5.skydrive.live.com/self.aspx/PHP/LAMPvsWIMP.COMSENZ.ppt"&gt;性能评估结果&lt;/a&gt;，证明在Windows Server 2008 + IIS上运行PHP，从平均相应时间，每秒处理的请求数，以及数据吞吐量等多方便均显著优于Linux + Apache的托管方式。当然，这个结果也受到了一些质疑，例如为什么在Apache中使用了mod_php而不是在大多数情况下性能更好的FastCGI。关于这点我没有测试过，我不清楚。不过这并不要紧，我想说的是：Windows的性能真的不差。&lt;/p&gt;&lt;a href="http://img.zhaojie.me/blog/168980/o_iis7-perf.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/168980/r_iis7-perf.png" width="450"&gt;&lt;/a&gt;  &lt;p&gt;这也是幻灯片的截图之一。从中可以看出IIS 7的吞吐量完全可以达到静态文件请求20K，ASP.NET请求5K RPS（每秒请求数）的吞吐量。这是100%的事实，我可以担保，因为在我07年在微软的时候，曾经在自己工作用的普通 32位workstation上试验过，请求静态文件轻松超过了10K。算上IIS 7的性能增强，以及测试机的性能因素，得到上图的结果完全没有问题。至于ASP.NET动态请求的性能，5000多完全就是一个没有意义的数字了——我不是说它假，我只是说它没有意义。因为对于动态请求来说，纯粹比这种“空请求”的吞吐量，几乎没有任何实际参考价值。因为，我们有其它的性能瓶颈，根本达不到IIS本身的性能限制。&lt;/p&gt; &lt;p&gt;对于普通Web应用程序来说，如果在实现上没有大的问题，几乎不会让Web服务器（指IIS这种，不是指“机器”）成为性能瓶颈。性能瓶颈往往是在外部服务器调用，或者外部数据访问上。例如耗时的SQL查询一多，应用程序整体性能自然就下来了。因此，业界最为热烈的讨论往往是基于“缓存”和各种数据存储方式的，因为到目前为止它们都是最有可能成为性能瓶颈的。对于Web服务器本身性能的讨论也不是没有，只是相对就少很多了，要有，大部分也是基于静态请求的性能比较。&lt;/p&gt; &lt;p&gt;对于Web 2.0的网站来说，由于变化太多太快，几乎无法生成静态页。因此，这样的Web应用程序在一台机器上的吞吐量根本达不到5K，即时是1K也几乎做不到。在一台目前普通配置的服务器上，如果可以达到每秒100多的动态请求，基本上已经做的相当不错了，甚至50、60多也已经“够意思”了——像当年&lt;a href="http://www.douban.com"&gt;豆瓣&lt;/a&gt;鬼神般的5、600（如果我没有记错的话）几乎难以再现。不过100 RPS也已经是一个很了不起的数字了，如果按6小时的密集请求来算，您想一下这样一个动态站点的日访问量是多少呢？&lt;/p&gt; &lt;p&gt;当然，Windows不是没有性能问题，我只是说在IIS，Web开发等方面不会出现性能问题。有人说Windows的文件系统，也就是NTFS的性能很差，尤其是在处理零碎地文件时候。这我也有所耳闻，平时也有类似的感觉，但是没有经过这方面的实践，所以并无法说出准确的结论。不过真的性能差，也要去好好了解它，这样我们就可以设法避免一些薄弱的环节。随意举个例子，我们可以优化自己程序的存储方式，尽量读取连续的数据，让文件系统的性能问题可以缓解一下。SQL Server不也是在NTFS上构建出高效数据应用的吗？&lt;/p&gt; &lt;p&gt;使用Windows系统的另外的问题，也是被人提及很多次的问题，便是授权协议。Linux使用是不要钱的，而Windows是要花钱买的，这个自然应该算入成本。不过在我看来，其实Windows并不贵。为什么呢？您可以去Dell的网站上看一下一台预装了Windows Server的服务器的价格，一个Web Edition的Windows Server操作系统的价格大约是3000多元，这也就相当于一个普通程序员1个月的薪水而已，更何况操作系统可以算作是一次性投资。对于一个公司来说，每天开一次门就相当于几千几万的钱花出去了，购买一个Web Edition的Windows Server价钱根本算不了什么。至于开发成本，这是一个太“虚”的东西，暂时就不讨论了吧——我想，应该也没有什么理由可以有力证明使用ASP.NET会加大开发成本。&lt;/p&gt; &lt;p&gt;事实上，Web Edition已经足够部署ASP.NET应用程序了，Enterprise Edition自然要贵上十几倍甚至更多，但是您根本用不着。用盗版操作系统时带来的坏习惯“要用就用最好的”，在产品环境中一定要改一下。哦，对了，微软对于年收入低于100万的公司，或是非盈利机构都有非常大的折扣，甚至免费的策略（如&lt;a href="http://www.microsoft.com/Bizspark/Default.aspx"&gt;BizSpark计划&lt;/a&gt;）。您不应该错过。&lt;/p&gt; &lt;p&gt;因此我认为，在Windows平台上使用ASP.NET，是一个非常合适的Web应用程序开发/运行平台。即时是对于创业型小公司来说，我也会选择使用Windows + ASP.NET。&lt;/p&gt; &lt;p&gt;但是……微软平台上的授权价格并非总是个可以忽视的问题。因为SQL Server实在是太贵了，而免费的Express版本是不可以用于商业应用的（存疑，求证）。如果我们想要使用SQL Server，那么成本的确会哗哗地上升，尤其对于创业公司来说这是比不可忽视的支出（当然如果加入了BizSpark计划……）。因此可以这么说，运行ASP.NET的Windows很便宜，但是SQL Server，以及运行SQL Server的Windows会非常昂贵。因此，对于数据存储来说，我会选择Linux下的免费及开源的产品。而且我可以有更多的选择，无论是关系型数据库，键/值存储方式，还是现在慢慢再兴起的如&lt;a href="http://www.infoq.com/cn/news/2009/09/mongodb"&gt;MongoDB&lt;/a&gt;那样的无架构，文档型数据库，都可以合理组装使用。&lt;/p&gt; &lt;p&gt;所以我现在越来越推荐Windows + *nix的合作关系，至于在Windows和*nix项目的通讯问题上，其实丝毫不用担心。如今各个项目都是基于标准的通讯协议（如TCP/IP，甚至HTTP），使用通用的或自定义的格式进行数据交换。对于一个Linux下的数据库来说，它根本不会关心与它连接的是Windows还是Linux，也不会关心发起调用的是.NET还是Java，Python或Ruby平台。大家根据协议办事即可。&lt;/p&gt; &lt;p&gt;那么为什么业界总是认为Windows性能差呢？原因可能是因为微软的“声誉”不太好，而业界声音最响，最为活跃的大都是开源工作者或自由人士，他们自然会不遗余力地支持自己的环境——这很正常，优秀的程序员一定是有信仰的，我一直这么认为——当然也会产生一些不客观的FUD行为。还有便是，Windows进入服务器领域时间比较晚，而且在IIS 5那些年代，Windows在服务器领域的表现的确不怎么好。因此，那时候留下的负面印象自然也会产生不好的影响。但是微软是在发展的，微软牛人非常多，微软研究院的论文年年占据各大会议相当比例，而微软的产品的质量也已经足够了得了。如果继续用10年前的印象来判断如今的微软是不正确的——当年微软系统在服务器这块没有丝毫占用率（几乎都是Unix，Oracle的天下），现在已经占据中小公司80-90%，世界五百强50%的份额，这还不够说明问题吗？&lt;/p&gt; &lt;p&gt;关于这点，作为Windows平台下的程序员，我们应该有足够的自信，管别人怎么“怀疑”我们呢。&lt;/p&gt; &lt;p&gt;对了，还有一个可能的原因是由于微软的技术太容易入门，导致给人不够“牛逼”的感觉。这也是没有必要的，就好比说，一个国家的初等教育水平高，能证明它的高等教育水准不行吗？这只是定位的问题，当然这可能也是微软的策略——但不是微软的错，也不是微软技术没有价值的证据。当然，作为Windows平台上的程序员，提高自己的能力是没有错的。不光没有错，是一定必须要这么做的！&lt;/p&gt; &lt;p&gt;不过必须承认的是，Windows有个硬伤，就是您无法在上面捣鼓某些东西，例如换一个文件系统，改一改内核。如果你是热衷于这方面的Geek，那么自然不适合使用Windows。&lt;/p&gt;&lt;p&gt;最后，还是用幻灯片里的内容收尾吧。在这份幻灯片的末尾附有30多页的IIS Web应用程序配置最佳实践，是一个非常不错的参考，您一定要看一下。&lt;/p&gt; &lt;p&gt;就写到这里吧，虽然结尾有些仓促，但我真的已经很困了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/php-on-windows-and-more.html#comments</comments>
      <pubDate>Thu, 03 Sep 2009 18:17:00 GMT</pubDate>
      <lastBuildDate>Thu, 03 Sep 2009 18:17:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>通过表达式树构造URL时忽略部分参数</title>
      <link>http://blog.zhaojie.me/2009/09/ignore-some-arguments-when-constructing-url-via-expression-tree.html</link>
      <guid>http://blog.zhaojie.me/2009/09/ignore-some-arguments-when-constructing-url-via-expression-tree.html</guid>
      <description>&lt;p&gt;您的使用ASP.NET MVC的时候，一定遇到过使用Post接受数据的Action方法。例如：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HomeController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    [&lt;span style="color: #2b91af"&gt;AcceptVerbs&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;HttpVerbs&lt;/span&gt;.Post)]
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewResult &lt;/span&gt;List(&lt;span style="color: blue"&gt;string &lt;/span&gt;keywords, &lt;span style="color: blue"&gt;int &lt;/span&gt;page) { ... }
}
&lt;/pre&gt;
&lt;p&gt;于是乎，客户端只要像Home/List这样的URL中Post数据，这个Controller便可以从请求的Body中获得keyword和page的值。为了实现这个功能，我们必须在客户端准备一个form，把它的Action——也就是Post的目标URL写为Home/List。但是这个URL改怎么生成呢？按照传统的做法，我们会使用表达式树来构造这个URL：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Url.ActionEx&amp;lt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.List(&lt;span style="color: #a31515"&gt;"hello"&lt;/span&gt;, 3)) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;但是您会发现，上面这条语句最终生成的URL是：&lt;/p&gt;&lt;pre class="code"&gt;Home/List?keywords=hello&amp;amp;page=3&lt;/pre&gt;
&lt;p&gt;这是因为ASP.NET Routing在处理配置规则中没有标明的Route Values时，会将它们作为Query String拼接在URL后面。这也是可以预料到的，因为作为Form的URL，我们又如何明确指定一个参数的值呢？无论指定什么值都是不合适的，我们必须将它们忽略掉——或者说，我们需要找一种可以表示“任意”参数的方式。&lt;/p&gt;
&lt;p&gt;接下来的做法还是接着&lt;a href="http://blog.zhaojie.me/2009/09/make-expression-tree-based-url-construction-faster.html"&gt;上次的结果&lt;/a&gt;继续改进。您会发现这种做法有明显的&lt;a href="http://code.google.com/p/moq"&gt;Moq&lt;/a&gt;框架的影子，因为我们要使用这样的方式来表示参数的忽略：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Url.ActionEx&amp;lt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.List(&lt;span style="color: #2b91af"&gt;It&lt;/span&gt;.IsAny&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;(), &lt;span style="color: #2b91af"&gt;It&lt;/span&gt;.IsAny&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;())) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这需要我们准备一个简单的It.IsAny方法的“结构”：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;It
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;T IsAny&amp;lt;T&amp;gt;()
    {
        &lt;span style="color: blue"&gt;string &lt;/span&gt;message = 
            &lt;span style="color: #a31515"&gt;"Use for expression construction only, " &lt;/span&gt;+
            &lt;span style="color: #a31515"&gt;"please DO NOT execute directly"&lt;/span&gt;;
        &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InvalidOperationException&lt;/span&gt;(message);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这个方法不是用来直接调用的，它只是作为表达式树的一部分存在——这也再次说明，&lt;font color="#ff0000"&gt;表达式树的构造，并不意味着一定执行。表达式树是一种表示方式，用来说明我们的“意图”&lt;/font&gt;，仅此而已。&lt;/p&gt;
&lt;p&gt;在原先的代码中，我们是这样向一个RouteValueDictionary里填充数据的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static void &lt;/span&gt;AddParameterValues(&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;rvd, &lt;span style="color: #2b91af"&gt;MethodCallExpression &lt;/span&gt;call)
{
    &lt;span style="color: #2b91af"&gt;ParameterInfo&lt;/span&gt;[] parameters = call.Method.GetParameters();

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; parameters.Length; i++)
    {
        rvd.Add(parameters[i].Name, Eval(call.Arguments[i]));
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如今，call.Arguments[i]可能是一个It.Any&amp;lt;…&amp;gt;()表达式，它不能直接用于求值（Eval）。因此，我们要将代码修改为以下这样：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static void &lt;/span&gt;AddParameterValues(&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;rvd, &lt;span style="color: #2b91af"&gt;MethodCallExpression &lt;/span&gt;call)
{
    &lt;span style="color: #2b91af"&gt;ParameterInfo&lt;/span&gt;[] parameters = call.Method.GetParameters();

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; parameters.Length; i++)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;arg = call.Arguments[i];

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(!IsParameterShouldBeIgnored(arg))
        {
            rvd.Add(parameters[i].Name, Eval(arg));
        }
    }
}

&lt;span style="color: blue"&gt;private static bool &lt;/span&gt;IsParameterShouldBeIgnored(&lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;arg)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;call = arg &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodCallExpression&lt;/span&gt;;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(call == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return false&lt;/span&gt;;

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(call.Method.DeclaringType != &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;It&lt;/span&gt;)) &lt;span style="color: blue"&gt;return false&lt;/span&gt;;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(call.Method.Name != &lt;span style="color: #a31515"&gt;"IsAny"&lt;/span&gt;) &lt;span style="color: blue"&gt;return false&lt;/span&gt;;

    &lt;span style="color: blue"&gt;return true&lt;/span&gt;;
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;我们在求值之前，需要判断这个表达式是否是It.IsAny方法的调用。如果不是，才将其加入RouteValueDictionary中。就这样，修改结束了，总共也就10多行代码的改动而已。&lt;/p&gt;
&lt;p&gt;为了检验我们的成果，最好的方法进行单元测试。首先，我们准备一个测试用的Controller类和Action方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TestController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Index(&lt;span style="color: blue"&gt;string &lt;/span&gt;s, &lt;span style="color: blue"&gt;int &lt;/span&gt;i) { &lt;span style="color: blue"&gt;return null&lt;/span&gt;; }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;然后检查在普通情况下，所有的Route Value都被正常捕获到：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;Fact&lt;/span&gt;]
&lt;span style="color: blue"&gt;public void &lt;/span&gt;Get_Route_Values_With_Arguments()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;routeValues = &lt;span style="color: #2b91af"&gt;RouteExpression&lt;/span&gt;.GetRouteValues&amp;lt;&lt;span style="color: #2b91af"&gt;TestController&lt;/span&gt;&amp;gt;(
        c =&amp;gt; c.Index(&lt;span style="color: #a31515"&gt;"abc"&lt;/span&gt;, 5));

    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Test"&lt;/span&gt;, routeValues[&lt;span style="color: #a31515"&gt;"controller"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Index"&lt;/span&gt;, routeValues[&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"abc"&lt;/span&gt;, routeValues[&lt;span style="color: #a31515"&gt;"s"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(5, routeValues[&lt;span style="color: #a31515"&gt;"i"&lt;/span&gt;]);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;以及，如果我们想要忽略到一个参数时，它就不会出现在RouteValueDictionary中：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;Fact&lt;/span&gt;]
&lt;span style="color: blue"&gt;public void &lt;/span&gt;Get_Route_Values_With_Ignored_Arguements()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;routeValues = &lt;span style="color: #2b91af"&gt;RouteExpression&lt;/span&gt;.GetRouteValues&amp;lt;&lt;span style="color: #2b91af"&gt;TestController&lt;/span&gt;&amp;gt;(
        c =&amp;gt; c.Index(&lt;span style="color: #a31515"&gt;"abc"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;It&lt;/span&gt;.IsAny&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;()));

    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Test"&lt;/span&gt;, routeValues[&lt;span style="color: #a31515"&gt;"controller"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Index"&lt;/span&gt;, routeValues[&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"abc"&lt;/span&gt;, routeValues[&lt;span style="color: #a31515"&gt;"s"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.False(routeValues.ContainsKey(&lt;span style="color: #a31515"&gt;"i"&lt;/span&gt;), &lt;span style="color: #a31515"&gt;"This arg should be ignored!"&lt;/span&gt;);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;就这样，我们通过表达式树生成URL的功能又前进了一小步。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/ignore-some-arguments-when-constructing-url-via-expression-tree.html#comments</comments>
      <pubDate>Thu, 03 Sep 2009 03:37:00 GMT</pubDate>
      <lastBuildDate>Thu, 03 Sep 2009 03:37:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>优化通过表达式树构造URL的性能</title>
      <link>http://blog.zhaojie.me/2009/09/make-expression-tree-based-url-construction-faster.html</link>
      <guid>http://blog.zhaojie.me/2009/09/make-expression-tree-based-url-construction-faster.html</guid>
      <description>&lt;p&gt;我们继续改进通过表达式树构造URL的方式。在&lt;a href="http://blog.zhaojie.me/2009/09/get-action-name-from-expression-tree-by-actionnameattribute.html"&gt;上一篇文章&lt;/a&gt;中，辅助方法可以正确地识别了ActionNameAttribute，而这次改进的则是性能方面的问题。首先还是来看一下用于从表达式树获取RouteValueDictionary的方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;GetRouteValues&amp;lt;TController&amp;gt;(
    &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action)
    &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;var &lt;/span&gt;rvd = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;();
    rvd.Add(&lt;span style="color: #a31515"&gt;"controller"&lt;/span&gt;, controllerName);
    rvd.Add(&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;, GetActionName(call.Method));

    AddParameterValues(rvd, call);
    &lt;span style="color: blue"&gt;return &lt;/span&gt;rvd;
}

&lt;span style="color: blue"&gt;private static void &lt;/span&gt;AddParameterValues(
    &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;rvd, &lt;span style="color: #2b91af"&gt;MethodCallExpression &lt;/span&gt;call)
{
    &lt;span style="color: #2b91af"&gt;ParameterInfo&lt;/span&gt;[] parameters = call.Method.GetParameters();

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; parameters.Length; i++)
    {
        &lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;arg = call.Arguments[i];

        &lt;span style="color: blue"&gt;object &lt;/span&gt;value = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
        &lt;span style="color: #2b91af"&gt;ConstantExpression &lt;/span&gt;ce = arg &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ConstantExpression&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(ce != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: green"&gt;// If argument is a constant expression, just get the value
            &lt;/span&gt;value = ce.Value;
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            &lt;span style="color: green"&gt;// Otherwise, convert the argument subexpression to type object,
            // make a lambda out of it, compile it, and invoke it to get the value
            &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;object&lt;/span&gt;&amp;gt;&amp;gt; lambdaExpression = 
                &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Lambda&amp;lt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;object&lt;/span&gt;&amp;gt;&amp;gt;(
                    &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Convert(arg, &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;object&lt;/span&gt;)));
            &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;object&lt;/span&gt;&amp;gt; func = lambdaExpression.Compile();
            value = func();
        }

        rvd.Add(parameters[i].Name, value);&lt;span style="color: green"&gt;
    &lt;/span&gt;}
}&lt;/pre&gt;
&lt;p&gt;这次我们关注的是第二个方法AddParameterValues。这个方法的目的是从表示action调用的表达式树（它是一个MethodCallExpression）中提取所有的参数——也是一个一个表达式树，并将它们表示的“值”填充到RouteValueDictionary中。这段代码使用了传统计算一个表达式树的方式：“使用LambdaExpression对象封装，再编译，最后执行”来获得一个Expression对象的值。但是，Compile方法的性能是比较低下的，如果密集地执行会对性能产生一定影响。&lt;/p&gt;
&lt;p&gt;那么，您认为在ASP.NET MVC的场景中，Compile方法的执行频率如何呢？请想象一下这样的一个场景：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Article List&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
 
&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;article &lt;span style="color: blue"&gt;in &lt;/span&gt;Model.Articles) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;div&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.ActionLink&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;(
            c =&amp;gt; c.Detail(article.ArticleID, 1), article.Title) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    
    &lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;page = 2; page &amp;lt;= article.MaxPage; page++) { &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;small&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Html.ActionLink&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;(
            c =&amp;gt; c.Detail(article.ArticleID, page), page.ToString()) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;    &lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;small&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;        
&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;div&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt; } &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;上述代码的作用，是在文章列表页上生成一系列指向文章详细页的链接。那么在上面的代码中，将会出现多少次表达式树的计算呢？&lt;/p&gt;&lt;pre class="code"&gt;Html.ActionLink&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;(
    c =&amp;gt; c.Detail(&lt;span style="color: red"&gt;article.ArticleID&lt;/span&gt;, 1), article.Title)
 
Html.ActionLink&amp;lt;&lt;span style="color: #2b91af"&gt;ArticleController&lt;/span&gt;&amp;gt;(
    c =&amp;gt; c.Detail(&lt;span style="color: red"&gt;article.ArticleID&lt;/span&gt;, &lt;span style="color: red"&gt;page&lt;/span&gt;), article.Title)&lt;/pre&gt;
&lt;p&gt;可以看出，每篇文章将进行(2 * MaxPage – 1)次计算，对于一个拥有数十篇文章的列表页，计算次数很可能逾百次。此外，再加上页面上的各种其它元素，如分类列表，Tag Cloud等等，每生成一张略为复杂的页面便会造成数百次的表达式树计算。从&lt;a href="http://codeclimber.net.nz/archive/2009/04/17/how-to-improve-the-performances-of-asp.net-mvc-web-applications.aspx"&gt;Simone Chiaretta的性能测试&lt;/a&gt;上来看，使用表达式树生成链接所花时间，大约为直接使用字符串的30倍。而根据我的本地测试结果，在一台P4 2.0 GHz的服务器上，单线程连续计算一万个简单的四则运算表达式便要花费超过1秒钟时间。这并非是一个可以忽略的性能开销，引入一种性能更好的表达式树计算方法势在必行。&lt;/p&gt;
&lt;p&gt;在《&lt;a href="http://blog.zhaojie.me/2009/07/expression-tree-fast-evaluation.html"&gt;快速计算表达式树&lt;/a&gt;》一文中，我已经对这一话题进行了非常深入的探讨（甚至还可以算上&lt;a href="http://blog.zhaojie.me/2009/03/expression-cache-1.html"&gt;表达式树的缓存&lt;/a&gt;这一系列文章），并最终给出了一个可以直接复用的解决方案：&lt;a href="http://code.msdn.microsoft.com/FastLambda"&gt;FastLambda&lt;/a&gt;。因此，我们在这里只需要使用如下的方式来改进表达式树的“计算”方式即可：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static void &lt;/span&gt;AddParameterValues(
    &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;rvd, &lt;span style="color: #2b91af"&gt;MethodCallExpression &lt;/span&gt;call)
{
    &lt;span style="color: #2b91af"&gt;ParameterInfo&lt;/span&gt;[] parameters = call.Method.GetParameters();

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; parameters.Length; i++)
    {
        rvd.Add(parameters[i].Name, Eval(call.Arguments[i]));
    }
}

&lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FastEvaluator &lt;/span&gt;s_parameterEvaluator = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FastEvaluator&lt;/span&gt;();

&lt;span style="color: blue"&gt;private static object &lt;/span&gt;Eval(&lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;exp)
{
    &lt;span style="color: #2b91af"&gt;ConstantExpression &lt;/span&gt;ce = exp &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ConstantExpression&lt;/span&gt;;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(ce != &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return &lt;/span&gt;ce.Value;

    &lt;span style="color: blue"&gt;return &lt;/span&gt;s_parameterEvaluator.Eval(exp);
}
&lt;/pre&gt;
&lt;p&gt;FastEvaluator是FastLambda项目中提供的一个类，它会对输入的表达式树进行分析，并根据其结构缓存编译后的结果，于是下次输入结构相同的表达式时，便可以快速的计算出结果，省去了编译的开销。例如在上面的例子中，无论article指向的是什么对象，无论page的值是多少，article.ArticleID或page本身的结构永远是不变的。因此，对于刚才的例子，无论访问了多少次页面，作了多少次循环，都只会进行两次编译。此外，由于FastEvaluator已经实现为一个线程安全的组件，因此这里我们只须直接使用即可，无需进行太多考虑。&lt;/p&gt;
&lt;p&gt;那么，这么做会带来多少性能提升呢？请看如下的示意图：&lt;/p&gt;&lt;a title="perf test" href="http://www.infoq.com/resource/articles/dot-net-expression-tree/zh/resources/image4.png" target="_blank"&gt;&lt;img alt="perf test" src="http://www.infoq.com/resource/articles/dot-net-expression-tree/zh/resources/image4.png" width="450"&gt;&lt;/a&gt; 
&lt;p&gt;这幅图表达的是在计算拥有2n + 1个节点的表达式树时，普通的做法（红线）与FastEvaluator（紫线）的性能差距。根据我的个人经验，项目中所计算的表达式树的节点数量一般都在10个以内。如图所示，在这个数据范围内，FastEvaluator的计算耗时仅为传统方法的1/20。对于刚才的示例来说，节点数量为3或5，则表示n为1或3，在这种节点数量极少的情况下，性能的差距甚至可以达到50至100倍。&lt;/p&gt;
&lt;p&gt;当然，一个应用程序的性能不是由这么简单的一个细节决定的，但是现在我们为一个较为频繁的操作进行了非常明显的性能优化。更重要的是，我们并没有因此损失任何易用性。因此，如果您在ASP.NET MVC中通过表达式树构造URL的话，我建议您使用现在的方案进行改进。&lt;/p&gt;
&lt;p&gt;至于FastEvaluator组件的原理及实现等细节内容，还请参考我写的《&lt;a href="http://blog.zhaojie.me/2009/07/expression-tree-fast-evaluation.html"&gt;快速计算表达式树&lt;/a&gt;》一文。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/make-expression-tree-based-url-construction-faster.html#comments</comments>
      <pubDate>Tue, 01 Sep 2009 11:29:00 GMT</pubDate>
      <lastBuildDate>Tue, 01 Sep 2009 11:29:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>通过表达式树构建URL时正确识别ActionNameAttribute</title>
      <link>http://blog.zhaojie.me/2009/09/get-action-name-from-expression-tree-by-actionnameattribute.html</link>
      <guid>http://blog.zhaojie.me/2009/09/get-action-name-from-expression-tree-by-actionnameattribute.html</guid>
      <description>&lt;p&gt;在MvcFutures项目中提供了一个辅助方法，可以将一个表达式树对象转化成一个RouteValueDictionary集合。只可惜，这个辅助方法的毛病比较多。例如，它直接把方法名作为action的值，而忽略了其上标记的ActionNameAttribute。这导致了某个被“改名”的Action方法一旦用在了表达式树中，最终得到的URL便是错误的。例如有一个Action方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HomeController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    [&lt;span style="color: #2b91af"&gt;ActionName&lt;font color="#000000"&gt;(&lt;span style="color: #a31515"&gt;"Default"&lt;/span&gt;)&lt;/font&gt;&lt;/span&gt;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ViewResult &lt;/span&gt;Index()
    {
        ...
    }
}&lt;/pre&gt;
&lt;p&gt;如果您使用这样的方式来生成URL（ActionEx方法的实现请参考《&lt;a href="http://blog.zhaojie.me/2009/08/build-url-from-expression-tree-for-domainroute.html"&gt;使用表达式树构建DomainRoute的URL&lt;/a&gt;》）：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ActionEx&amp;lt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Index()) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;"&amp;gt;&lt;/span&gt;Home&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;则最终得到的代码是：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="/Home/Index&lt;/span&gt;&lt;span style="color: blue"&gt;"&amp;gt;&lt;/span&gt;Home&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;而我们需要的结果应该是：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="/Home/Default&lt;/span&gt;&lt;span style="color: blue"&gt;"&amp;gt;&lt;/span&gt;Home&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;正是因为这个原因（以及一些其他因素），许多朋友放弃使用强类型的方式构造URL。不过，如果您继续看下去，就会发现这个功能其实非常简单。只要做稍微一点点修改就可以了。不过现在，让我们来观察MvcFutures是如何实现这部分功能的。我已经把相关的代码复制到自己的RouteExpression类中：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteExpression
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;GetRouteValues&amp;lt;TController&amp;gt;(
        &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action)
        &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(action == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArgumentNullException&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;);
        }

        &lt;span style="color: #2b91af"&gt;MethodCallExpression &lt;/span&gt;call = action.Body &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodCallExpression&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(call == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArgumentException&lt;/span&gt;(
                &lt;span style="color: #a31515"&gt;"The action must be a method call."&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;);
        }

        &lt;span style="color: blue"&gt;string &lt;/span&gt;controllerName = &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(TController).Name;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(!controllerName.EndsWith(&lt;span style="color: #a31515"&gt;"Controller"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;StringComparison&lt;/span&gt;.OrdinalIgnoreCase))
        {
            &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArgumentException&lt;/span&gt;(
                &lt;span style="color: #a31515"&gt;"The controller name must end with 'Controller'."&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;);
        }

        controllerName = controllerName.Substring(
            0, controllerName.Length - &lt;span style="color: #a31515"&gt;"Controller"&lt;/span&gt;.Length);
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(controllerName.Length == 0)
        {
            &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArgumentException&lt;/span&gt;(
                &lt;span style="color: #a31515"&gt;"Cannot route to the Controller class"&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;);
        }

        &lt;span style="color: blue"&gt;var &lt;/span&gt;rvd = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;();
        rvd.Add(&lt;span style="color: #a31515"&gt;"controller"&lt;/span&gt;, controllerName);&lt;span style="color: green"&gt;
        &lt;/span&gt;rvd.Add(&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;, call.Method.Name);

        AddParameterValuesFromExpressionToDictionary(rvd, call);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;rvd;
    }

    &lt;span style="color: blue"&gt;private static void &lt;/span&gt;AddParameterValues(
        &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;rvd, &lt;span style="color: #2b91af"&gt;MethodCallExpression &lt;/span&gt;call)
    {
        ...
    }
}
&lt;/pre&gt;
&lt;p&gt;这段代码大部分内容都是进行参数校验，一旦出现以下情况之一，便会抛出异常：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表达式树为null。 
&lt;li&gt;表达式树不是一个MethodCallExpression（应该是一个Action方法的调用） 
&lt;li&gt;如果控制器类型的名称不以Controller结尾（破坏了约定） 
&lt;li&gt;如果控制器类型的名称就是Controller&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;经过校验之后，这个方法根据控制器类型的名称计算出controller（HomeController =&amp;gt; Home），再把所调方法的名称作为action（Index() =&amp;gt; Index）。最后，再使用AddParameterValues方法获得参数，并填充RouteValueDictionary（关于这点我们下次再来讨论）。&lt;/p&gt;
&lt;p&gt;不过，问题就出现在从Action方法的MethodInfo“直接获取”名称这个步骤上。这个MethodInfo可能还标记着ActionNameAttribute呢，它的Name属性可不是action的名称。为此，我们必须多做这么一步：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReaderWriterLockSlim &lt;/span&gt;s_rwLock = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReaderWriterLockSlim&lt;/span&gt;();
&lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MethodInfo&lt;/span&gt;, &lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; s_actionNames = 
    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MethodInfo&lt;/span&gt;, &lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;();

&lt;span style="color: blue"&gt;private static string &lt;/span&gt;GetActionName(&lt;span style="color: #2b91af"&gt;MethodInfo &lt;/span&gt;methodInfo)
{
    &lt;span style="color: blue"&gt;string &lt;/span&gt;actionName = &lt;span style="color: blue"&gt;null&lt;/span&gt;;

    s_rwLock.EnterReadLock();
    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(s_actionNames.TryGetValue(methodInfo, &lt;span style="color: blue"&gt;out &lt;/span&gt;actionName))
        {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;actionName;
        }
    }
    &lt;span style="color: blue"&gt;finally
    &lt;/span&gt;{
        s_rwLock.ExitReadLock();
    }

    &lt;span style="color: blue"&gt;var &lt;/span&gt;attribute = (&lt;span style="color: #2b91af"&gt;ActionNameAttribute&lt;/span&gt;)methodInfo
        .GetCustomAttributes(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ActionNameAttribute&lt;/span&gt;), &lt;span style="color: blue"&gt;false&lt;/span&gt;)
        .SingleOrDefault();
    actionName = attribute == &lt;span style="color: blue"&gt;null &lt;/span&gt;? methodInfo.Name : attribute.Name;

    s_rwLock.EnterWriteLock();
    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        s_actionNames[methodInfo] = actionName;
    }
    &lt;span style="color: blue"&gt;finally
    &lt;/span&gt;{
        s_rwLock.ExitWriteLock();
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;actionName;
}
&lt;/pre&gt;
&lt;p&gt;在GetActionName方法的中部则是获得action名称的代码。它会根据methodInfo上的ActionNameAttribute标记情况来确定。如果标记了ActionNameAttribute，则使用Attribute的Name属性作为action名称，否则就使用MethodInfo对象的Name属性。获得action名称之后，我们会将其保存在一个字典中。至于使用ReaderWriterLockSlim来控制并发读写的方式已经成为了标准，您甚至可以将其封装为一个组件避免重复编写相同的代码。&lt;/p&gt;
&lt;p&gt;最后，我们把原来GetRouteValues方法中的一行代码加以替换即可：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;GetRouteValues&amp;lt;TController&amp;gt;(
    &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action)
    &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    ...
    &lt;strike&gt;rvd.Add(&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;, call.Method.Name);&lt;/strike&gt;
    rvd.Add(&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;, GetActionName(call.Method));
    ...
}
&lt;/pre&gt;
&lt;p&gt;ASP.NET MVC给了我们充分的自由度定制需要的组件。从中我们也可以了解到如何在项目中编写合适的API。其实很多东西只要多走一步就会美好很多，例如这个例子，需要花费您超过半小时的时间吗？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/get-action-name-from-expression-tree-by-actionnameattribute.html#comments</comments>
      <pubDate>Tue, 01 Sep 2009 06:25:00 GMT</pubDate>
      <lastBuildDate>Tue, 01 Sep 2009 06:25:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>使用表达式树构建DomainRoute的URL</title>
      <link>http://blog.zhaojie.me/2009/08/build-url-from-expression-tree-for-domainroute.html</link>
      <guid>http://blog.zhaojie.me/2009/08/build-url-from-expression-tree-for-domainroute.html</guid>
      <description>&lt;p&gt;由于DomainRoute支持针对URL域名的捕获和构造，这有些破坏了ASP.NET Routing所制定的“协议”（ASP.NET Routing只支持Path），因此在&lt;a href="http://blog.zhaojie.me/2009/08/url-building-method-for-domainroute.html"&gt;上一篇文章&lt;/a&gt;中，我们需要自己构造一个辅助方法来获得一个“包含域名”的URL。不过根据&lt;a href="http://blog.zhaojie.me/2009/02/mvc-use-strong-type-everywhere.html"&gt;尽可能强类型&lt;/a&gt;的原则，我们应该使用的是类似于MvcFutures中定义的基于表达式树的辅助方法。由于MvcFutures已经提供了非常充足的辅助功能，因此这其实并不需要耗费我们多少代价。&lt;/p&gt; &lt;p&gt;上一次我们编写了这样的辅助方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ActionEx(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: blue"&gt;string &lt;/span&gt;action, &lt;span style="color: blue"&gt;object &lt;/span&gt;routeValues)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;values = routeValues == &lt;span style="color: blue"&gt;null &lt;/span&gt;?
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;() : 
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(routeValues);
    values.Add(&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;, action);
    values.Add(&lt;span style="color: #a31515"&gt;"controller"&lt;/span&gt;, helper.RequestContext.RouteData.Values[&lt;span style="color: #a31515"&gt;"controller"&lt;/span&gt;]);

    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.GetRouteUrl(values);
}

&lt;span style="color: blue"&gt;private static string &lt;/span&gt;GetRouteUrl(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;pathData = helper.RouteCollection.GetPath(helper.RequestContext, values);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;url = pathData.VirtualPath;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;IsAbsolute(url) ? url : &lt;span style="color: #a31515"&gt;"/" &lt;/span&gt;+ url;
}&lt;/pre&gt;
&lt;p&gt;但是根据我们的需要，我们应该设法编写如下的代码：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="&lt;/span&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;= Url.ActionEx&amp;lt;&lt;span style="color: #2b91af"&gt;HomeController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.Index()) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;"&amp;gt;&lt;/span&gt;Home&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;a&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: #ffee62"&gt;
&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;那么，这个ActionEx方法的签名应该是什么样的呢？从一个方法的调用方式上得出它的签名也是构造良好API的必要能力。在这里，我们可以把ActionEx方法的签名定成：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ActionEx&amp;lt;TController&amp;gt;(
    &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action)
&lt;span style="color: blue"&gt;    where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ActionEx(helper, action, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
}

&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ActionEx&amp;lt;TController&amp;gt;(
    &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action, &lt;span style="color: blue"&gt;object &lt;/span&gt;routeValues)
    &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller&lt;/span&gt;
{
    ...
}
&lt;/pre&gt;
&lt;p&gt;与原来的ActionEx方法不同，原来的ActionEx方法仅仅携带了一个字符串，而现在的action是一个表达式树，其中包含了大量的信息：调用&lt;font color="#ff0000"&gt;哪个Controller&lt;/font&gt;中的&lt;font color="#ff0000"&gt;哪个Action&lt;/font&gt;方法，并使用了&lt;font color="#ff0000"&gt;哪些参数&lt;/font&gt;。例如，以下两种用法，最终生成的URL是相同的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="background: #ffee62"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Url.ActionEx(&lt;span style="color: #a31515"&gt;"List"&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Post"&lt;/span&gt;, id = 5, area = &lt;span style="color: #a31515"&gt;"blogs" &lt;/span&gt;}) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;= &lt;/span&gt;Url.ActionEx&amp;lt;&lt;span style="color: #2b91af"&gt;PostController&lt;/span&gt;&amp;gt;(c =&amp;gt; c.List(5), &lt;span style="color: blue"&gt;new &lt;/span&gt;{ area = &lt;span style="color: #a31515"&gt;"blogs" &lt;/span&gt;}) &lt;span style="background: #ffee62"&gt;%&amp;gt;
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这样，您应该就可以看出两种情况下，各种必要的数据是如何传递进来的。因此，新增的ActionEx方法应该是这样：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;ActionEx&amp;lt;TController&amp;gt;(
    &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UrlHelper &lt;/span&gt;helper, &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action, &lt;span style="color: blue"&gt;object &lt;/span&gt;routeValues)
    &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;values = GetRouteValuesFromExpression(action);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(routeValues != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        values.CopyFrom(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(routeValues));
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;helper.GetRouteUrl(values);
}

&lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;GetRouteValuesFromExpression&amp;lt;TController&amp;gt;(
    &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action) &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    ...
}&lt;/pre&gt;
&lt;p&gt;您应该可以料想得到，这里的关键是如何从表达式树中提取数据（即GetRouteValuesFromExpression方法的实现）。如果您不了解表达树，那么这方面可能略有难度。幸运的是，其实MvcFutures项目已经帮我们自带了充足的辅助功能：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;GetRouteValuesFromExpression&amp;lt;TController&amp;gt;(
    &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;TController&amp;gt;&amp;gt; action) &lt;span style="color: blue"&gt;where &lt;/span&gt;TController : &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ExpressionHelper&lt;/span&gt;.GetRouteValuesFromExpression(action);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;就这样，结束了。当然，原有MvcFutures中缺少或不足的功能也直接带入了我们的项目中，下次我们便要改进这些功能。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/build-url-from-expression-tree-for-domainroute.html#comments</comments>
      <pubDate>Mon, 31 Aug 2009 07:48:00 GMT</pubDate>
      <lastBuildDate>Mon, 31 Aug 2009 07:48:00 GMT</lastBuildDate>
    </item>
  </channel>
</rss>
