<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>项目扩展 - 老赵点滴 - 追求编程之美</title>
    <link>http://blog.zhaojie.me/extension/</link>
    <description>先做人，再做技术人员，最后做程序员。打造国内最好的.NET技术博客。</description>
    <language>zh-cn</language>
    <managingEditor>jeffz@live.com (老赵)</managingEditor>
    <webMaster>jeffz@live.com (老赵)</webMaster>
    <pubDate>Wed, 27 Apr 2011 15:20:17 GMT</pubDate>
    <lastBuildDate>Wed, 28 Dec 2011 04:45:46 GMT</lastBuildDate>
    <ttl>60</ttl>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <title>增强Jscex目标代码可调试性：已包含输入代码</title>
      <link>http://blog.zhaojie.me/2012/02/jscex-debug-improved-target-code-contains-source-now.html</link>
      <guid>http://blog.zhaojie.me/2012/02/jscex-debug-improved-target-code-contains-source-now.html</guid>
      <description>&lt;p&gt;我们先来看一段Jscex生成的目标代码，您能看出其输入代码是什么样的吗？&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-before.png"&gt;&lt;img alt="target code without source" src="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-before.png" width="450" /&gt; &lt;/a&gt;

&lt;p&gt;其实这段代码的原始输入是&lt;a href="files.zhaojie.me/jscex/samples/async/sorting-animations.html?quick"&gt;排序算法动画&lt;/a&gt;中“快速排序”的Partition方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(array, begin, end) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;i = begin;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;j = end;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;pivot = array[Math.floor((begin + end) / 2)];

    &lt;span style="color: blue"&gt;while &lt;/span&gt;(i &amp;lt;= j) {
        &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;r = $await(compareAsync(array[i], pivot));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;lt; 0) { i++; } &lt;span style="color: blue"&gt;else &lt;/span&gt;{ &lt;span style="color: blue"&gt;break&lt;/span&gt;; }
        }

        &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;r = $await(compareAsync(array[j], pivot));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) { j--; } &lt;span style="color: blue"&gt;else &lt;/span&gt;{ &lt;span style="color: blue"&gt;break&lt;/span&gt;; }
        }

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(i &amp;lt;= j) {
            $await(swapAsync(array, i, j));
            i++;
            j--;
        }
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;i;
}&lt;/pre&gt;

&lt;p&gt;是不是显得比较繁琐？但其实Jscex生成代码的模式很简单，您只要忽视Combine，Delay等辅助方法，剩下的代码几乎都跟原始代码一一对应了。不过用肉眼来识别原始代码多少还是需要些脑力，于是即将发布的0.6.0版Jscex编译器便直接在目标代码的左侧生成对应的原始代码了：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition.png"&gt;&lt;img alt="target code with source" src="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition.png" width="450" /&gt; &lt;/a&gt;

&lt;p&gt;当然左侧原始代码并非与您输入的完全一致，它毕竟也是经过解析和重新生成的结果，并需要与目标代码逐行对应，多少会有些格式上的改变，但此时您想设置断点也好，调试也罢，只要把注意力更多放在左侧的输入代码上，而不用肉眼去识别相对复杂的Monad代码了。&lt;/p&gt;

&lt;p&gt;当然，复杂和简单总是相对的，Jscex的目标代码虽然比输入要复杂许多，但至少还能辨别出原始代码，也能通过肉眼识别给转化回去。如果您观察Jscex的同类产品&lt;a href="https://github.com/Sage/streamlinejs"&gt;streamline.js&lt;/a&gt;的话，就会发现它的目标代码真是如天书一般：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-streamline.png"&gt;&lt;img alt="streamlined" src="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-streamline.png" width="450" /&gt; &lt;/a&gt;

&lt;p&gt;这是因为streamline.js则使用了传统的状态机展开，Jscex使用的是Monad生成方式，其主要目的之一便是考虑到目标代码的可读性。Jscex是基于成熟理论，并经过精心设计的类库，并没有很多人想象中的那么多问题。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2012/02/jscex-debug-improved-target-code-contains-source-now.html#comments</comments>
      <pubDate>Thu, 16 Feb 2012 15:30:03 GMT</pubDate>
      <lastBuildDate>Thu, 16 Feb 2012 15:32:01 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>Node.js中相同模块是否会被加载多次？</title>
      <link>http://blog.zhaojie.me/2011/12/same-node-module-load-multiple-times.html</link>
      <guid>http://blog.zhaojie.me/2011/12/same-node-module-load-multiple-times.html</guid>
      <description>&lt;p&gt;JavaScript的包管理一直是个软肋，我很难想象，连这一基础功能都没有做好的语言，现在居然会如此流行。在我看来，其实JavaScript流行的最主要元素还是把持了浏览器，而Web应用在这几年掀起了一阵腥风血雨。任意一门语言，只要能像JavaScript般被标准采纳，被所有浏览器接受，它都能“成功”，真是所谓宿命。&lt;/p&gt;

&lt;p&gt;当然，既然它流行了，既然人们想要用它做大事了，就要开始为它制定一些模块的约定。这几天我为&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;实现&lt;a href="https://github.com/amdjs/amdjs-api/wiki/AMD"&gt;AMD规范&lt;/a&gt;的时候，便深刻体会到模块化的优势。Node.js也使用了&lt;a href="http://wiki.commonjs.org/"&gt;CommonJS&lt;/a&gt;模块机制，最近&lt;a href="http://www.infoq.com/cn/articles/nodejs-module-mechanism"&gt;在InfoQ上有一篇文章讨论了这方面的问题&lt;/a&gt;。这篇文章提到Node.js在载入模块时，如果之前该模块已经加载过则不会有重复开销，因为模块加载有缓存机制。这篇文章是我初审的，当时也正好在思考Jscex在Node.js使用时的模块化问题，但研究了它的规则之后，我发现在某些情况下还是可能加载多次。现在我们便来分析这个问题。&lt;/p&gt;

&lt;p&gt;当我们使用require方法加载另一模块的时候，Node.js会去查询一系列的目录。我们可以从module.paths中得到这些路径，例如：&lt;/p&gt;

&lt;pre class="code"&gt;[ &lt;span style="color: maroon"&gt;'/Users/jeffz/Projects/node-test/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/Users/jeffz/Projects/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/Users/jeffz/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/Users/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/node_modules'&lt;/span&gt;]&lt;/pre&gt;

&lt;p&gt;这里是我在运行/User/jeffz/Projects/node-test目录下一个模块时得到的结果。可见，Node.js会从当前模块所在目录的node_modules（这里怎么不遵守Unix习惯，而使用了下划线呢？）开始找起，如果没找到再会去找上级目录的node_modules，直到根目录为止。当然，实际情况下还会有NODE_PATH环境变量标识的目录等等。当模块的位置确定之后，Node.js便会查看这个位置的模块是否已经被加载，如果已加载，则直接返回。&lt;/p&gt;

&lt;p&gt;简单地说，Node.js是根据模块所在路径来缓存模块的。&lt;/p&gt;

&lt;p&gt;这么看来，“相同模块是否会被加载多次”这个问题，其实就演变成了“相同模块是否会出现在不同路径里”。简单想来这似乎不太可能，因为如果我们要使用某个模块的时候，它的位置总是确定的。例如，使用npm安装的模块，总是会出现在当前目录的node_modules里，加载时总是会找到相同的路径。那么，在“间接”依赖相同模块的情况下呢？&lt;/p&gt;

&lt;p&gt;例如我们想要使用Express框架，于是使用npm来安装，便会得到：&lt;/p&gt;

&lt;pre class="code"&gt;$ npm install express
express@2.5.2 ./node_modules/express 
├── mkdirp@0.0.7
├── qs@0.4.0
├── mime@1.2.4
└── connect@1.8.5&lt;/pre&gt;

&lt;p&gt;可见，Express依赖了其他一些模块，它们都存放在express模块自己的目录里面，例如./node_modules/express/node_modules/mime。好，假如我们项目自身也要使用mime项目，我们自然也可以使用npm来安装：&lt;/p&gt;

&lt;pre class="code"&gt;$ npm install mime
mime@1.2.4 ./node_modules/mime &lt;/pre&gt;

&lt;p&gt;于是我们最终得到的是这样的结构：&lt;/p&gt;

&lt;pre class="code"&gt;./node_modules
├── &lt;span style="color: red"&gt;&lt;strong&gt;mime&lt;/strong&gt;&lt;/span&gt;
└── express
    └── node_modules
        ├── mkdirp
        ├── qs
        ├── &lt;span style="color: red"&gt;&lt;strong&gt;mime&lt;/strong&gt;&lt;/span&gt;
        └── connect&lt;/pre&gt;

&lt;p&gt;请注意，这里的mime模块便出现在两个位置上，它们名称版本都一致，完全是一个模块。那么试想，如果我们在自己的代码里加载的mime模块，以及express内部加载的mime模块是同一个吗？显然不是，可见，在这里相同的模块被重复加载了两次，产生了两个模块“实例”。&lt;/p&gt;

&lt;p&gt;这种重复加载在一般情况下不会有太大问题，最多内存占用大一点而已，不会影响程序的正确性。但是，我们也可以轻易设想到一些意外的情况。例如，在Jscex中，每个Task对象我都会给定一个ID，不断增长。要实现这点我们需要维护一个“种子”，全局唯一。之前这个种子定义在闭包内部，但由于Jscex模块会被加载多次，这样从不同模块“实例”生成的Task对象，它们的ID便有可能重复。当然，解决这个问题也并不困难，只需要将种子定义在根对象上即可，不同的模块“实例”共享相同的根对象。&lt;/p&gt;

&lt;p&gt;还有个问题可能就显得隐蔽些了，我们可以通过一个简单的实验来观察结果。我们先来定义一个jeffz-a模块，其中暴露出一个MyType类型：&lt;/p&gt;

&lt;pre class="code"&gt;module.exports.MyType = &lt;span style="color: blue"&gt;function &lt;/span&gt;() { }&lt;/pre&gt;

&lt;p&gt;然后将其发布到npm上。然后再写一个jeffz-b模块，依赖jeffz-a，并将jeffz-a中定义的MyType类型直接暴露出去：&lt;/p&gt;

&lt;pre class="code"&gt;module.exports.MyType = require(&lt;span style="color: maroon"&gt;&amp;quot;jeffz-a&amp;quot;&lt;/span&gt;).MyType;&lt;/pre&gt;

&lt;p&gt;接着将jeffz-b也发布置npm上。再重新写一个测试模块，使用npm安装jeffz-a和jeffz-b，最终目录会是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;./node_modules
├── jeffz-a
└── jeffz-b
    └── node_modules
        └── jeffz-a&lt;/pre&gt;

&lt;p&gt;在测试模块内，我们来测试实例与类型之间的关系：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;a = require(&lt;span style="color: maroon"&gt;&amp;quot;jeffz-a&amp;quot;&lt;/span&gt;);
&lt;span style="color: blue"&gt;var &lt;/span&gt;b = require(&lt;span style="color: maroon"&gt;&amp;quot;jeffz-b&amp;quot;&lt;/span&gt;);

console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;a.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;a.MyType); &lt;span style="color: #006400"&gt;// true&lt;/span&gt;
console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;b.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;b.MyType); &lt;span style="color: #006400"&gt;// true&lt;/span&gt;

console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;a.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;b.MyType); &lt;span style="color: #006400"&gt;// false&lt;/span&gt;
console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;b.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;a.MyType); &lt;span style="color: #006400"&gt;// false&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;从表面上看，jeffz-b和jeffz-a暴露出的应该是相同的MyType类型，它们的对象通过instanceof相互判断应该都返回true，但实际上由于jeffz-b中的jeffz-a，与我们直接加载的jeffz-a模块是不同的实例，因此MyType类型自然也不是同一个了。&lt;/p&gt;

&lt;p&gt;这对于Jscex的影响在于，Jscex的异步模块在取消时，原本是通过判断异常对象是否为CanceledError类型来决定Task的状态为cancelled还是faulted。但由于Node.js可能会将相同的模块加载为多个实例，因此即便抛出的的确是某个实例的CancelledError，也无法通过另一个实例内部的判断。因此，目前Jscex的判断方式修改为检查异常对象的isCancellation字段，简单地解决了这个问题。&lt;/p&gt;

&lt;p&gt;当然，Node.js这种“重复加载”的影响也并非完全是负面的，至少它天然的解决了多版本共存的问题。例如，express v2.5.2依赖mime v1.2.4，但我们程序自身又想使用mime v1.2.5。此时，express内部自然使用mime v1.2.4，而我们自己的程序使用的便是mime v1.2.5。&lt;/p&gt;

&lt;p&gt;有些情况下您可能也想避免这种重复加载，这就必须手动地删除模块内部被间接依赖的模块，将其移动到模块查询路径的公用部分上了。就目前看来，这些操作必须手动进行，因为npm在安装模块时不会关心依赖的模块是否已经安装过了（例如在NODE_PATH环境变量标识的路径里），它一定会重新下载所有依赖的模块。可惜如果您使用的是托管形式的Node.js服务，则很有可能无法做到这一点。&lt;/p&gt;

&lt;p&gt;因此，我们在编写Node.js模块的时候，便事先考虑下它会被重复加载时的情况吧。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/12/same-node-module-load-multiple-times.html#comments</comments>
      <pubDate>Tue, 27 Dec 2011 15:19:29 GMT</pubDate>
      <lastBuildDate>Wed, 28 Dec 2011 04:45:46 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>受禁锢的异步编程思维</title>
      <link>http://blog.zhaojie.me/2011/12/the-stuck-mind-of-asynchronous-programming.html</link>
      <guid>http://blog.zhaojie.me/2011/12/the-stuck-mind-of-asynchronous-programming.html</guid>
      <description>&lt;p&gt;最近一直在努力推广&lt;a href="http://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;，补充了很多中文文档和示例，因此博客上都已经有两篇文章有了“上”而没有“下”，即使最复杂的图示也已经绘制完毕。在推广Jscex的过程中，我发现有个比较明显的问题是，许多使用JavaScript的程序员已经习惯旧有的编程方式，甚至推崇一些据他们说很“漂亮”的模式。但在我看来，这其实跟许多GoF模式是在修补OO语言的不足有些类似，很多异步模式都只是因为JavaScript语言特性不足而设计出来的“权宜之计”。我们在传统JavaScript编程环境下并没有其他选择，单纯地认为这是“美”，说实话只不过是一种安慰罢了。&lt;/p&gt;

&lt;p&gt;Jscex的重头戏便是处理异步操作，但异步操作并不只是如Node.js中通过回调函数传回结果的那些方法，或者是网页上的AJAX请求等等。异步操作的定义其实可以概括成“&lt;span style="color:#ff0000"&gt;会在未来某个时刻完成的操作&lt;/span&gt;”，就只是这么简单。什么事情会在未来发生，那么它对你来说就是个异步操作。因此其实在日常开发过程中可谓到处是异步操作，例如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;播放动画（播放会在未来结束） &lt;/li&gt;

  &lt;li&gt;模态对话框关闭（模态对话框会在未来关闭） &lt;/li&gt;

  &lt;li&gt;用户操作（用户会在未来点击按钮） &lt;/li&gt;

  &lt;li&gt;各类事件（数据流会在未来关闭，WebWorker会在未来获得消息，图片会在未来加载成功） &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些示例实在数不胜数。但是，在许多JavaScript程序员眼中，似乎只有AJAX或是Node.js中的那些异步方法才算是异步操作。其他的东西，比如用户点击一个按钮，这难道不是个天然的“事件”吗？其实这就要视这个异步任务的性质如何了。如果它是一系列操作的“发起者”，那么的确，使用事件触发的方式来对待这次点击操作可能是最合理的。但如果，这个操作只是一系列过程中的一个步骤，那么如果依然把它视为一个事件型的操作，就只会破坏我们的逻辑了。&lt;/p&gt;

&lt;p&gt;举个例子，和Jscex的&lt;a href="https://github.com/JeffreyZhao/jscex/blob/master/README-cn.md"&gt;快速入门&lt;/a&gt;比较类似，即&lt;a href="http://en.wikipedia.org/wiki/Fibonacci_number"&gt;菲薄纳契（Fibonacci）数列&lt;/a&gt;：&lt;/p&gt;
&lt;img class="embed" title="F_n = F_{n-1} + F_{n - 2}" src="http://latex.codecogs.com/gif.latex?F_n = F_{n-1} + F_{n - 2}" /&gt; 

&lt;p&gt;其边界情况为：&lt;/p&gt;
&lt;img class="embed" title="F_0 = 0, F_1 = 1" src="http://latex.codecogs.com/gif.latex?F_0 = 0, F_1 = 1" /&gt; 

&lt;p&gt;以上是其标准定义，直接写成算法即是：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;fib = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {

    console.log(0);
    console.log(1);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0, current = 1;
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;b = a;
        a = current;
        current = a + b;

        console.log(current);
    }
};&lt;/pre&gt;

&lt;p&gt;上述代码将会无限地循环下去，不断输出数列的每一项。快速入门里的要求，是将其修改为“每隔一秒输出一个数字”，于是有同学就说：这不天生是计时器的场景吗？但事实并非如此。“计时器”或是setTimeout函数，都只是环境提供给我们的唯一可用的功能，我们要意识到这不是我们主动的“选择”。如果一看到“每隔一秒”这样的需求，JavaScript程序员就认为“计时器”是“最好”的办法，这就说明思维被禁锢了。我相信这样的功能交给其他任何平台的程序员，他们的第一感觉几乎都会是“使用Sleep函数暂停一秒”。这其实才是最简单的做法，直接，清晰，完整保留现有代码逻辑。&lt;/p&gt;

&lt;p&gt;这也是基于Jscex之后的实现方式。这里我再将要求修改一下，改为用户“每点击一次按钮”输出一个数字，又该怎么做？基于Jscex的做法如下：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;Async = Jscex.Async;

&lt;span style="color: blue"&gt;var &lt;/span&gt;fibAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;button = document.getElementById(&lt;span style="color: maroon"&gt;&amp;quot;button&amp;quot;&lt;/span&gt;);

    $await(Async.onEvent(button, &lt;span style="color: maroon"&gt;&amp;quot;click&amp;quot;&lt;/span&gt;)); &lt;span style="color: #006400"&gt;// 等待用户点击
    &lt;/span&gt;console.log(0);

    $await(Async.onEvent(button, &lt;span style="color: maroon"&gt;&amp;quot;click&amp;quot;&lt;/span&gt;)); &lt;span style="color: #006400"&gt;// 等待用户点击
    &lt;/span&gt;console.log(1);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0, current = 1;
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;b = a;
        a = current;
        current = a + b;

        $await(Async.onEvent(button, &lt;span style="color: maroon"&gt;&amp;quot;click&amp;quot;&lt;/span&gt;)); &lt;span style="color: #006400"&gt;// 等待用户点击
        &lt;/span&gt;console.log(current);
    }
}));

fibAsync().start();&lt;/pre&gt;

&lt;p&gt;有朋友可能会问：用户点击按钮不是需要响应事件的嘛，这个事件到哪里去了？其实正像我所说的那样，把这里的“用户点击按钮”当作事件对待并非最合理的方式，因为它只是“整个过程”中的一个环节而已。在这里，我们其实只是要在输出数字之前“等待用户点击”即可，这个“输出”以及相关的“计算”操作，并非是由“按钮点击”所触发的逻辑，而是一个连续的统一过程中的一部分而已。&lt;/p&gt;

&lt;p&gt;您可以试试纯粹使用事件机制来实现这个功能，保证您需要重新实现这段斐波那契数列的逻辑。当然，菲薄纳契数列的逻辑很简单，重写下估计也不会花太大的功夫，但如果您需要改造&lt;a href="http://files.zhaojie.me/jscex/samples/async/hanoi.html"&gt;汉诺塔的动画效果&lt;/a&gt;呢？&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;hanoiAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(n, from, to, mid) {
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(n &amp;gt; 0) {
        $await(hanoiAsync(n - 1, from, mid, to));
    }

    &lt;span style="color: #006400"&gt;// 等待按钮点击
    // var btnNext = document.getElementById(&amp;quot;btnNext&amp;quot;);
    // $await(Jscex.Async.onEvent(btnNext, &amp;quot;click&amp;quot;));

    &lt;/span&gt;$await(moveDishAsync(n, from, to));

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(n &amp;gt; 0) {
        $await(hanoiAsync(n - 1, mid, to, from));
    }
}));&lt;/pre&gt;

&lt;p&gt;以上代码是以动画形式表现汉诺塔的解题过程，但如果用户提出想要“每点一次按钮”才移动一个盘子，那其实我们只要将上面两行代码取消注释即可。如果忽然有一天，老板要求通过一个选项来决定是否“自动移动”，在Jscex里只要加一个if判断即可。您可以简单设想一下直接裸写这些代码会遇到什么样的景象，改造时会遇到哪些困难。&lt;/p&gt;

&lt;p&gt;我还为Jscex准备了一个示例，是关于“&lt;a href="https://github.com/JeffreyZhao/jscex/blob/master/doc/async/samples/modal-dialog-cn.md"&gt;模态对话框&lt;/a&gt;”配合相关异步操作的。由于是“模态对话框”，我们是要在对话框关闭之后才继续做某些事情。可惜在JavaScript中，如果您直接把一个界面元素展现为一个模态对话框，它是无法阻止后面的代码继续执行的，要阻止则只能使用confirm或alert方法。于是，我们只能把后续操作放到一个回调函数中去，并在模态对话框关闭之后才执行。但是您要知道，模态对话框只不过是整个过程中的一个步骤，理想状况下我们的完整功能不该被拆成多个部分，再使用所谓“美妙”的回调串联起来。&lt;/p&gt;

&lt;p&gt;这点在Jscex中还是那么简单，直接按最简单的逻辑来进行即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// 显示模态对话框&lt;/span&gt;
$await($(&lt;span style="color: maroon"&gt;&amp;quot;#dialog-confirm&amp;quot;&lt;/span&gt;).dialogAsync({ modal: &lt;span style="color: blue"&gt;true &lt;/span&gt;}));

&lt;span style="color: #006400"&gt;// 发起AJAX请求&lt;/span&gt;
&lt;span style="color:blue"&gt;var&lt;/span&gt; response = $await($.ajaxAsync({ url: &lt;span style="color: maroon"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;, dataType: &lt;span style="color: maroon"&gt;&amp;quot;text&amp;quot;&lt;/span&gt; }));

&lt;span style="color: #006400"&gt;// 继续做事&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;而无需：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// 继续做事&lt;/span&gt;
$(&lt;span style="color: maroon"&gt;&amp;quot;#dialog-confirm&amp;quot;&lt;/span&gt;).dialog({
    modal: &lt;span style="color: blue"&gt;true&lt;/span&gt;,
    close: &lt;span style="color: blue"&gt;function&lt;/span&gt; () {
        &lt;span style="color: #006400"&gt;// 发起AJAX请求&lt;/span&gt;
        $.ajax({
            url: &lt;span style="color: maroon"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;,
            dataType: &lt;span style="color: maroon"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;,
            success: &lt;span style="color: blue"&gt;function&lt;/span&gt; () {
                &lt;span style="color: #006400"&gt;// 继续做事&lt;/span&gt;
            }
        });
});&lt;/pre&gt;

&lt;p&gt;经常会听到有些朋友谈起，说在实际开发过程中很少遇到异步场景。但在我看来，实在可谓遍地是异步，这种观念的差别只是在于是否经过了“抽象”。不加抽象地使用技术平台为我们提供的异步操作，会让我们的思维被它所禁锢。在JavaScript编程中浸淫太久了，可能就会忘记我们从最初是如何编程的。Jscex的目标，便是将这些东西回归自然，将逻辑以最自然的方式表达出来。循环？那就用for或是while吧，在函数之间跳来跳去是做什么的？&lt;/p&gt;

&lt;p&gt;我从来不担心的Jscex的实用价值。Jscex来自C#，F#以及Scala等现成的理念，各种开发模式都是被翻来覆去讨论过，总结过，验证过的。这些语言其实都能实现与JavaScript类似的编程模式，但它们不需要，因为语言特性让程序员可以使用更简单直接的做法来解决问题。Jscex只是将这些现成的内容，从其他模式带到JavaScript编程领域上而已。&lt;/p&gt;

&lt;p&gt;如今我唯一担心的，只是那些被禁锢的编程思维。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/12/the-stuck-mind-of-asynchronous-programming.html#comments</comments>
      <pubDate>Mon, 19 Dec 2011 13:49:37 GMT</pubDate>
      <lastBuildDate>Tue, 27 Dec 2011 15:17:21 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <title>Jscex正式发布至npm</title>
      <link>http://blog.zhaojie.me/2011/12/jscex-officially-released-to-npm.html</link>
      <guid>http://blog.zhaojie.me/2011/12/jscex-officially-released-to-npm.html</guid>
      <description>&lt;p&gt;之前一直不敢将&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;发布至&lt;a href="http://npmjs.org/"&gt;npm&lt;/a&gt;上，因为有些问题还没有完全拿定主意，例如“取消”任务的模型。一个异步任务一定是需要取消功能，尽管这个模型&lt;a href="http://www.cnblogs.com/iamzhanglei/archive/2011/11/17/2253253.html"&gt;不一定需要直接定义在Jscex里&lt;/a&gt;。但是我还是为Jscex提供了一个统一的标准化的取消模型，一是易于使用，二是提供模型这个之后，API在设计时可以有更多表现力（例如，现在的task的status可能是canceled了）。上周末我实现这个模型（其实也就十几二十行代码），昨天我又修改了模块的加载方式，使其支持&lt;a href="http://www.commonjs.org/"&gt;CommonJS&lt;/a&gt;规范。在发布到npm之后，现在可以说Jscex已经正式对外公开了！&lt;/p&gt;

&lt;p&gt;在加载Jscex代码之后，全局环境里会出现一个Jscex对象，这是所有Jscex功能的入口（根对象）。但对于&lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt;来说，相同的模块（无论版本是否一致）有可能还会被加载多次，因此这种方式并不适合Node.js。昨天的修改主要就是为了迎合Node.js的包管理机制（类似CommonJS），避免对全局环境造成污染。自然，如果脚本执行环境里没有实现CommonJS规范（例如浏览器），则Jscex的行为依然和之前保持不变。&lt;/p&gt;

&lt;p&gt;目前Jscex的核心部分已经发布到npm里，版本为0.5.0，分为两个互不依赖的三个部分：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;jscex：&lt;/strong&gt;提供了Jscex的基础功能，作为Jscex的根对象。 &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;jscex-jit：&lt;/strong&gt;提供了Jscex的JIT编译器，以“插件”形式为Jscex根对象进行扩展，一般仅作为开发工具使用。 &lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;jscex-async：&lt;/strong&gt;Jscex的异步支持，以“插件”形式为Jscex根对象进行扩展。在Node.js中一般与JIT编译器同时使用，但理论上可以在AOT编译之后摆脱对JIT编译器的依赖。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;您可以使用npm安装这三个模块：&lt;/p&gt;

&lt;pre class="code"&gt;$ &lt;strong&gt;npm install jscex jscex-jit jscex-async&lt;/strong&gt;
jscex@0.5.0 ./node_modules/jscex
jscex-jit@0.5.0 ./node_modules/jscex-jit
jscex-async@0.5.0 ./node_modules/jscex-async&lt;/pre&gt;

&lt;p&gt;然后即可在脚本里使用：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color:blue"&gt;var &lt;/span&gt;Jscex = require(&lt;span style="color:maroon"&gt;"jscex"&lt;/span&gt;);
require(&lt;span style="color:maroon"&gt;"jscex-jit"&lt;/span&gt;).init(Jscex);
require(&lt;span style="color:maroon"&gt;"jscex-async"&lt;/span&gt;).init(Jscex);&lt;/pre&gt;

&lt;p&gt;然后便可以像以前那样正常使用Jscex了。请注意，由于Jscex的实现方式所限，一定要在上下文里存在名为Jscex的根对象。此外，目前发布在npm上只有JIT编译器及其异步支持的核心功能，并没有包含如sleep或parallel方法那样的核心类库。这部分代码已经被剥离至jscex-async-powerpack模块内，将在合适的时候发布至npm上。&lt;/p&gt;

&lt;p&gt;目前使用Jscex的人依旧不多，有同学将Jscex配合HTML 5的绘图使用，进行了&lt;a href="http://www.cnblogs.com/iamzhanglei/"&gt;进行了许多尝试&lt;/a&gt;，&lt;a href="http://game.mhtml5.com/game/works/ztTnYEvyFrEbguW6/"&gt;颇为有趣&lt;/a&gt;。如果您对Jscex有什么意见或建议，也请及时告知。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/12/jscex-officially-released-to-npm.html#comments</comments>
      <pubDate>Thu, 01 Dec 2011 06:23:00 GMT</pubDate>
      <lastBuildDate>Wed, 28 Dec 2011 04:26:31 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>基于Node.js、Express和Jscex开发的ToDo网站示例</title>
      <link>http://blog.zhaojie.me/2011/07/nodejs-express-jscex-demo-website-todo.html</link>
      <guid>http://blog.zhaojie.me/2011/07/nodejs-express-jscex-demo-website-todo.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/jscex" target="_blank"&gt;Jscex&lt;/a&gt;的主要使用场景是“JavaScript异步编程”，不过并没有限制是跑在浏览器还是服务器端。最近&lt;a href="http://nodejs.org/" target="_blank"&gt;Node.js&lt;/a&gt;很火热，也刚发布了原生的Windows版，不少同学会用它来做一些网站这样的小程序。目前用Node.js开发网站最著名的框架是&lt;a href="http://expressjs.com/" target="_blank"&gt;Express&lt;/a&gt;，使用起来也是比较容易的。前段时间看到&lt;a href="http://cnodejs.org/blog/?p=1310" target="_blank"&gt;CNodeJS社区的一篇文章&lt;/a&gt;，有同学将一个&lt;a href="http://simple-is-better.com/news/309" target="_blank"&gt;Python写的ToDo列表网站&lt;/a&gt;移植到了Node.js上，我为了推广Jscex，就fork了这个项目，将其修改为&lt;a href="https://github.com/JeffreyZhao/todo" target="_blank"&gt;基于Jscex的版本&lt;/a&gt;，大伙儿可以来比较一下。当然这个网站过于简单，我也正在寻找更合适的项目。&lt;/p&gt;

&lt;p&gt;JavaScript是一个没有阻塞特性的语言，因此各类API都会设计为异步，这对于服务器的伸缩性和客户端网页的响应能力都有好处，不过在程序编写上就会遇到各种问题了。例如在ToDo示例中的一个简单的处理函数，因为需要查询数据库，就要写成带回调的样子：&lt;/p&gt;

&lt;pre class="code"&gt;exports.index = &lt;span style="color: blue"&gt;function &lt;/span&gt;(req, res, next) {
    db.query(&lt;span style="color: maroon"&gt;'select * from todo order by finished asc, id asc limit 50'&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, rows) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        res.render(&lt;span style="color: maroon"&gt;'index'&lt;/span&gt;, { todos: rows });
    });
};&lt;/pre&gt;

&lt;p&gt;db变量用来操作MySQL数据库，它的query方法传入sql（可能还会有参数）并提供一个回调函数，用来提示错误或是返回查询结果。在回调中我们必须判断err是否存在，如果存在便调用next报告框架“出错了”。每个异步操作都必须如此，试想如果在这个查询后还有另一个查询，则还需要进行一次嵌套和err判断。每个处理函数都是如此，这也是异步编程的烦恼之一：难以进行统一的异常处理，处理代码总是需要分散在各处，一不小心就变成“野异常”，还很难排查出来。&lt;/p&gt;

&lt;p&gt;我将ToDo网站简单地Jscex化了一下。首先让MySQL的查询能够接入Jscex（lib\jscex.mysql.js）：&lt;/p&gt;

&lt;pre class="code"&gt;exports.jscexify = &lt;span style="color: blue"&gt;function &lt;/span&gt;(db) {
    db.queryAsync = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;_this = &lt;span style="color: blue"&gt;this&lt;/span&gt;;

        &lt;span style="color: blue"&gt;var &lt;/span&gt;args = [];
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; arguments.length; i++) {
            args.push(arguments[i]);
        }

        &lt;span style="color: blue"&gt;var &lt;/span&gt;delegate = {
            onStart: &lt;span style="color: blue"&gt;function &lt;/span&gt;(callback) {

                args.push(&lt;span style="color: blue"&gt;function &lt;/span&gt;(err, result) {
                    &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) {
                        callback(&lt;span style="color: maroon"&gt;&amp;quot;failure&amp;quot;&lt;/span&gt;, err);
                    } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
                        callback(&lt;span style="color: maroon"&gt;&amp;quot;success&amp;quot;&lt;/span&gt;, result);
                    }
                });

                _this.query.apply(_this, args);
            }
        };

        &lt;span style="color: blue"&gt;return new &lt;/span&gt;Jscex.Async.Task(delegate);
    }
}&lt;/pre&gt;

&lt;p&gt;一般来说，将一个异步接口给Jscex化并不需要那么多代码（最关键的其实只是onStart函数）。这里近30行代码，其中大部分是为了支持“变长”参数，因此queryAsync函数会保留调用时的所有参数，补上一个callback，再去调用query函数本身。此时，便可以去改写之前的index等处理函数了（controllers\todo.js），例如：&lt;/p&gt;

&lt;pre class="code"&gt;exports.index = toHandler(eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(req, res) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;todos = $await(db.queryAsync(&lt;span style="color: maroon"&gt;'select * from todo order by finished asc, id asc limit 50'&lt;/span&gt;));
    res.render(&lt;span style="color: maroon"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;, { todos: todos });

})));&lt;/pre&gt;

&lt;p&gt;toHandler函数的作用，是将一个“接受req和res，返回Task”的函数，封装成标准的“接受req、res和next三个参数”的处理函数，并提供统一的错误处理：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;toHandler = &lt;span style="color: blue"&gt;function &lt;/span&gt;(asyncFunc) {
    &lt;span style="color: blue"&gt;return function &lt;/span&gt;(req, res, next) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;task = asyncFunc(req, res);
        task.addListener(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(task.status == &lt;span style="color: maroon"&gt;&amp;quot;failed&amp;quot;&lt;/span&gt;) {
                next(task.error);
            }
        });
        task.start();
    }
}&lt;/pre&gt;

&lt;p&gt;我在todo.js里保留了原有各个处理函数的实现，感兴趣的朋友可以对比一下它们之前的差别。可惜的是，由于ToDo实在过于简单，Jscex的优势并没有表现出来太多。例如，每个处理程序中只有一个MySQL查询，没有判断和循环，更别说为了充分利用IO并发能力，从而组合多个异步函数了。因此，我最近也一直在寻找更复杂一些的示例，不过似乎用Express的开源网站并不多见，我几乎都想自己写一个了。目前感觉&lt;a href="https://github.com/alexyoung/nodepad" target="_blank"&gt;Nodepad&lt;/a&gt;似乎还算不错，接下来可能会对它下手。&lt;/p&gt;

&lt;p&gt;ToDo网站依赖Express，ejs和MySQL驱动，同时我把Jscex作为添加为它的子模块。如果您要&lt;a href="https://github.com/JeffreyZhao/todo" target="_blank"&gt;克隆一份ToDo的代码&lt;/a&gt;把玩一番，可以：&lt;/p&gt;

&lt;pre class="code"&gt;&amp;gt; git clone git://github.com/JeffreyZhao/todo.git
&amp;gt; cd todo
&amp;gt; git submodule init
&amp;gt; git submodule update
&amp;gt; npm install express ejs mysql
&amp;gt; node server.js&lt;/pre&gt;

&lt;p&gt;从现在开始，我会在InfoQ中文站上发表一系列关于Jscex的文章，既有关于浏览器端的JavaScript开发，也有在服务器端利用Node.js开发的内容。可能您目前还可能会有所疑惑，例如为什么要使用危险的eval函数，eval和Jscex.compile函数不能封装起来吗？其实在看了我的文章并对Jscex有了基本了解之后，就会发现这些都是以“传统眼光”来看待Jscex时所形成的误解。Jscex的做法的确“另辟蹊径”，否则在JavaScript异步类库已经多如牛毛的情况下，我不知如何让它脱颖而出。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/07/nodejs-express-jscex-demo-website-todo.html#comments</comments>
      <pubDate>Fri, 15 Jul 2011 06:33:10 GMT</pubDate>
      <lastBuildDate>Fri, 15 Jul 2011 08:57:40 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>JavaScript：假如default不是switch的最后一项</title>
      <link>http://blog.zhaojie.me/2011/05/javascript-when-break-is-not-the-last-choice-of-switch.html</link>
      <guid>http://blog.zhaojie.me/2011/05/javascript-when-break-is-not-the-last-choice-of-switch.html</guid>
      <description>&lt;p&gt;话说大家对于switch语句应该再熟悉不过了，各种类C语言都不例外，JavaScript自然也是如此。switch的逻辑很简单，根据switch内容的值执行对应的case项，否则执行default项即可。但是不同的语言在具体一些细节上面的处理却是不同的。例如在JavaScript里，每个case项都可以没有break，于是语句便会顺延到下个case或是default里面去——但某些语言设计者认为这种特性容易造成代码理解上的偏差，因此比如在C#里便要求每个非空的case都要有个break。那么再来一个细节问题：如果default之后还有case，那么会出现什么样的情况？如果default里没有break呢？&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
}&lt;/pre&gt;

&lt;p&gt;就好比这段代码，当a等于0、1或2的时候，将会输出什么样的内容呢？先猜猜，别急着往下看。&lt;/p&gt;

&lt;p&gt;当a等于0时，则会输出：&lt;/p&gt;

&lt;pre class="code"&gt;0
default
1&lt;/pre&gt;

&lt;p&gt;当a等于1时，则会输出：&lt;/p&gt;

&lt;pre class="code"&gt;1&lt;/pre&gt;

&lt;p&gt;当a等于2时，则会输出：&lt;/p&gt;

&lt;pre class="code"&gt;default
1&lt;/pre&gt;

&lt;p&gt;好吧，尽管这样的代码比较罕见，但执行结果也并没有什么“特殊”的。switch的规则依旧可以用一句话说清：如果匹配到某个case，则从该case处开始执行，否则就从default处开始执行，一直向下，直到出现break语句为止。至于default的位置是否在最后，对于执行的策略可谓完全没有影响。&lt;/p&gt;

&lt;p&gt;当然，我实在没想到为什么有人会写这样的代码，所以假如有人对这点感觉恍惚我也觉得没太大关系。不过既然我要写&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;，则还是必须对此类代码的行为有所了解。尽管语言的使用者可以选择合适的子集，但语言的开发者（编译器、解释器等等）却必须遵循完整的规范，这是Jscex这类项目需要应对的麻烦。&lt;/p&gt;

&lt;p&gt;既然Jscex号称支持“全部JavaScript语言特性”，自然对switch的支持也在包括在内。switch的麻烦之处在于它的每个分支不像if语句那样完全相互独立，而是会不断“穿透”下去直至遇上break。因此Jscex在处理switch的时候也使用了一些技巧。例如下面这段代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        $await(helloWorld());
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
}&lt;/pre&gt;

&lt;p&gt;Jscex会将每个case及default中的语句“补齐”，以“确保”每项里都有完整的语句以及最后的break：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        $await(helloWorld());
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;break&lt;/span&gt;;
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;break&lt;/span&gt;;
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;break&lt;/span&gt;;
}&lt;/pre&gt;

&lt;p&gt;然后再将其编译为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Bind(helloWorld(), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
            console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        });
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
    }
})&lt;/pre&gt;

&lt;p&gt;自然，如果switch里没有包含bind操作（例如$await语句），则整个switch语句都会得以保留，这也是&lt;a href="http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html"&gt;Jscex编译结果的优化策略&lt;/a&gt;之一。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/javascript-when-break-is-not-the-last-choice-of-switch.html#comments</comments>
      <pubDate>Tue, 24 May 2011 03:33:03 GMT</pubDate>
      <lastBuildDate>Wed, 25 May 2011 16:59:52 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>上周末Jscex项目介绍的幻灯片</title>
      <link>http://blog.zhaojie.me/2011/05/jscex-write-sexy-javascript-slide.html</link>
      <guid>http://blog.zhaojie.me/2011/05/jscex-write-sexy-javascript-slide.html</guid>
      <description>&lt;p&gt;上周末，在风景秀丽的浙江大学校园内，举行了&lt;a href="http://cnodejs.org/blog/?p=956"&gt;NodeParty杭州站的活动&lt;/a&gt;。我在活动上结合&lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt;项目对&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;进行了简单介绍，包括其设计目的，设计原则，使用方式，高级模式，组成部分等等。在场的许多朋友也提出了不少问题，我也一一作了解答或是演示。总体感觉还算不错，毕竟是亲手编写的项目，对其各方面还是了然于胸的。在此发布演讲用的幻灯片，希望能给不在现场的同学带来一些帮助。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_7976001"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="Jscex: Write Sexy JavaScript (中文)" href="http://www.slideshare.net/jeffz/jscex-write-sexy-javascript-cn"&gt;Jscex: Write Sexy JavaScript (中文)&lt;/a&gt;&lt;/strong&gt; &lt;iframe height="355" marginheight="0" src="http://www.slideshare.net/slideshow/embed_code/7976001" frameborder="0" width="425" marginwidth="0" scrolling="no"&gt;&lt;/iframe&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt; &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;为了便于在国外社区推广，我也制作了英文版的幻灯片（事实上这是“原版”，中文版反而是会前临时决定翻译过来的），在此一并献上：&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_7975832"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="Jscex: Write Sexy JavaScript" href="http://www.slideshare.net/jeffz/jscex-write-sexy-javascript"&gt;Jscex: Write Sexy JavaScript&lt;/a&gt;&lt;/strong&gt; &lt;iframe height="355" marginheight="0" src="http://www.slideshare.net/slideshow/embed_code/7975832" frameborder="0" width="425" marginwidth="0" scrolling="no"&gt;&lt;/iframe&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt; &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;欢迎您使用Jscex，遇到什么问题也请及时与我联系。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;GitHub（英文主站）：&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;https://github.com/JeffreyZhao/jscex&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;SNDACode（中文站）：&lt;a href="http://www.sndacode.com/projects/jscex"&gt;http://www.sndacode.com/projects/jscex&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/jscex-write-sexy-javascript-slide.html#comments</comments>
      <pubDate>Mon, 16 May 2011 05:43:40 GMT</pubDate>
      <lastBuildDate>Mon, 16 May 2011 05:45:21 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>浅谈Jscex的$await语义及异步任务模型</title>
      <link>http://blog.zhaojie.me/2011/05/jscex-async-task-model-and-await-semantic.html</link>
      <guid>http://blog.zhaojie.me/2011/05/jscex-async-task-model-and-await-semantic.html</guid>
      <description>&lt;p&gt;从某些程度上说，&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;的是提供了“新语言”，只不过这种新语言和JavaScript长的一模一样，最多添加了一个$await操作这个语义而已。其他方面，JavaScript的各种语法都可以让Jscex编译，所以它基本可以说是个完备的方案。之前有朋友提出疑问，说$await只能执行单个任务，那么岂不是多个任务之间就出现了先后依赖关系？假如有三个任务：A和B可以并行，但C依赖前两者，ABC如果串行的话，系统的总耗时便不够理想了。其实Jscex并没有这种限制，因为它的任务模型和$await语义简单且具有深厚的理论基础，灵活、丰富而统一。&lt;/p&gt;

&lt;p&gt;首先谈下$await的语义，有些朋友阅读示例代码，可能会觉得它表示“执行一个异步任务”。其实不然。$await的语义实际上只是“等待该任务结束”，同时：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果该任务没有运行，则启动该任务。 &lt;/li&gt;

  &lt;li&gt;如果该任务已经完成，则立即返回结果（或抛出异常）。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在Jscex的异步类库中，“异步任务”是独立的模型，它有自己的start或addListener等成员。一个Jscex异步函数的执行结果也是个异步任务对象，我们最终也是调用其start方法来启动这个任务。在一个Jscex函数内部，我们也可以手动地启动任务。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(taskA, taskB, taskC) {
    taskA.start();
    taskB.start();

    $await(taskA);
    $await(taskB);

    $await(taskC);
}&lt;/pre&gt;

&lt;p&gt;任务A和B的start方法会在调用后立即返回，并在两者都完成后，才会启动并等待C任务。以上便回答了之前那个朋友提出的问题。由于A和B没有依赖，我们便让其并行执行；C依赖前两者，于是便等A和B结束再启动，仅此而已。事实上，我们也可以这么做：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(taskA, taskB, taskC) {
    $await(Jscex.Async.parallel(taskA, taskB));
    $await(taskC);
}&lt;/pre&gt;

&lt;p&gt;Jscex.Async.parallel是一个辅助函数，接受一堆异步任务，并返回一个新的异步任务，执行它表示并行地执行这些子任务，它也会在子任务都完成后才结束。这个异步任务模型十分简单，人人都能轻松地使用及扩展，也十分灵活。假如我们将刚才的需求换一下：C依赖于B，但A与前两者都独立，则可以编写这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(taskA, taskB, taskC) {
    taskA.start();

    $await(taskB.continueWith(taskC));
    $await(taskA);
}&lt;/pre&gt;

&lt;p&gt;在JavaScript中，我们只要为Jscex.Async.Task对象扩展一个continueWith方法，表示taskC将于taskB之后执行，并作为一个新任务返回即可。此类&lt;a href="http://en.wikipedia.org/wiki/Futures_and_promises"&gt;Future/Promise模型&lt;/a&gt;，以及Jscex的Monadic编译形式在异步编程领域中都有着经典的理论基础，因此Jscex在异步编程方面的支持可谓简单而统一。&lt;/p&gt;

&lt;p&gt;Jscex一直在不断前进，如今Jscex又支持了一种$await语句形式，“赋值”：&lt;/p&gt;

&lt;pre class="code"&gt;hello.world = $await(...);&lt;/pre&gt;

&lt;p&gt;其实按理说，Jscex早该支持这种形式了，但可能是由于最早的思路有些过于借鉴F#的模式（无副作用，因此没有此类赋值），一不留意便疏忽至今了。JavaScript其实终究是一门更为“命令式”的语言，虽然Jscex由F#那里得到启发，但如今其具体实现和最终形式，却也是专为JavaScript而设计的，以确保其必要的功能（例如break，continue等中断逻辑流的语言特性）与性能。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/jscex-async-task-model-and-await-semantic.html#comments</comments>
      <pubDate>Tue, 10 May 2011 14:29:43 GMT</pubDate>
      <lastBuildDate>Tue, 10 May 2011 14:32:07 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>浅谈Jscex编译结果的优化</title>
      <link>http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html</link>
      <guid>http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/Jscex"&gt;Jscex&lt;/a&gt;的核心是一个JavaScript语言到Monadic形式的编译器。从理论上说，这种编译规则十分简单，要写一个能够“正常运行”的编译器很容易。但是“正常运行”不代表足够优化。优化不当，会导致生成的结果中产生太多函数及闭包，对性能产生负面影响。在Jscex的早期原型中，从AST生成最终代码的逻辑比较简单，只做了一些基础优化。后来&lt;a href="http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html"&gt;重构了编译器，减少了不必要的代码&lt;/a&gt;。而上周我提交了更新，实现了更复杂而有效的优化策略。如今的Jscex编译器部分应该已经足够稳定，剩下的便是类库方便的发展了。&lt;/p&gt;

&lt;h1&gt;基础优化&lt;/h1&gt;

&lt;p&gt;Jscex的基础编译策略十分精简，这里仅列出其中最简单一条来说明问题。比如有如下代码：&lt;/p&gt;

&lt;pre class="code"&gt;statement1;
statement2;&lt;/pre&gt;

&lt;p&gt;按照Jscex的转化标准，应该由Combine及Delay组合而成：&lt;/p&gt;

&lt;pre class="code"&gt;builder.Combine(
    builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        statement1;
    },
    builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        statement2;
    }));&lt;/pre&gt;

&lt;p&gt;在Jscex中，无论是Combine和Delay返回的都是协议相同的中间对象。出现Delay，是因为JavaScript一门Strict Evaluation的语言，需要有个机制确保“延迟执行”。Combine的作用是连接两个中间对象，并返回一个新的中间对象，该对象的“执行语义”表示“按序”执行两者。如果有三条语句则会继续使用Combine嵌套起来。&lt;/p&gt;

&lt;p&gt;那么如果有10条语句连续执行呢？这便会产生大量的Combine，Delay调用，以及一大堆函数、闭包等等。运行结果自然没有问题（就像Delay套Delay那样），但很显然会产生不必要的性能开销。因此，从最起初的Jscex原型开始，便对这样的连续语句做出了优化。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// before&lt;/span&gt;
&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0;
    b = a + 1;
    a++;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;a + b;
}

&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0;
            b = a + 1;
            a++;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Return(a + b);
        })
    );
})&lt;/pre&gt;

&lt;p&gt;无谓的Combine和Delay消失了，程序主体依旧是顺序执行的普通代码。&lt;/p&gt;

&lt;h1&gt;高级优化&lt;/h1&gt;

&lt;p&gt;连续的语句容易优化，所以在最早的Jscex原型中就已经有所体现。但是，如try/catch或是while循环这样的语言特性，又会变成怎么样的代码呢？例如，我们得到一个URL数组，然后使用个普通的for循环来遍历并请求每个URL里的内容：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// before
&lt;/span&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
        $await(requestAsync(urls[i]));
    }
}

&lt;span style="color: #006400"&gt;// after
&lt;/span&gt;(&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Loop(
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;i &amp;lt; urls.length;
                },
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    i++;
                },
                $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Bind(requestAsync(urls[i]), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
                    });
                }),
                &lt;span style="color: blue"&gt;false
            &lt;/span&gt;);
        })
    );
})&lt;/pre&gt;

&lt;p&gt;无论是for，while还是do循环，编译之后都会成为了Loop调用。以上的代码已经足够优化，但如果我们换种写法，将“顺序”请求变为“并发”请求：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// before
&lt;/span&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;requests = [];

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
        requests.push(requestAsync(urls[i]));
    }

    $await(Jscex.Async.parallel(requests));
}&lt;/pre&gt;

&lt;p&gt;在相当一段时间内，Jscex编译器输出的代码是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;requests = [];
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Combine(
                $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0;
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Loop(
                        &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                            &lt;span style="color: blue"&gt;return &lt;/span&gt;i &amp;lt; urls.length;
                        },
                        &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                            i++;
                        },
                        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                            requests.push(requestAsync(urls[i]));
                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
                        })
                    );
                }),
                $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(Jscex.Async.parallel(requests), &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Normal();
                    });
                })
            );
        });
    )
})&lt;/pre&gt;

&lt;p&gt;其实也很容易理解：一个标准的for循环嘛。但实际上，编译器完全没有必要生成那么复杂的代码。因为这个for循环内部没有涉及到绑定操作（例如异步类库下的$await），它完全只是段普通的代码，应该全部保留，就像这样：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;requests = [];
            &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
                requests.push(requestAsync(urls[i]));
            }
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Bind(Jscex.Async.parallel(requests), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
            });
        })
    );
})&lt;/pre&gt;

&lt;p&gt;以上便是目前编译器会生成的代码。从表面上看来，要实现这点并不困难，只要检查AST中的for节点内部有没有绑定操作，如果没有，则直接输出。我一开始也是这么认为的，但是写了一大堆代码以后发现事情并没有想象中那么简单，于是回滚代码，重新开始。试看这样一段简单的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// before&lt;/span&gt;
&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;try &lt;/span&gt;{
        &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
    } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex) {
        &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex;
    }
}&lt;/pre&gt;

&lt;p&gt;您可以猜测一下，这段代码编译后的结果是怎么样的呢？应该是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;try &lt;/span&gt;{
                &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
            } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex) {
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Throw(ex);
            }
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;请注意，try内部的throw在目标代码里还是throw，而catch里的throw则是把异常抛向外部，因此则编译为Throw方法。如果再嵌套一个try/catch则会看的更加明显：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// before&lt;/span&gt;
&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;try &lt;/span&gt;{
        &lt;span style="color: blue"&gt;try &lt;/span&gt;{
            &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
        } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex1) {
            &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex1;
        }
    } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex2) {
        &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex2;
    }
}

&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;try &lt;/span&gt;{
                &lt;span style="color: blue"&gt;try &lt;/span&gt;{
                    &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
                } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex1) {
                    &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex1;
                }
            } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex2) {
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Throw(ex2);
            }
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;在这段代码里，内层catch里的throw会被外层的catch捕获到，因此它还是普通的throw。与F#不同的是，JavaScript的某些语言特性（如throw，break，continue）会中断代码正常的执行流。因此，即便是一个普通的if分支里的的break，我们也必须判断这是个普通的break语句还是一个Break输出。不过实现这样的编译器也并不困难，例如我只是在生成代码时记录当前位置而已。例如一个普通try内部的throw便是普通的throw，如果这个try变成了Try结构，则必然是Throw输出了。&lt;/p&gt;

&lt;p&gt;优化到这步之后，应该说编译器的输出结果已经定型，剩下的就是类库方面的事情了。顺便一提，&lt;a href="http://www.sndacode.com/projects/jscex/wiki"&gt;Jscex中文站上的说明&lt;/a&gt;也已经同步更新，并补充了更多内容。此外，下周六我将会在&lt;a href="http://cnodejs.org/blog/?p=897"&gt;杭州CNodeJS聚会&lt;/a&gt;上和大家讨论Jscex类库方面的话题，感兴趣的朋友不妨同去同去。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html#comments</comments>
      <pubDate>Thu, 05 May 2011 10:45:31 GMT</pubDate>
      <lastBuildDate>Thu, 05 May 2011 10:55:01 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>Jscex编译器更新：已支持嵌套Jscex函数</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-compiler-update-support-nested-jscex-function.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-compiler-update-support-nested-jscex-function.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;的编译器更新了。之前的编译器并不会将一个Jscex函数内部的其他Jscex函数代码一并展开，这导致内嵌的Jscex函数会在外部函数调用时反复编译，性能开销较大；不过更重要问题，可能是AOT编译后的代码无法彻底解除与编译器的依赖。嵌套Jscex函数是否合理是一回事儿，使用者可以不去这么做，但是编译器本身还是该支持的。这也是Jscex编译器改进计划中的重要一步。&lt;/p&gt;

&lt;p&gt;之前，如果您编写这样的函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;outerAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;innerAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: #006400"&gt;// inner implementations
    &lt;/span&gt;}));

}));&lt;/pre&gt;

&lt;p&gt;实际上编译器会生成这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;outerAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$ = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {

            &lt;span style="color: blue"&gt;var &lt;/span&gt;innerAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: #006400"&gt;// inner implementations&lt;/span&gt;
            }));

            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;这样在每次调用outerAsync的时候，都会重新启用Jscex.compile及eval的过程，这个性能开销还是比较可观的。不过目前的编译器已经会将内部的Jscex函数彻底展开：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;outerAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {

            &lt;span style="color: blue"&gt;var &lt;/span&gt;innerAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_1 = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_1.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
                    &lt;span style="color: #006400"&gt;// compiled inner implementations&lt;/span&gt;
                    $$_builder_$$_1.Normal()
                );
            });

            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;除了不会多次编译内部的Jscex函数外，还彻底解除了与Jscex编译器的依赖，AOT编译器表示情绪愉快。您可能发现了，原本的builder变量名是固定的，不过为了支持内嵌函数，我在代码里添加了一个种子，这样每次编译时的builder变量名就不同了。例如在上面的代码中，编译后的外层函数使用$$_builder_$$_0，而内层函数使用的则是$$_builder_$$_1。&lt;/p&gt;

&lt;p&gt;于是现在我们便可以使用内嵌Jscex函数了，例如&lt;a href="http://blog.zhaojie.me/2010/11/the-coming-talks-and-jscex.html"&gt;之前提到过的“动画”示例&lt;/a&gt;，现在便可以写为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;moveSquareAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function&lt;/span&gt;(e) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;moveAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function&lt;/span&gt;(startPos, endPos, duration) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; t &amp;lt; duration; t += 50) {
            e.style.left = (startPos.x + (endPos.x - startPos.x) * t / duration) + &lt;span style="color: maroon"&gt;&amp;quot;px&amp;quot;&lt;/span&gt;;
            e.style.top = (startPos.y + (endPos.y - startPos.y) * t / duration) + &lt;span style="color: maroon"&gt;&amp;quot;px&amp;quot;&lt;/span&gt;;
            $await(Jscex.Async.sleep(50));
        }

        e.style.left = endPos.x;
        e.style.top = endPos.y;
    }));

    $await(moveAsync({x:100, y:100}, {x:400, y:100}, 1000));
    $await(moveAsync({x:400, y:100}, {x:400, y:400}, 1000));
    $await(moveAsync({x:400, y:400}, {x:100, y:400}, 1000));
    $await(moveAsync({x:100, y:400}, {x:100, y:100}, 1000));
}));&lt;/pre&gt;

&lt;p&gt;内层的moveAsync函数直接使用外部函数的参数e，这代码绝对美观大方——至于是否真写这样的代码，就靠开发人员自身的考量了。&lt;/p&gt;

&lt;p&gt;话说回来，其实让现有的编辑器支持内嵌的Jscex函数并不困难，事实上原本“不支持”的原因也只是“没想太多”，就这么机械地写下来了。例如最核心的Jscex.compile，其“逻辑”大约是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;Jscex.compile = &lt;span style="color: blue"&gt;function &lt;/span&gt;(builderName, func) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;code = &lt;span style="color: maroon"&gt;&amp;quot;var f = &amp;quot; &lt;/span&gt;+ func.ToString() + &lt;span style="color: maroon"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;ast = UglifyJS.parse(code);

    &lt;span style="color: #006400"&gt;// [ &amp;quot;toplevel&amp;quot;, [ [ &amp;quot;var&amp;quot;, [ [ &amp;quot;f&amp;quot;, [...] ] ] ] ] ]
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;funcAst = ast[1][0][1][0][1];

    &lt;span style="color: #006400"&gt;// compile funcAst with builderName;
&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;简单地说，我是根据传入函数的AST生成代码，当然还有些中间过程，但总体而言的“输入”只是函数本身（外加个builderName）。而为了能够在“生成代码”的过程中继续编译内嵌函数，其实我们也只要支持“标准模式（即eval(Jscex.compile(...))这种形式）”的AST即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;compileStandardPattern(evalAst) {
    &lt;span style="color: #006400"&gt;// ...
&lt;/span&gt;}

Jscex.compile = &lt;span style="color: blue"&gt;function &lt;/span&gt;(builderName, func) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;funcCode = func.toString();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;evalCode = &lt;span style="color: maroon"&gt;&amp;quot;eval(Jscex.compile(&amp;quot; &lt;/span&gt;+ JSON.stringify(builderName) + &lt;span style="color: maroon"&gt;&amp;quot;, &amp;quot; &lt;/span&gt;+ funcCode + &lt;span style="color: maroon"&gt;&amp;quot;))&amp;quot;
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;evalCodeAst = UglifyJS.parse(evalCode);

    &lt;span style="color: #006400"&gt;// [ &amp;quot;toplevel&amp;quot;, [ [ &amp;quot;stat&amp;quot;, [ &amp;quot;call&amp;quot;, ... ] ] ] ]
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;evalAst = evalCodeAst[1][0][1];
    &lt;span style="color: blue"&gt;var &lt;/span&gt;newCode = compileStandardPattern(evalAst);

    &lt;span style="color: #006400"&gt;// ...
&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;compileStandardPattern方法会根据“标准模式”的AST输出展开后的代码。而对于从Jscex.compile调用中输入的函数对象，我们也将其构造为标准形式并生成AST。然后，compileStandardPattern方法内部在生成代码的时候，如果发现某一部分AST又恰好符合“标准模式”，则递归调用自身来生成内嵌Jscex函数的代码，最终便得到了充分展开的结果。&lt;/p&gt;

&lt;p&gt;当然，这么做和AOT编译器都有一个限制：由于其展开方式依靠静态代码分析，因此只能识别出“标准模式”的代码。例如，以下代码可以在JIT编译器下正常工作：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;compile = Jscex.compile;
&lt;span style="color: blue"&gt;var &lt;/span&gt;builderName = &lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;;
&lt;span style="color: blue"&gt;var &lt;/span&gt;func = &lt;span style="color: blue"&gt;function &lt;/span&gt;() { ... };
&lt;span style="color: blue"&gt;var &lt;/span&gt;newCode = compile(builderName, func);
&lt;span style="color: blue"&gt;var &lt;/span&gt;funcAsync = eval(newCode);&lt;/pre&gt;

&lt;p&gt;但显然，如果这段代码出现在某个Jscex函数内部，或是使用AOT编译器，便无法将其展开了。不过这个“限制”只是理论上的，我还没有想到不用“标准模式”的理由。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-compiler-update-support-nested-jscex-function.html#comments</comments>
      <pubDate>Fri, 29 Apr 2011 16:11:16 GMT</pubDate>
      <lastBuildDate>Fri, 29 Apr 2011 16:13:12 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>SNDACode及Jscex项目中文站</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-chinese-repository-sndacode.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-chinese-repository-sndacode.html</guid>
      <description>&lt;p&gt;之前有朋友问我，为什么&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex只有英文站&lt;/a&gt;却没有中文的。其实原因很简单：国际化的项目势必更容易推广，而且国内看的懂英文的同学肯定比懂中文的外国人多嘛。而且我也不希望在一个源码库里维护两种语言的说明，这样对于浏览者也会造成误解——但更不希望维护两个源码库。因此我理想中的情况便是：某个站点可以完整地同步我的源码库，并提供一个供我放置文档的地方。最近我的同事搞了一个&lt;a href="http://www.sndacode.com"&gt;SNDACode&lt;/a&gt;站点，公开了部分盛大员工的开源项目。虽然远不如&lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt;等类似站点来的完备，但恰好能满足我的需求，我自然很乐意将它作为&lt;a href="http://www.sndacode.com/projects/jscex"&gt;Jscex的中文站点&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;以下为SNDACode的&lt;a href="http://www.sndacode.com/doc/about.html"&gt;自述&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我们创建这样一个平台的目的是：希望通过它，通过我们负责任的、开放的心态，在一定程度上加强同行之间的交流、促进中国开源事业的良性有序发展。&lt;/p&gt;

  &lt;p&gt;目前SNDACode平台上所有的开源项目均由盛大内部员工贡献，或者由盛大内部项目组贡献，（如果没有指明开源协议）我们的所有项目都遵循《GNU通用公共许可协议》发布，这意味着，您享有本站开源项目的：运行、复制软件的自由，发行传播软件的自由，获得软件源码的自由，改进软件并将自己作出的改进版本向社会发行传播的自由。&lt;/p&gt;

  &lt;p&gt;在SNDACode，开发者们可以针对这个平台上开源的项目，讨论交流，提交Issue，进而参与到这些项目中来，从而使得这些项目，能够为更多的开发者带来价值。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;目前Jscex的详细说明文字也已经翻译为中文，发布在&lt;a href="http://www.sndacode.com/projects/jscex/wiki"&gt;项目的Wiki&lt;/a&gt;上。一般来说，中文文档会与英文版保持同步，视心情等原因也可能会有一至两天的延迟。希望有了中文资源以后，能够吸引更多的国内同学来使用Jscex。欢迎您在SNDACode上提出各种问题（当然最好还是在GitHub上提出英文的Issue），与我一齐参与到Jscex的建设中来。发起一个开源项目十分简单，几乎只是几分钟的事情，但最关键的还是看后续的努力。&lt;/p&gt;

&lt;p&gt;希望Jscex能够发展壮大，我认为这还是有一定希望的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-chinese-repository-sndacode.html#comments</comments>
      <pubDate>Wed, 27 Apr 2011 15:20:17 GMT</pubDate>
      <lastBuildDate>Wed, 27 Apr 2011 15:20:17 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>Jscex使用BSD授权协议正式发布</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html</guid>
      <description>&lt;p&gt;这次打算把Jscex好好搞一下了，其实很少会有技术方面的障碍能“轮到”我们去突破，但我觉得Jscex的确有机会，HTML 5、Node.js各个都是红火的玩意儿。前几天我花了两个晚上用半生不熟的中式英语写了一篇自认为比较完整的说明文字放到了Github上的项目首页上，没想到几个小时后便收到了&lt;a href="http://onilabs.com/stratifiedjs"&gt;StratifiedJS&lt;/a&gt;（一个与Jscex目标有些类似的项目）作者的邮件，提到了一些关于StratifiedJS的事情。我向他咨询了StratifiedJS的某些细节问题，也向他简单介绍了Jscex的实现原理。如今&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex已经使用BSD授权协议正式发布&lt;/a&gt;（中文站也会在近期推出），再进行一些细节上的优化便要开始作推广了。&lt;/p&gt;

&lt;p&gt;最近的一次优化便是去除不必要的Delay方法调用。Delay的目的是延迟某段代码的执行时间，确保它只会在合适的时间执行，这对于非“延迟”及有副作用的语言来说十分重要。在F#中Delay方法的签名如下（Jscex与之类似）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// async.Delay
&lt;/span&gt;((&lt;span style="color: blue"&gt;unit &lt;/span&gt;-&amp;gt; Async&amp;lt;'a&amp;gt;) -&amp;gt; Async&amp;lt;'a&amp;gt;)&lt;/pre&gt;

&lt;p&gt;Delay函数可以不断嵌套，从效果说来讲不会有所区别，例如：&lt;/p&gt;

&lt;pre class="code"&gt;builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;builder.Loop(...);
        });
    });
});&lt;/pre&gt;

&lt;p&gt;上面这段代码和直接一个builder.Loop相比不会对结果产生任何影响，但是显然，从生成代码的整洁、美观程度（这涉及到可调试性）及性能都会有所损耗。之前的Jscex编译器会生成不必要的Delay（当然也不会有上面那么夸张），那是因为以前的代码生成器只是直接遍历从UglifyJS解析器那里获得的AST，以此来生成代码。新的编译器重新调整了结构和策略，会先将UglifyJS的AST转化为一个中间形式——我把它叫做Jscex AST，然后再让代码生成器从Jscex AST生成代码。新的生成器在工作时，会略过某些delay节点，直接从它的自节点中生成代码，以此达到去除不必要的Delay方法调用的目的。&lt;/p&gt;

&lt;p&gt;举例来说，一个快速排序的Jscex函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0; x &amp;lt; array.length; x++) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0; y &amp;lt; array.length - x; y++) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;r = $await(compareAsync(array[y], array[y + 1]));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) {
                $await(swapAsync(array, y, y + 1));
            }
        }
    }
}));&lt;/pre&gt;

&lt;p&gt;它会生成如下的代码（与&lt;a href="http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html"&gt;之前的结果&lt;/a&gt;相比省去了一些Delay方法调用）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$_builder_$ = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;, $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;strike&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {&lt;/strike&gt;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Loop(
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;x &amp;lt; array.length;
                },
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    x++;
                },
                $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;strike&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {&lt;/strike&gt;
                        &lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0;
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Loop(
                            &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                &lt;span style="color: blue"&gt;return &lt;/span&gt;y &amp;lt; (array.length - x);
                            },
                            &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                y++;
                            },
                            $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Bind(compareAsync(...), &lt;span style="color: blue"&gt;function &lt;/span&gt;(r) {
                                    &lt;strike&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {&lt;/strike&gt;
                                        &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) {
                                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Bind(swapAsync(...), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                                &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Normal();
                                            });
                                        } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
                                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Normal();
                                        }
                                    });
                                });
                            }),
                            &lt;span style="color: blue"&gt;false
                        &lt;/span&gt;);
                    });
                }),
                &lt;span style="color: blue"&gt;false
            &lt;/span&gt;);
        });
    }));
})&lt;/pre&gt;

&lt;p&gt;当然，Jscex还是有进一步优化的空间。例如包括保留“没有bind操作”的代码块，以及“嵌套Jscex函数”等等。最近一段时间应该会不断有一些更新。如果您在用JavaScript开发异步程序，也不妨一起来使用Jscex吧，它毫无疑问能显著改善您的编程生活。&lt;/p&gt;

&lt;p&gt;&lt;font color="#ff0000"&gt;&lt;strong&gt;广告时间：&lt;/strong&gt;&lt;/font&gt;第四届nBazaar技术交流会将于4月23日（本周六）于畅星大厦（上海市浦东新区碧波路888号，地铁二号线张江高科站下，步行10分钟可达）副楼3楼会议厅举行，在此欢迎您的到来，请于下午1点前准时入场。没有报名的朋友也可以现场报名。由于领导支持，我们将在现场送出&lt;a href="http://bambook.sdo.com/"&gt;市场价999元的盛大Bambook&lt;/a&gt;一台作为幸运观众的礼品，此外图灵出版社也赞助了十几本图书将会送给积极发言的观众。更多详细信息请参考&lt;a href="http://nbazaar.org/"&gt;活动首页&lt;/a&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html#comments</comments>
      <pubDate>Thu, 21 Apr 2011 16:15:04 GMT</pubDate>
      <lastBuildDate>Thu, 21 Apr 2011 16:15:04 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>Jscex项目现状：UglifyJS解析器及AOT编译器</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;项目是我为了简化JavaScript异步的一个类库，支持任意JavaScript（ECMASCript 3）引擎。Jscex小巧而强大，可以极大地改善前端的AJAX及动画等场景的编程体验，同样也可以用在&lt;a href="http://nodejs.org/"&gt;node.js&lt;/a&gt;进行服务器开发。从产生Jscex的想法到现在也有几个月的时间了，也一直想设法进行推广。在思考过程也发现了它在实际生产中可能会遇到的问题，于是前两个星期的主要工作，便是针对这些问题进行优化。首先我将Jscex的JavaScript分析器从&lt;a href="https://github.com/mozilla/narcissus"&gt;Narcissus&lt;/a&gt;换成了&lt;a href="https://github.com/mishoo/UglifyJS"&gt;UglifyJS&lt;/a&gt;，并基于node.js开发了一个简单的AOT编译器。接下来我也打算写个稍微详细一点的介绍，然后在国外社区看看反响如何。&lt;/p&gt;

&lt;p&gt;Jscex的本质是一个用JavaScript编写的JavaScript编译器，因此我需要一个JavaScript实现的JavaScript解析器。我起初&lt;a href="http://blog.zhaojie.me/2010/11/narcissus-javascript-parser.html"&gt;选择了著名的Narcissus项目&lt;/a&gt;，但由于它用到了&lt;a href="http://en.wikipedia.org/wiki/SpiderMonkey_(JavaScript_engine)"&gt;SpiderMonkey&lt;/a&gt;的一些扩展，最终我使用的其实是&lt;a href="http://www.neilmix.com/narrativejs/doc/"&gt;NarrativeJS&lt;/a&gt;中旧版的Narcissus代码。我一直在设法减小Jscex核心的体积及执行速度（毕竟一个重要的场景是浏览器端），再加上不是很喜欢旧版Narcissus代码的解析结果，于是我也在不断寻找它的替代品。前段时间我发现了UglifyJS这个JavaScript压缩器，它的解析器移植于&lt;a href="http://marijn.haverbeke.nl/parse-js/"&gt;parse-js&lt;/a&gt;项目，后者是一个用Common Lisp实现的类库，因此输出结构也十分简单，一个“表”而已，&lt;a href="http://blog.zhaojie.me/2011/04/uglifyjs-has-a-good-javascript-parser.html"&gt;执行速度也大大领先于Narcissus&lt;/a&gt;，体积也更小。于是我花了一个周末的时间将Jscex编译器改写为基于UglifyJS的实现。&lt;/p&gt;

&lt;p&gt;在改写过程中，我也同样考虑了目标代码在压缩后的体积。我使用&lt;a href="http://code.google.com/closure/compiler/"&gt;Closure Compiler&lt;/a&gt;的“高级”模式压缩代码，一般来说Closure Compiler的高级模式很破坏代码，我&lt;a href="http://blog.zhaojie.me/2011/04/compress-javascript-with-google-closure-compiler-in-advance-mode.html"&gt;使用了各种方式&lt;/a&gt;来保证压缩后的代码能够正确执行。目前，如果您要在项目中使用Jscex编写异步程序，需要依次加载以下三个文件（它们都在项目源码的bin目录中）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;uglifyjs-parser.min.js：UglifyJS解析器，大小20K，gzip后8K。 &lt;/li&gt;

  &lt;li&gt;jscex.min.js：Jscex核心编译器，大小5.5K，gzip后1.8K。 &lt;/li&gt;

  &lt;li&gt;jscex.async.min.js：Jscex异步核心类库，大小2K，gzip后0.9K。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果您觉得gzip后10K左右的体积还是有些大，那么也可以使用目前已经提供的AOT编译器——虽然AOT编译器的原始目的并不是为了减小体积。&lt;/p&gt;

&lt;p&gt;Jscex改善异步编程的原理，在于让程序员直接编写代码，使用普通的编程思路来实现算法，包括是用try...catch来捕获异常等等，而不会因为异步所需要的回调将代码拆得支离破碎。例如我们要实现冒泡排序算法的动画演示，也只需要使用传统编码方式实现算法即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 标准算法&lt;/span&gt;
&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSort = &lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0; x &amp;lt; array.length; x++) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0; y &amp;lt; array.length - x; y++) {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(array[y] &amp;gt; array[y + 1]) {
                swap(array, y, y + 1);
            }
        }
    }
}

&lt;span style="color: green"&gt;// 演示动画&lt;/span&gt;
&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0; x &amp;lt; array.length; x++) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0; y &amp;lt; array.length - x; y++) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;r = $await(compareAsync(array[y], array[y + 1]));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) {
                $await(swapAsync(array, y, y + 1));
            }
        }
    }
}));&lt;/pre&gt;

&lt;p&gt;Jscex.compile会解析代码，并生成异步代码，并交给eval来解释执行。bubbleSortAsync和其中调用的compareAsync（比较两个元素大小，并暂停10毫秒）和swapAsync（交换两个元素，绘图，并暂停20毫秒）都是异步方法。但是无论在编写和使用上，异步方法和同步算法几乎没有区别——唯一的区别便是$await语句必须单起一行。这个限制一是为了保证开发人员可以明确分清普通的JavaScript代码及异步方法调用，二便是为了简化编译器的实现。例如，“理想情况”下类似以下的代码也需要支持：&lt;/p&gt;

&lt;pre class="code"&gt;f(g(1), $await(...))

&lt;span style="color: blue"&gt;if &lt;/span&gt;(x &amp;gt; y &amp;amp;&amp;amp; $await(...)) { ... }&lt;/pre&gt;

&lt;p&gt;尤其是第二行代码，$await可能由于短路而根本不会执行。为此，Jscex要求开发人员明确编写这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;a1 = g(1);
&lt;span style="color: blue"&gt;var &lt;/span&gt;a2 = $await(...);
f(a1, a2);

&lt;span style="color: blue"&gt;if &lt;/span&gt;(x &amp;gt; y) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;flag = $await(...);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(flag) { ... }
}&lt;/pre&gt;

&lt;p&gt;我并不担心这会让开发人员编写代码时有所不便，事实上F#的&lt;a href="http://msdn.microsoft.com/en-us/library/dd233250.aspx"&gt;Async Workflow&lt;/a&gt;是有这般要求，我甚至敢保证&lt;a href="http://blog.zhaojie.me/2010/10/pdc2010-the-future-of-csharp-and-vb-by-anders-hejlsberg-1.html"&gt;未来C#的异步特性&lt;/a&gt;也是类似的设计。但是，JavaScript有个重要的特点：它在实际使用时往往会被压缩。如果仅仅是去除空白字符，那么Jscex自然还可以正常工作。但事实上现代的JavaScript压缩工具都会分析代码的语义，并重新生成体积更小的代码。例如之前的bubbleSortAsync经过压缩便会成为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync=eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;,&lt;span style="color: blue"&gt;function&lt;/span&gt;(a){&lt;span style="color: blue"&gt;for&lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;b=0;b&amp;lt;a.length;b++)&lt;span style="color: blue"&gt;for&lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;c=0;c&amp;lt;a.length-b;c++){&lt;span style="color: blue"&gt;var &lt;/span&gt;d=$await(compareAsync(a[c],a[c+1]));d&amp;gt;0&amp;amp;&amp;amp;$await(swapAsync(a,c,c+1))}}))&lt;/pre&gt;

&lt;p&gt;试看d&amp;gt;0&amp;amp;&amp;amp;$wait(...)这段代码，完全就让Jscex无法工作了。为此，我为Jscex开发了AOT编译器（scripts目录下的jscexc.js及JscexExtractor.js文件），即在部署前便对代码进行编译并生成目标代码（之前是在运行时生成代码，即JIT编译）。AOT编译器同样使用JavaScript编写，使用node.js运行，这样便可以直接使用Jscex的编译器实现。与编译器核心不同，AOT编译器使用了最新版的Narcissus来解析代码，这是因为Narcissus能够提供更丰富的解析结果，我可以直接获得整个目标方法的起始和结束地址（不过有bug，我使用时绕开了），自然还包括原始代码，用起来十分方便。至于之前提到的依赖于SpiderMonkey扩展，体积较大，执行速度慢等缺点，对于AOT编译器来说便完全不是问题了。&lt;/p&gt;

&lt;p&gt;Jscex的AOT编译器使用起来十分简单：&lt;/p&gt;

&lt;pre class="code"&gt;node jscexc.js --input input_file --output output_file&lt;/pre&gt;

&lt;p&gt;例如，如果一个文件包含之前的bubbleSortAsync方法，那么经过AOT编译器之后，它的代码便会被替换成为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$_builder_$ = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Loop(
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;x &amp;lt; array.length;
                },
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    x++;
                },
                $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0;
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Loop(
                            &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                &lt;span style="color: blue"&gt;return &lt;/span&gt;y &amp;lt; (array.length - x);
                            },
                            &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                y++;
                            },
                            $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Bind(compareAsync(...), &lt;span style="color: blue"&gt;function &lt;/span&gt;(r) {
                                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                        &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) {
                                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Bind(swapAsync(...), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                                &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Normal();
                                            });
                                        } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
                                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Normal();
                                        }
                                    });
                                });
                            }),
                            &lt;span style="color: blue"&gt;false
                        &lt;/span&gt;);
                    });
                }),
                &lt;span style="color: blue"&gt;false
            &lt;/span&gt;);
        });
    });
})&lt;/pre&gt;

&lt;p&gt;再进行压缩，便不会产生任何问题了。从表面看起来，编译后的Jscex代码体积大了不少，但是其中大部分为重复架子代码，压缩比例一般也会比较大。使用AOT编译后的代码有以下几个好处：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;经过JavaScript压缩器处理后也能正确执行。 &lt;/li&gt;

  &lt;li&gt;运行时只需要加载一个极小的jscex.async.min.js文件（异步核心类库），gzip后大小不到1K。 &lt;/li&gt;

  &lt;li&gt;由于代码在发布前生成，节省了JIT编译的开销。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在我个人看来，目前的Jscex已经可以在一些比较正式的场合中使用了。Jscex功能强大，实现小巧，能够与其它类库同时使用（它只会在全局对象上产生一个Jscex对象），接下来我也会为jQuery或MooTools等著名JavaScript框架/类库提供Jscex的绑定。在此也希望您可以实际使用一下Jscex项目，如果遇到问题请及时与我联系，我会给予您必要的支持。&lt;/p&gt;

&lt;p&gt;&lt;span style="color: red"&gt;广告时间：&lt;/span&gt;&lt;a href="http://nbazaar.org/"&gt;第四届nBazaar技术交流会&lt;/a&gt;将于2011年4月23日举行。第四届交流会的形式将略作改变：除了三场演讲（Windows Phone 7、IDE插件开发、单点登陆解决方案设计与实现）之外，本次活动设有嘉宾互动环节，您将有机会和嘉宾就某些话题进行探讨。我们正在收集话题，也希望大家踊跃提问，具体信息详见&lt;a href="http://nbazaar.org/"&gt;http://nbazaar.org/&lt;/a&gt;。此外，nBazaar技术沙龙的邮件列表已经正式启用，所有用户也已添加完成（之前报名或参加过技术会议）。如果有任何疑问，请邮件至&lt;img class="embed" src="http://services.nexodyne.com/email/customicon/9xaGAYDnFdYrRqAdl1tTvrs%3D/Y4fIT8s%3D/000000/ffffff/000000/0/image.png" /&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html#comments</comments>
      <pubDate>Thu, 14 Apr 2011 18:04:38 GMT</pubDate>
      <lastBuildDate>Thu, 14 Apr 2011 18:09:53 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>基于Jscex.Async的JavaScript动画/游戏</title>
      <link>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html</link>
      <guid>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html</guid>
      <description>&lt;p&gt;首先和大家宣布一个消息，Jscex的代码已经&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;提交至Github上&lt;/a&gt;了，感兴趣的朋友下载来Dog Fooding一把，并欢迎提出反馈意见。Jscex受到F#计算表达式的启发，是一个面向JavaScript语言的monadic扩展，最常见的用途便是编写异步程序，尤其是逻辑复杂的异步程序。不过除此之外，使用这套异步库来编写动画或是游戏也是十分容易的事情。例如，一个人物的走动或是爆炸效果，其实可以视为一个贴图随时间不断变化的过程。这个变化的过程是异步的，但是有了Jscex.Async，我们只需使用最直接的同步形式编写代码就行了。&lt;/p&gt;

&lt;p&gt;例如这里有个&lt;a href="http://files.zhaojie.me/demos/jscex/samples/async/bullet.html"&gt;我用一个多小时编写的示例&lt;/a&gt;（需要支持canvas的浏览器，例如IE 9，FireFox，Chrome，Safari等等），类似于“是男人就撑过20秒”，其中有这么一段代码：&lt;/p&gt;

&lt;pre class="code"&gt;BulletGame.prototype._bulletFlyAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(f) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;options = &lt;span style="color: blue"&gt;this&lt;/span&gt;._options;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;bullet = {pos: {x: 0, y: 0}};

    &lt;span style="color: blue"&gt;this&lt;/span&gt;._addBullet(bullet);
    
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; &lt;span style="color: blue"&gt;this&lt;/span&gt;._playing &amp;amp;&amp;amp; &lt;span style="color: blue"&gt;this&lt;/span&gt;._inArea(bullet.pos); t += 20) {
        $await(&lt;span style="color: blue"&gt;this&lt;/span&gt;._timer.setTimeoutAsync(20));
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._tryHit(bullet.pos); &lt;span style="color: green"&gt;// 判断是否击中&lt;/span&gt;
        bullet.pos = f(t); &lt;span style="color: green"&gt;// 改变子弹位置&lt;/span&gt;
    }

    &lt;span style="color: blue"&gt;this&lt;/span&gt;._removeBullet(bullet);
}));&lt;/pre&gt;

&lt;p&gt;这段代码是一个异步程序，表示一颗子弹的飞行过程。子弹的飞行轨迹（某一时刻的位置）由函数f来决定，因此我们只需要写一个for循环，并且在循环内部“暂停”一段时间就行了，就这么简单。在循环之前，我们将子弹添加到一个容器里，这样在一个不断循环的paint方法里就会把这颗子弹绘制在canvas上面。循环结束后，我们将子弹从容器中移出。这一切都十分顺其自然，虽然这段代码的执行过程完全是异步的。&lt;/p&gt;

&lt;p&gt;Jscex可以使用各种方式与现有代码组合至一块儿，例如这里我为BulletGame的prototype对象扩展了一个方法，为此我晚上还改进了一下Jscex的编译逻辑，以及Jscex.Async的实现，其目的就是在_bulletFlyAsync这样的函数内部保留this对象在JavaScript语义上的正确性。JavaScript语言中this引用的动态特征是种很强大的特性，但是对于“语言改造者”来说，在一个充满回调的过程中保持语义正确并不是件十分容易的事情（当然，也不算十分困难，只要“想明白”即可）。上面的代码经过编译之后，就类似于我们直接编写了这样的代码（暂时如此，未来会有修改和优化）：&lt;/p&gt;

&lt;pre class="code"&gt;BulletGame.prototype._bulletFlyAsync = &lt;span style="color: blue"&gt;function &lt;/span&gt;(f) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;options = &lt;span style="color: blue"&gt;this&lt;/span&gt;._options;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;bullet = { pos: { x: 0, y: 0} };
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._addBullet(bullet);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Combine(
            $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0;
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Loop(
                    &lt;span style="color: blue"&gt;function &lt;/span&gt;() { &lt;span style="color: blue"&gt;return this&lt;/span&gt;._playing &amp;amp;&amp;amp; &lt;span style="color: blue"&gt;this&lt;/span&gt;._inArea(bullet.pos); },
                    &lt;span style="color: blue"&gt;function &lt;/span&gt;() { t += 20; },
                    $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(&lt;span style="color: blue"&gt;this&lt;/span&gt;._timer.setTimeoutAsync(20), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                            &lt;span style="color: blue"&gt;this&lt;/span&gt;._tryHit(bullet.pos);
                            bullet.pos = f(t);
                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
                        });
                    })
                );
            }),
            $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;this&lt;/span&gt;._removeBullet(bullet);
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
            })
        );
    });
};&lt;/pre&gt;

&lt;p&gt;此外，JavaScript是一个很容易编写此类动画或是游戏的平台，因为它所有的代码都在UI线程上执行，因此不会有任何多线程方面的问题。例如上面的代码，我们可以放心地向容器里添加或删除“子弹”对象，试想在一个并发环境里构造一个线程安全的双向链表是件多么麻烦的事情。&lt;/p&gt;

&lt;p&gt;最大的问题其实是浏览器环境里的性能问题，不过我对此并不怎么担心，因为V8已经给我们开了个好头，并且还在&lt;a href="http://blog.chromium.org/2010/12/new-crankshaft-for-v8.html"&gt;不断前进&lt;/a&gt;。此外对于动画和游戏来说，最大的性能问题更可能是canvas的绘图性能。现在这个简单的例子性能自然不会有太大问题，不过一旦加上贴图和素材，性能问题就会凸现出来了。对于目前的示例，我在安装Windows 7的台式机里试验了多种浏览器，帧数（fps）各有高低：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Firefox：110左右 &lt;/li&gt;

  &lt;li&gt;Chrome：210左右 &lt;/li&gt;

  &lt;li&gt;IE 9：300至800不等，比较常见的是500左右 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而在我的MBP上：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Firefox：70左右 &lt;/li&gt;

  &lt;li&gt;Safari：60左右 &lt;/li&gt;

  &lt;li&gt;Chrome：190左右 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;此外还有同事测试了Safari最新的Nightly Build，帧数与Chrome不相上下。从中可以看出，IE 9的表现最为出色，可见它的GPU加速的确不是在吹牛的。值得一提的是，如果图像上没有子弹，那么帧数大约只有60出头，一旦同时出现了几十颗子弹，帧数变会飙升至500以上，峰值甚至可以到800。由此推测，IE 9也是在“按需”使用GPU，十分周全。&lt;/p&gt;

&lt;p&gt;Jscex的源码及示例都在Github上，欢迎尝试。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html#comments</comments>
      <pubDate>Wed, 15 Dec 2010 17:03:28 GMT</pubDate>
      <lastBuildDate>Thu, 16 Dec 2010 05:14:09 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>运行在.NET/Mono上的Readability</title>
      <link>http://blog.zhaojie.me/2010/11/readability-runs-on-dotnet.html</link>
      <guid>http://blog.zhaojie.me/2010/11/readability-runs-on-dotnet.html</guid>
      <description>&lt;p&gt;之前我在《&lt;a href="http://blog.zhaojie.me/2010/08/get-a-better-style-for-reading.html"&gt;改善自己的阅读体验&lt;/a&gt;》推荐使用&lt;a href="http://lab.arc90.com/experiments/readability/"&gt;Readability&lt;/a&gt;这个小工具。它是一段JavaScript脚本，通过在浏览器的页面上运行，提取出文章正文部分，并通过一种干净清爽的形式展示给用户。那么，如果我们在服务器端得到了一个HTML字符串，又该如何得到它的可读部分？直接&lt;a href="http://blog.zhaojie.me/2010/11/execute-javascript-at-server.html"&gt;在服务器端执行JavaScript&lt;/a&gt;不太可行，因为Readability依赖浏览器的DOM结构及相关API。如果调用WebKit和IE的浏览器内核又需要大动干戈，也很难跨平台。因此，我基于&lt;a href="http://htmlagilitypack.codeplex.com/"&gt;HtmlAgilityPack&lt;/a&gt;将Readability的部分算法移植到了C#上。&lt;/p&gt;

&lt;p&gt;我们知道，把一个页面的HTML解析为DOM树并不是件容易的事情，因为HTML几乎不会是标准的XML，各种容错必不可少，否则寸步难行。HtmlAgilityPack是一个非常有用的类库，提供了近乎浏览器的解析功能以及各种DOM操作，只需简单补充几行代码便可以对应几乎浏览器上所有的DOM操作。更重要的是，它只是依赖了.NET 2.0中的XPath实现，完全不涉及MSHTML.dll，ActiveX或是COM等非托管代码，为我们移植Readability提供了良好的基础。&lt;/p&gt;

&lt;p&gt;Readability的算法并不复杂，我主要移植了它的getArticleTitle及grabArticle两个方法，分别用于获取文章标题及页面内容。不过值得一提的是，我发现它的某些算法还是为英文内容服务的。例如在获取文章标题时，它会按照空格分割文档的标题，查看其中有多少个单词，并进行一些处理，而对于中文内容很显然是不合适的（因此对于中文页面，捕获到的标题往往就是页面标题）。同样，在计算某个节点是否是文章正文的时候，其中“英文逗号”的数量也是分值的一部分，我在移植的时候也添加了“中文逗号”。&lt;/p&gt;

&lt;p&gt;目前我的移植成果放在了github上，称为&lt;a href="https://github.com/JeffreyZhao/NReadability"&gt;NReadability&lt;/a&gt;，可以从一个HTML字符串中获得标题和正文内容。包括阅读Readability的代码在内，移植工作大约花了我三个小时的时间，其中的算法几乎和JavaScript代码完全对应，不过并不完整，还有些细微之处没有移植（甚至还没完全去除样式），有时间我会慢慢补上。从效果上看执行结果还是比较令人满意的，使用起来也非常简单，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;documentHtml = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebClient&lt;/span&gt;().DownloadString(&lt;span style="color: #a31515"&gt;&amp;quot;http://...&amp;quot;&lt;/span&gt;);
&lt;span style="color: blue"&gt;var &lt;/span&gt;readability = &lt;span style="color: #2b91af"&gt;Readability&lt;/span&gt;.Create(documentHtml);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(readability.Title);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(readability.Content);&lt;/pre&gt;

&lt;p&gt;我写了个最最简单的&lt;a href="http://blog.zhaojie.me/pages/readability.aspx"&gt;示例程序&lt;/a&gt;部署在我的博客上（Mono 2.8，ASP.NET 4），您可以尝试一下。目前这个页面在下载URL内容时直接使用了UTF-8编码，因此对于某些腌臜泼皮的中文页面会得到乱码。您可以使用英文页面，或是比较正常的中文页面（例如InfoQ，博客园，CSDN等等）进行试验。如果结果与您的预期不符，也请在评论或是在推特上回复一下，谢谢。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/11/readability-runs-on-dotnet.html#comments</comments>
      <pubDate>Fri, 26 Nov 2010 03:30:09 GMT</pubDate>
      <lastBuildDate>Fri, 26 Nov 2010 08:38:20 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>在传统.NET程序中使用Silverlight SDK里的JSON类库</title>
      <link>http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html</link>
      <guid>http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html</guid>
      <description>&lt;p&gt;话说在Silverlight SDK中提供了一套JSON类库，叫做&lt;a href="http://msdn.microsoft.com/en-us/library/system.json(VS.95).aspx"&gt;System.Json&lt;/a&gt;。这个类库功能很简单，就是使用.NET来表示JSON格式的“结构”。换句话说，就是我之前&lt;a href="http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html"&gt;在JsonMe中所提到的&lt;/a&gt;JsonObject，JsonArray之类的东西，但完全不包括JSON结构和实际类型之间的转化。虽然这个类库很不好用（谁用谁知道），但至少是一个可以通用于Silverlight和MonoTouch的类库，因此我决定将JsonMe基于它进行构建。为此，我&lt;a href="http://github.com/JeffreyZhao/System.Json"&gt;对mono中的开源实现进行了移植&lt;/a&gt;，使它仅仅依赖于功能最基本的.NET Framework 3.5 Client Profile，并修改了其中的一些明显的Bug。&lt;/p&gt;

&lt;p&gt;Mono的项目源码可以&lt;a href="http://github.com/mono/mono"&gt;从github里获得&lt;/a&gt;，或者您直接&lt;a href="http://ftp.novell.com/pub/mono/sources-stable/"&gt;下载mono 2.8的源码tar包&lt;/a&gt;，不过现在我们&lt;a href="http://github.com/mono/mono/tree/master/mcs/class/System.Json/System.Json/"&gt;只需要其中的System.Json&lt;/a&gt;。System.Json，只有五个文件，不过它依赖了mono的&lt;a href="http://github.com/mono/mono/tree/master/mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/"&gt;System.ServiceModel.Web.dll里定义&lt;/a&gt;的JavaScriptReader和JavaScriptObjectDeserializer类，因此我也将这两个内部类一并移植到System.Json项目中。因此，最终项目的组织结构如下：&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/system-json-solution.png" /&gt; 

&lt;p&gt;从上图中可以看出，如今的System.Json项目只依赖了极少的系统类库，事实上它可以在.NET Framework 3.5 Client Profile下运行，这样无论是Silverlight还是普通的客户端都可以使用它的功能，这是依赖System.Web.Extensions.dll的时候所无法做到的。同样，JsonMe也已经进行了相应的改写，现在它已经能够用于Silverlight，MonoTouch，甚至未来的MonoDroid中。不过基于System.Json之后JsonMe反而因此变得复杂了，这是因为System.Json实在不是一个好用的类库。&lt;/p&gt;

&lt;p&gt;mono的System.Json还存在一个bug：JsonObject的Add方法不允许value为null，这是个十足的问题，使得类库无法支持null值。我使用.NET Reflector检查了Silverlight中的实现，它并没有这样的约束，这才符合我的预期。将这个约束去除以后，序列化成JSON字符串的逻辑便会引发NullReferenceException，因此我也修改了对应的序列化逻辑。此外，如果您用DateTime创建一个JsonPrimitive类型，序列化时也会出现问题。我还不太清楚Silverlight SDK中是如何处理这个情况的，再我看来在JSON中放置DateTime也是一个很奇怪的行为，还是先转化为字符串吧。杯具的是，MonoTouch SDK中也有这些bug，不幸中的万幸，只要在MonoTouch重新编译&lt;a href="http://github.com/JeffreyZhao/System.Json"&gt;我移植的代码&lt;/a&gt;就行了。&lt;/p&gt;

&lt;p&gt;如果您对此感兴趣，也不妨&lt;a href="http://github.com/JeffreyZhao/System.Json"&gt;获取这些代码&lt;/a&gt;捣鼓捣鼓。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html#comments</comments>
      <pubDate>Tue, 12 Oct 2010 09:34:41 GMT</pubDate>
      <lastBuildDate>Tue, 12 Oct 2010 09:34:41 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>JsonMe - 合约与类型分离的轻量级JSON映射类库</title>
      <link>http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html</link>
      <guid>http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html</guid>
      <description>&lt;p&gt;JSON全称为JavaScript Object Notation，原本作为JavaScript语言中用于表示对象结构的文本形式。不过目前JSON成功地脱离了JavaScript语言，它已经成为一种运用十分广泛的数据交换格式。从表面看来，目前用于某个对象与JSON格式之间相互转化的解决方案已经有了许多种，例如在.NET平台上，我们可以使用ASP.NET AJAX中引入的JavaScriptSerializer，WCF中引入的DataContractJsonSerializer，亦或是&lt;a href="http://json.codeplex.com/"&gt;Json.NET&lt;/a&gt;。但是，最近我忽然发现这些类库都无法满足我的要求，因此，我今天花了一点时间，写了一个非常简单的对象与JSON格式相互转化的类库，是为&lt;a href="http://github.com/JeffreyZhao/JsonMe"&gt;JsonMe&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;现有解决方案的不足&lt;/h1&gt;

&lt;p&gt;您可能会感到疑惑，难道现有的解决方案都不够好吗？又搞一个新的实现出来，这不是重复造轮子嘛。但事实上，它们在我眼里，都有一些难以逾越的障碍。就拿JavaScriptSerializer来说，它使用起来十分简单，配合C#的匿名对象特性，输出一个JSON格式可谓无比直接：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;value = &lt;span style="color: blue"&gt;new
&lt;/span&gt;{
    hello = &lt;span style="color: #a31515"&gt;&amp;quot;world&amp;quot;&lt;/span&gt;,
    array = &lt;span style="color: blue"&gt;new object&lt;/span&gt;[] { 1, 2, 3, &lt;span style="color: #a31515"&gt;&amp;quot;jeffz&amp;quot; &lt;/span&gt;}
};

&lt;span style="color: blue"&gt;var &lt;/span&gt;json = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JavaScriptSerializer&lt;/span&gt;().Serialize(value);&lt;/pre&gt;

&lt;p&gt;JavaScriptSerializer也支持JSON格式与某种类型的对象相互转化，甚至可以加上ScriptIgnoreAttribute标记来忽略某个属性，例如：&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;Post
&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;span style="color: #2b91af"&gt;ScriptIgnore&lt;/span&gt;]
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Content { &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;DateTime &lt;/span&gt;CreateTime { &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;如果我想要改变某个属性在JSON中的字段名呢？JavaScriptSerializer应该也做得到，但我现在我一时想不起来了，也懒得去查。我知道DataContractJsonSerializer一定支持，事实上它是.NET中用于代替JavaScriptSerializer的JSON序列化解决方案，为此如今的JavaScriptSerializer已经标记了ObsolateAttribute，编译时您应该可以看到warning。&lt;/p&gt;

&lt;p&gt;那么我再提一个要求：&lt;font color="#ff0000"&gt;在序列化某个字段的时候，对它的值进行一个简单的转化&lt;/font&gt;。例如，我希望将DateTime对象在JSON中表现为字符串，这对于JavaScriptSerializer和DataContractJsonSerializer来说似乎就做不到了，至少会十分麻烦。但是这对于Json.NET来说不是个问题，相比于前两者来说，Json.NET可谓是JSON解决方案中的Word和Excel，经过了许多版本的积极开发，如今的Json.NET已经实现了大量的功能，几乎在每一处都提供了扩展点。我对Json.NET的了解不多，不过在简单浏览代码以后，我发现它的复杂度已经能和如今的ASP.NET MVC比肩了，是不是显得有些可怕？我也这么觉得。&lt;/p&gt;

&lt;p&gt;但事实上，让我重新写一个JsonMe的关键，是因为我希望&lt;font color="#ff0000"&gt;同一种类型能够与不同的JSON格式相互转化&lt;/font&gt;。试想，我有一个User类型，但是我却有两个不同格式的JSON数据源。或者说，相同的User对象，需要根据环境得到两种不同格式的JSON输出。这样JavaScriptSerializer或DataContractJsonSerializer就无法满足我的要求了，因为它们使用自定义特性来控制JSON的格式，但一个类型只能应用一种JSON策略，这又让我如何是好？&lt;/p&gt;

&lt;p&gt;当然，这点对于Json.NET应该不是问题，但是它实在太复杂了，如果我能把它研究透彻并加以扩展，还不如自己重新去写一个简单的类库呢。于是，一个秋高气爽的周日下午，JsonMe就此诞生了。&lt;/p&gt;

&lt;h1&gt;JsonMe使用方式&lt;/h1&gt;

&lt;p&gt;为了实现以上的要求，在JsonMe中我将JSON转化的“合约”与类型本身分离。我很喜欢这样的策略，正如&lt;a href="http://github.com/JeffreyZhao/EasyMongo"&gt;EasyMongo&lt;/a&gt;那样，除了一个类型可以对应多种映射策略之外，我还能将一种类型与它的映射策略解耦。例如，现在有一个User对象：&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;User
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;UserName { &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 int &lt;/span&gt;Age { &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 bool &lt;/span&gt;IsAdult { &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return this&lt;/span&gt;.Age &amp;gt;= 18; } }
}&lt;/pre&gt;

&lt;p&gt;在进行序列化之前，需要先定义这样的“合约”（即映射策略）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; userContract = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonContract&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;User&lt;/span&gt;&amp;gt;();
userContract.SimpleProperty(u =&amp;gt; u.UserName).Name(&lt;span style="color: #a31515"&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;);
userContract.SimpleProperty(u =&amp;gt; u.Age);&lt;/pre&gt;

&lt;p&gt;User类型中定义了三个属性，其中我们只采纳了两个：Age和UserName（并改名为Name）。利用这个“合约”我们可以进行JSON转化：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;user = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;User &lt;/span&gt;{ UserName = &lt;span style="color: #a31515"&gt;&amp;quot;Tom&amp;quot;&lt;/span&gt;, Age = 20 };
&lt;span style="color: blue"&gt;var &lt;/span&gt;jsonUser = &lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(user, userContract);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(jsonUser);&lt;/pre&gt;

&lt;p&gt;这将会输出（已经过手动格式化）：&lt;/p&gt;

&lt;pre class="code"&gt;{
    &amp;quot;Name&amp;quot; : &amp;quot;Tom&amp;quot;,
    &amp;quot;Age&amp;quot; : 20
}&lt;/pre&gt;

&lt;p&gt;同样，我们可以为某个属性运用转化规则，只要提供一个IJsonConverter对象即可：&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;Post
&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;span style="color: blue"&gt;public string &lt;/span&gt;Content { &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;DateTime &lt;/span&gt;CreateTime { &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 class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DataTimeConverter &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IJsonConverter
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public object &lt;/span&gt;ToJsonValue(&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;type, &lt;span style="color: blue"&gt;object &lt;/span&gt;value)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;((&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;)value).ToString(&lt;span style="color: #a31515"&gt;&amp;quot;R&amp;quot;&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;public object &lt;/span&gt;FromJsonValue(&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;type, &lt;span style="color: blue"&gt;object &lt;/span&gt;value)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.ParseExact((&lt;span style="color: blue"&gt;string&lt;/span&gt;)value, &lt;span style="color: #a31515"&gt;&amp;quot;R&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    }
}&lt;/pre&gt;

&lt;p&gt;定义合约：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; postContract = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonContract&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Post&lt;/span&gt;&amp;gt;();
postContract.SimpleProperty(p =&amp;gt; p.Title);
postContract.SimpleProperty(p =&amp;gt; p.CreateTime).Converter(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DataTimeConverter&lt;/span&gt;());&lt;/pre&gt;

&lt;p&gt;在这里，DataTimeConverter会将CreateTime属性的时间日期转化为字符串以后再输出。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;post = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Good day today.&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now };
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(post, postContract));
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine();&lt;/pre&gt;

&lt;p&gt;便会得到：&lt;/p&gt;

&lt;pre class="code"&gt;{
    &amp;quot;Title&amp;quot; : &amp;quot;Good day today.&amp;quot;,
    &amp;quot;CreateTime&amp;quot; : &amp;quot;Sun, 10 Oct 2010 23:12:53 GMT&amp;quot;
}&lt;/pre&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;User &lt;/span&gt;Author { &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;Post&lt;/span&gt;&amp;gt; Posts { &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;我们在定义Category的合约时，则可以用到之前的合约：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; categoryContract = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonContract&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;();
categoryContract.SimpleProperty(p =&amp;gt; p.Name);
categoryContract.ComplexProperty(p =&amp;gt; p.Author).Contract(userContract);
categoryContract.ArrayProperty(p =&amp;gt; p.Posts).ElementContract(postContract);&lt;/pre&gt;

&lt;p&gt;那么下面这段代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;category = &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;&amp;quot;Default&amp;quot;&lt;/span&gt;,
    Author = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;User &lt;/span&gt;{ UserName = &lt;span style="color: #a31515"&gt;&amp;quot;Jerry&amp;quot;&lt;/span&gt;, Age = 15 },
    Posts = &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;Post&lt;/span&gt;&amp;gt;
    {
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Post 1&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;(2010, 1, 1) },
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Post 2&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;(2010, 2, 1) },
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Post 3&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;(2010, 3, 1) }
    }
};
&lt;span style="color: blue"&gt;var &lt;/span&gt;jsonCategory = &lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(category, s_categoryContract);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(jsonCategory);&lt;/pre&gt;

&lt;p&gt;将会输出：&lt;/p&gt;

&lt;pre class="code"&gt;{
    &amp;quot;Name&amp;quot; : &amp;quot;Default&amp;quot;,
    &amp;quot;Author&amp;quot; :
    {
        &amp;quot;Name&amp;quot; : &amp;quot;Jerry&amp;quot;,
        &amp;quot;Age&amp;quot; : 15
    },
    &amp;quot;Posts&amp;quot; :
    [
        {
            &amp;quot;Title&amp;quot; : &amp;quot;Post 1&amp;quot;,
            &amp;quot;CreateTime&amp;quot; : &amp;quot;Fri, 01 Jan 2010 00:00:00 GMT&amp;quot;
        },
        {
            &amp;quot;Title&amp;quot; : &amp;quot;Post 2&amp;quot;,
            &amp;quot;CreateTime&amp;quot; : &amp;quot;Mon, 01 Feb 2010 00:00:00 GMT&amp;quot;
        },
        {
            &amp;quot;Title&amp;quot; : &amp;quot;Post 3&amp;quot;,
            &amp;quot;CreateTime&amp;quot; : &amp;quot;Mon, 01 Mar 2010 00:00:00 GMT&amp;quot;
        }
    ]
}&lt;/pre&gt;

&lt;p&gt;当然，您同样可以定义一个匿名对象作为JSON输出：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; value = &lt;span style="color: blue"&gt;new &lt;/span&gt;{ v = &lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(category, categoryContract) };
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.Serialize(value));&lt;/pre&gt;

&lt;p&gt;是不是很简单？&lt;/p&gt;

&lt;h1&gt;更多讨论&lt;/h1&gt;

&lt;p&gt;以上代码只是演示了序列化成JSON的功能，但是您应该也可以了解反序列化的使用方式。目前JsonMe的功能就只有这么多，可以说非常简单，但是我认为已经基本够用，甚至在大部分情况下完整代替JavaScriptSerializer和DataContractJsonSerialzier是没有什么问题的。&lt;/p&gt;

&lt;p&gt;开发JsonMe恰好花了我五个小时（下午2点到7点），只有几百行代码（当然还很粗糙），大部分还是用于合约配置的“骨架”，真正进行对象属性的赋值和转化的代码只有一百多行。JsonMe能够如此简约的原因，是因为站在JavaScriptSerializer的肩膀上。说实话，JavaScriptSerializer其实提供了一个很好的基础，因为它可以将一段JSON字符串转化为Dictionary&amp;lt;string, object&amp;gt;与object数组间的嵌套，这正是JSON格式的本质。当然，为了实现简单，我在JsonMe中创建了JsonObject和JsonArray两个对象，分别继承Dictionary&amp;lt;string, dynamic&amp;gt;和List&amp;lt;dynamic&amp;gt;，它们便作为JSON结构的表现形式。&lt;/p&gt;

&lt;p&gt;不过我也意识到，JavaScriptSerializer可能并不是一个合适的选择，因为这会让我们依赖System.Web.Extensions.dll。事实上在.NET平台上有一个更独立，更简单的JSON结构实现，那就是&lt;a href="http://msdn.microsoft.com/en-us/library/system.json(VS.95).aspx"&gt;Silverlight中的System.Json.dll&lt;/a&gt;。只可惜我们只能用它开发Silverlight程序。我打算在合适的时候，将mono中的System.Json.dll实现移植到.NET 3.5中，这样JsonMe就可以摆脱对System.Web.Extensions.dll的依赖，并摆脱自定义的JsonObject和JsonArray，可以直接使用System.Json里的结构（&lt;a href="http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html"&gt;已完成&lt;/a&gt;）。更重要的是，这可以让JsonMe作用在Silverlight，甚至是&lt;a href="http://blog.zhaojie.me/2010/09/develop-ios-app-with-monotouch-in-visual-studio-1.html"&gt;基于MonoTouch的iOS开发&lt;/a&gt;中（很有可能还包括未来的&lt;a href="http://monodroid.net/"&gt;MonoDroid&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;如果您感兴趣的话，也不妨&lt;a href="http://github.com/JeffreyZhao/JsonMe"&gt;获取JsonMe的源代码和简单示例&lt;/a&gt;，修改修改，尝试尝试。我认为它还是相当实用的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html#comments</comments>
      <pubDate>Sun, 10 Oct 2010 16:29:24 GMT</pubDate>
      <lastBuildDate>Sun, 10 Oct 2010 16:29:24 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/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/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/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>
    <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/url-building-method-for-domainroute.html</link>
      <guid>http://blog.zhaojie.me/2009/08/url-building-method-for-domainroute.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2009/08/url-routing-with-domain.html"&gt;上一篇文章&lt;/a&gt;中我们构造了DomainRoute类，这是一个将URL Routing扩展至域名的Route组件，于是现在我们便可以轻易地从一个URL的Domain部分中捕获数据并在程序中使用。不过作为URL Routing的另一个重要部分，在URL构建方面，我们还需给DomainRoute补充额外的支持。&lt;/p&gt; &lt;p&gt;我想再次强调一下，URL Routing的功能并不只是从URL中捕获数据，它还有一个作用便是根据数据组装出一个URL。简单地说，URL Routing的功能是“双向”的。这也是为什么默认的Route组件会使用{controller}/{action}这样的简单模式，而不是常用的成熟的正则表达式，这就是因为正则表达式能够根据模式来匹配出值来，但是无法根据“值”反过来构建一个字符串。&lt;/p&gt; &lt;p&gt;在编写ASP.NET MVC的视图时，我们需要在HTML中填充链接的URL。在最初的时刻，我们会直接放上“约定”好的字符串，但是更好的方式显然是使用辅助方法。记得在搞URL Rewrite时，常常有朋友问我“页面中填写的URL怎么变成‘漂亮’的样子”，对此我只能说“没办法，直接写吧”，接着自然是一阵大改。而通过辅助方法来构造URL，由于我们只要向URL Routing提供数据，而Route会自动根据配置构造出一个可供自己识别的URL。如果我们想改变URL样式，只要在一个地方改变URL Routing的配置即可，页面中所有的URL便会同时改变了。&lt;/p&gt; &lt;p&gt;我认为，这也是遵守DRY原则的经典示例之一。&lt;/p&gt; &lt;p&gt;在ASP.NET MVC中已经自带了一部分构造URL所使用的辅助方法，例如在UrlHelper类中已经包含了Action方法（以及许多重载）。但是这些个方法并不适合FormatRoute。因为在这些实现最终都使用了RouteCollection的GetVirtualPath方法，它会把Route所得到的URL作为“虚拟路径”对待，这意味着FormatRoute返回了&lt;a href="http://www.cnblogs.com/JeffreyZhao/"&gt;http://www.cnblogs.com/JeffreyZhao/&lt;/a&gt;这样的URL之后，也会当作是一个普通的Path，最终在页面上便会出现“/http:/www.cnblogs.com/JeffreyZhao/”这样的URL，这显然是错误的。&lt;/p&gt; &lt;p&gt;这样看来，难道ASP.NET Routing本身是“不打算”支持域名的吗？似乎我们又不仅仅是在“扩展”，又出现“Hack”的意味了。&lt;/p&gt; &lt;p&gt;不过知道了错误原因之后，修改起来就非常容易了。例如，我们可以为UrlHelper补充一个扩展方法叫做ActionEx，实现起来也就几行代码：&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;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;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;VirtualPathData &lt;/span&gt;GetPath(
    &lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteCollection &lt;/span&gt;routes,
    &lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext,
    &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values)
{
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;RouteBase &lt;/span&gt;r &lt;span style="color: blue"&gt;in &lt;/span&gt;routes)
    {
        &lt;span style="color: #2b91af"&gt;VirtualPathData &lt;/span&gt;pathData = r.GetVirtualPath(requestContext, values);
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(pathData != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;pathData;
        }
    }

    &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;"Invalid values for building URL."&lt;/span&gt;);
}

&lt;span style="color: blue"&gt;private static bool &lt;/span&gt;IsAbsolute(&lt;span style="color: blue"&gt;string &lt;/span&gt;url)
{
    &lt;span style="color: blue"&gt;return
        &lt;/span&gt;url.StartsWith(&lt;span style="color: #a31515"&gt;"http://"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;StringComparison&lt;/span&gt;.InvariantCultureIgnoreCase) ||
        url.StartsWith(&lt;span style="color: #a31515"&gt;"https://"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;StringComparison&lt;/span&gt;.InvariantCultureIgnoreCase);
}
&lt;/pre&gt;
&lt;p&gt;要做的事情还是分三步走：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;收集route values 
&lt;li&gt;从route配置中获得URL 
&lt;li&gt;处理URL并返回&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;根据FormatRoute的逻辑，返回的URL可能有两种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果目标地址与当前请求的域名相同，则地址为一个相对路径，如“Home/Index/5”。 
&lt;li&gt;如果目标地址与当前请求的域名不同，则地址为一个绝对路径，如“http://space.cnblogs.com/Home/Index/5”。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;两种地址的处理方式自然不同，而我们目前的处理方式是最简单的：在相对路径之前加一个斜杠“/”。很明显，这里做了一个“假设”，那就是我们的ASP.NET MVC应用程序是部署在域名的根目录下的（似乎DomainRoute本身也有这样的要求）。如果您的应用程序部署在一个虚拟目录下，这部分逻辑自然是需要修改的。不过作为大部分应用来说，现在的功能应该已经足够使用了。&lt;/p&gt;
&lt;p&gt;试验一番吧。例如我们使用这样的Route配置：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;defaults = &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;, 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: 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;, defaults, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MvcRouteHandler&lt;/span&gt;())
    .WithDomain(&lt;span style="color: #a31515"&gt;"http://{area}.{*domain}"&lt;/span&gt;);
routes.Add(&lt;span style="color: #a31515"&gt;"Default"&lt;/span&gt;, route);
&lt;/pre&gt;
&lt;p&gt;然后请求http://www.jeffz-test.com/Home/Index，并在相应视图中写下这样的代码：&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("List", null) &lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;"&amp;gt;&lt;/span&gt;List&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;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("LogIn", new { id = 10, area = "account" })&lt;span style="background: #ffee62"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;"&amp;gt;&lt;/span&gt;Accout LogIn&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;于是在页面上就会生成这样的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;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="/Home/List"&amp;gt;&lt;/span&gt;List&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;a &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="http://account.jeffz-test.com/Home/LogIn/10"&amp;gt;&lt;/span&gt;Accout LogIn&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;p&gt;ASP.NET MVC中的其他与构造URL功能有关的辅助方法，如ActionLink，其实也都是相同的原理。如果您需要的话，也不妨自己实现一个对应的ActionLinkEx方法。&lt;/p&gt;
&lt;p&gt;不过，根据&lt;a href="http://blog.zhaojie.me/2009/02/mvc-use-strong-type-everywhere.html"&gt;尽可能强类型&lt;/a&gt;的原则，我们应该使用的是MvcFutures中定义的基于表达式树的辅助方法。不过MvcFutures里的这些方法有些问题，如最常见的“视ActionNameAttribute于无物”。在使用过程中我也遇到了其他一些问题，下次我们再来改造这些辅助方法。&lt;/p&gt;
&lt;p&gt;在条件充分的时候，使用表达式树来表示URL构造，可以说有百利而无一害。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/url-building-method-for-domainroute.html#comments</comments>
      <pubDate>Wed, 26 Aug 2009 04:18:00 GMT</pubDate>
      <lastBuildDate>Wed, 26 Aug 2009 04:18: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 Routing</title>
      <link>http://blog.zhaojie.me/2009/08/url-routing-with-domain.html</link>
      <guid>http://blog.zhaojie.me/2009/08/url-routing-with-domain.html</guid>
      <description>&lt;p&gt;经常有朋友问我，如何对域名作URL Routing，他们可能希望根据域名（或自域名）来获得一些值，最终影响Controller，Action或某些参数的选择。之前我只是简单地说“扩展一下ASP.NET Routing吧”，而现在由于自己也正好需要使用这个功能，便实现了一个扩展。使用下来，效果不错。&lt;/p&gt; &lt;p&gt;ASP.NET Routing已经实现了针对Path的匹配和构造，而如今我们是希望在这个基础上提供额外的Domain支持，而扩展的结果依旧是对URL的Routing支持。这种增加职责而不改变其外观的需求让我想到了&lt;a href="http://en.wikipedia.org/wiki/Decorator_pattern"&gt;装饰器模式&lt;/a&gt;。也就是说，如果我们的目标是构造一个RouteDomain，那么它可能就是这样的：&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;DomainRoute &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;RouteBase
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainParser &lt;/span&gt;m_domainParser;

    &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 string &lt;/span&gt;Pattern { &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;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: blue"&gt;this&lt;/span&gt;.InnerRoute = innerRoute;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Pattern = pattern;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_domainParser = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainParser&lt;/span&gt;(pattern);
    }

    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteData &lt;/span&gt;GetRouteData(&lt;span style="color: #2b91af"&gt;HttpContextBase &lt;/span&gt;httpContext)
    {&lt;span style="color: green"&gt;
        &lt;/span&gt;...
    }

    &lt;span style="color: blue"&gt;public override &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;p&gt;DomainRoute会封装一个内部Route对象，将匹配或构造Path的任务交给这个内部对象“之余”，再把对Domain的处理工作交给&lt;a href="http://blog.zhaojie.me/2009/08/domain-parser-based-on-parsedroute.html"&gt;DomainParser&lt;/a&gt;进行，而DomainRoute的主要逻辑，实际上便是将上两者进行组合。如GetRouteData方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteData &lt;/span&gt;GetRouteData(&lt;span style="color: #2b91af"&gt;HttpContextBase &lt;/span&gt;httpContext)
{
    &lt;span style="color: green"&gt;// match domain
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;domainValues = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_domainParser.Match(httpContext.Request.Url);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(domainValues == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;

    &lt;span style="color: green"&gt;// match path
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;routeData = &lt;span style="color: blue"&gt;this&lt;/span&gt;.InnerRoute.GetRouteData(httpContext);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(routeData == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;

    &lt;span style="color: green"&gt;// merge
    &lt;/span&gt;routeData.Values.CopyFrom(domainValues);
    routeData.Route = &lt;span style="color: blue"&gt;this&lt;/span&gt;;

    &lt;span style="color: blue"&gt;return &lt;/span&gt;routeData;
}
&lt;/pre&gt;
&lt;p&gt;GetRouteData的功能是匹配URL，分三步走，第一步是匹配Domain，第二步是使用内部Route匹配Path，然后通过&lt;a href="http://blog.zhaojie.me/2009/08/common-extensions.html"&gt;常用辅助方法&lt;/a&gt;中的CopyFrom方法，把一个字典中的所有数据复制到RouteData中并返回即可。可见，由于我们把任务进行了细小地拆分，每个类的职责均非常简单，可以进行独立的单元测试，因此代码也可以显得非常简单易懂。&lt;/p&gt;
&lt;p&gt;ASP.NET Routing的功能是构造URL和构造URL，因此我们还需要实现一个GetVirtualPath方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public override &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;span style="color: green"&gt;// bind domain
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;domain = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_domainParser.Bind(requestContext.RouteData.Values, values);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(domain == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;

    &lt;span style="color: green"&gt;// bind path
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;innerValues = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;();
    innerValues.CopyFrom(values).RemoveKeys(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_domainParser.Segments);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;pathData = &lt;span style="color: blue"&gt;this&lt;/span&gt;.InnerRoute.GetVirtualPath(requestContext, innerValues);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(pathData == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;
    
    &lt;span style="color: green"&gt;// merge
    &lt;/span&gt;pathData.Route = &lt;span style="color: blue"&gt;this&lt;/span&gt;;
    pathData.VirtualPath = Merge(requestContext.HttpContext, domain, pathData.VirtualPath);

    &lt;span style="color: blue"&gt;return &lt;/span&gt;pathData;
}

&lt;span style="color: blue"&gt;private static string &lt;/span&gt;Merge(&lt;span style="color: #2b91af"&gt;HttpContextBase &lt;/span&gt;context, &lt;span style="color: blue"&gt;string &lt;/span&gt;domain, &lt;span style="color: blue"&gt;string &lt;/span&gt;path)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;domainWithSlash = domain + &lt;span style="color: #a31515"&gt;"/"&lt;/span&gt;;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;ignoreDomain = context.Request.Url.ToString().StartsWith(domainWithSlash);
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ignoreDomain ? path : domainWithSlash + path;
}
&lt;/pre&gt;
&lt;p&gt;与GetRouteData的逻辑类似，GetVirtualPath方法首先根据所得的Route Value组装出Domain，再使用内部Route对象构造一个Path，并将其合并（Merge）起来。略有不同的是，再调用内部Route对象之前，必须去除所有用于Domain的部分（Segment），否则这些会&lt;a href="http://blog.zhaojie.me/2009/08/domain-parser-based-on-parsedroute.html#ParsedDomain-with-QueryString"&gt;出现在URL的QueryString部分中&lt;/a&gt;。在合并Domain和Path的时候，也有些许逻辑。Merge方法会判断当前请求与目标的Domain，如果两者相同，则会返回一个相对路径，省略URL前完整的域名。&lt;/p&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;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;最后还是进行几个单元测试吧。首先，我们可以捕获整个URL中的数据（关于MockHelper&lt;a href="http://blog.zhaojie.me/2009/08/set-the-path-in-http-context.html"&gt;请参考这里&lt;/a&gt;）：&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;Capture_Request_Scheme()
{
    &lt;span style="color: #2b91af"&gt;Mock&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;HttpRequestWrapper&lt;/span&gt;&amp;gt; mockRequest;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;mockContext = &lt;span style="color: #2b91af"&gt;MockHelper&lt;/span&gt;.MockRequest(&lt;span style="color: #a31515"&gt;"http://jeffz.space.cnblogs.com/posts/2009"&lt;/span&gt;, &lt;span style="color: blue"&gt;out &lt;/span&gt;mockRequest);

    &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;"{section}/{data}"&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;domainRoute = route.WithDomain(&lt;span style="color: #a31515"&gt;"{scheme}://{user}.{area}.{*domain}"&lt;/span&gt;);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;routeData = domainRoute.GetRouteData(mockContext.Object);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"http"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"scheme"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"space"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"area"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"cnblogs.com"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"domain"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"jeffz"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"user"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"posts"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"section"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"2009"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"data"&lt;/span&gt;]);
}
&lt;/pre&gt;
&lt;p&gt;其次，对于无法匹配的URL，也能够返回null：&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;Specified_Request_Scheme()
{
    &lt;span style="color: #2b91af"&gt;Mock&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;HttpRequestWrapper&lt;/span&gt;&amp;gt; mockRequest;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;mockContext = &lt;span style="color: #2b91af"&gt;MockHelper&lt;/span&gt;.MockRequest(&lt;span style="color: #a31515"&gt;"http://space.cnblogs.com/Home"&lt;/span&gt;, &lt;span style="color: blue"&gt;out &lt;/span&gt;mockRequest);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;sslRoute = &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}"&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;).WithDomain(&lt;span style="color: #a31515"&gt;"https://{sub_domain}.{*domain}"&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;sslData = sslRoute.GetRouteData(mockContext.Object);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Null(sslData);
}
&lt;/pre&gt;
&lt;p&gt;最后，我们也可以成功地构造整段URL：&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;Build_Url()
{
    &lt;span style="color: #2b91af"&gt;Mock&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;HttpRequestWrapper&lt;/span&gt;&amp;gt; mockRequest;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;mockContext = &lt;span style="color: #2b91af"&gt;MockHelper&lt;/span&gt;.MockRequest(&lt;span style="color: #a31515"&gt;"http://wiki.cnblogs.com/Home/Index"&lt;/span&gt;, &lt;span style="color: blue"&gt;out &lt;/span&gt;mockRequest);

    &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}"&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;).WithDomain(&lt;span style="color: #a31515"&gt;"{scheme}://{area}.{*domain}"&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;routeData = route.GetRouteData(mockContext.Object);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;requestContext = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RequestContext&lt;/span&gt;(mockContext.Object, routeData);

    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"http"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"scheme"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"wiki"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"area"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"cnblogs.com"&lt;/span&gt;, routeData.Values[&lt;span style="color: #a31515"&gt;"domain"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Home"&lt;/span&gt;, routeData.Values[&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;, routeData.Values[&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;]);

    &lt;span style="color: green"&gt;// same domain
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;values = &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;"Account"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List" &lt;/span&gt;});
    &lt;span style="color: blue"&gt;var &lt;/span&gt;pathData = route.GetVirtualPath(requestContext, values);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Account/List"&lt;/span&gt;, pathData.VirtualPath);

    &lt;span style="color: green"&gt;// different domain
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;spaceRoute = &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}"&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;).WithDomain(&lt;span style="color: #a31515"&gt;"http://{user}.{area}.{*domain}"&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;spaceHash = &lt;span style="color: blue"&gt;new &lt;/span&gt;{ controller = &lt;span style="color: #a31515"&gt;"Account"&lt;/span&gt;, action = &lt;span style="color: #a31515"&gt;"List"&lt;/span&gt;, area = &lt;span style="color: #a31515"&gt;"space"&lt;/span&gt;, user = &lt;span style="color: #a31515"&gt;"jeffz" &lt;/span&gt;};
    &lt;span style="color: blue"&gt;var &lt;/span&gt;spaceValues = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;(spaceHash);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;spacePathData = spaceRoute.GetVirtualPath(requestContext, spaceValues);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"http://jeffz.space.cnblogs.com/Account/List"&lt;/span&gt;, spacePathData.VirtualPath);
}
&lt;/pre&gt;
&lt;p&gt;整个DomainRoute类就这样完成了，除了单元测试外，总共也就60多行代码，但已经实现了我们所需要的常用功能。当然，目前还不支持“端口”，如果您需要的话，也可以修改代码，让其为您所用。&lt;/p&gt;
&lt;p&gt;不过，虽然DomainRoute已经准备好了，但是在视图中“构造”URL时的辅助方法还需要一些额外的实现。这个下次再说吧（已发布，请参考《&lt;a href="http://blog.zhaojie.me/2009/08/url-building-method-for-domainroute.html"&gt;支持DomainRoute的URL构造辅助方法&lt;/a&gt;》）。&lt;/p&gt;
&lt;p&gt;如果您有什么其他的想法或建议也请提出，我们可以一起讨论一下。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/url-routing-with-domain.html#comments</comments>
      <pubDate>Tue, 25 Aug 2009 08:00:00 GMT</pubDate>
      <lastBuildDate>Tue, 25 Aug 2009 08: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/extension/">项目扩展</category>
      <title>基于ParsedRoute的Domain Parser</title>
      <link>http://blog.zhaojie.me/2009/08/domain-parser-based-on-parsedroute.html</link>
      <guid>http://blog.zhaojie.me/2009/08/domain-parser-based-on-parsedroute.html</guid>
      <description>&lt;p&gt;之前谈了不少关于ASP.NET Routing中ParsedRoute的内容，例如&lt;a href="http://blog.zhaojie.me/2009/08/more-on-parsedroute.html"&gt;它的设计&lt;/a&gt;以及&lt;a href="http://blog.zhaojie.me/2009/08/use-the-internal-feature.html"&gt;如何调用它的功能&lt;/a&gt;，其目的便是为了如今的使用作准备。现在我们就基于它构建一个Domain Parser，而这个Parser也是为今后的功能打基础的。&lt;/p&gt; &lt;p&gt;为了调用内部的ParsedRoute类的功能，我们写了一个简单的“外壳类”。这个类的源代码可以&lt;a href="http://gist.github.com/173778"&gt;在这里获得&lt;/a&gt;，这里就不多重复了。我们主要关心如何借助这个类来实现一个Domain Parser。&lt;/p&gt; &lt;p&gt;Domain Parser的目的自然是对一个域名进行解析和组装。解析和组装也都是基于“模式”的，这个模式的形式便直接借鉴了ASP.NET Routing的Route类。例如使用{scheme}://{sub_domain}.{*domain}来匹配&lt;a href="http://www.cnblogs.com"&gt;http://www.cnblogs.com&lt;/a&gt;，便可以得到对应的scheme、sub_domain和domain的值。换句话说，它必须通过这样的单元测试（嘿嘿，我们似乎也有点测试驱动开发的意味了）：&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;Parse_Domain()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;parser = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainParser&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"{scheme}://{sub_domain}.{*domain}"&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;values = parser.Match(&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;"http://space.cnblogs.com"&lt;/span&gt;));
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"http"&lt;/span&gt;, values[&lt;span style="color: #a31515"&gt;"scheme"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"space"&lt;/span&gt;, values[&lt;span style="color: #a31515"&gt;"sub_domain"&lt;/span&gt;]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"cnblogs.com"&lt;/span&gt;, values[&lt;span style="color: #a31515"&gt;"domain"&lt;/span&gt;]);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;sslParser = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainParser&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"https://{sub_domain}.{*domain}"&lt;/span&gt;);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Null(sslParser.Match(&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;"http://www.cnblogs.com"&lt;/span&gt;)));
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;反过来也一样，提供对应的scheme、sub_domain和domain的值，便可以组装出一个正确的域名。单元测试如下：&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;Build_Domain()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;parser = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainParser&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"{scheme}://{sub_domain}.{*domain}"&lt;/span&gt;);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;currentValues = &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;{ sub_domain = &lt;span style="color: #a31515"&gt;"wiki"&lt;/span&gt;, domain = &lt;span style="color: #a31515"&gt;"cnblogs.com" &lt;/span&gt;});
    
    &lt;span style="color: blue"&gt;var &lt;/span&gt;values = &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;{ scheme = &lt;span style="color: #a31515"&gt;"http"&lt;/span&gt;, sub_domain = &lt;span style="color: #a31515"&gt;"space" &lt;/span&gt;});

    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"http://space.cnblogs.com"&lt;/span&gt;, parser.Bind(currentValues, values));

    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Null(parser.Bind(&lt;span style="color: blue"&gt;null&lt;/span&gt;, &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;只可惜，ParsedRoute本身只支持对斜杠（即“/”）分割的部分进行拆分，但是很显然域名的分割符比较特殊，如“://”或“.”，这意味着我们不能直接使用模式字符串构造一个ParsedRoute类，在此之前我们必须对其进行一定处理：&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;DomainParser
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;DomainParser(&lt;span style="color: blue"&gt;string &lt;/span&gt;pattern)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Pattern = pattern;

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Segments = CaptureSegments(pattern);

        &lt;span style="color: blue"&gt;string &lt;/span&gt;routePattern = pattern.Replace(&lt;span style="color: #a31515"&gt;"://"&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"/"&lt;/span&gt;).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;this&lt;/span&gt;.m_parsedRoute = &lt;span style="color: #2b91af"&gt;RouteParser&lt;/span&gt;.Parse(routePattern);
    }

    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReadOnlyCollection&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; CaptureSegments(&lt;span style="color: blue"&gt;string &lt;/span&gt;domainPattern)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;regex = &lt;span style="color: #a31515"&gt;&amp;#64;"{\*?([^}]+)}"&lt;/span&gt;;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;matches = &lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;.Matches(domainPattern, regex).Cast&amp;lt;&lt;span style="color: #2b91af"&gt;Match&lt;/span&gt;&amp;gt;();
        &lt;span style="color: blue"&gt;var &lt;/span&gt;segments = matches.Select(m =&amp;gt; m.Groups[1].Value);
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReadOnlyCollection&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;(segments.ToList());
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReadOnlyCollection&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; Segments { &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 string &lt;/span&gt;Pattern { &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;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;由于功能需要（稍后详谈），我们会提取其中所有需要捕获的“部分（segment）”，对此我们最好也通过单元测试来保证“提取”操作的正确性：&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;Capture_Segments()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;parser = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DomainParser&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"http://{sub_domain}.{*domain}"&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;sorted = parser.Segments.OrderBy(s =&amp;gt; s).ToList();
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"domain"&lt;/span&gt;, sorted[0]);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"sub_domain"&lt;/span&gt;, sorted[1]);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;自然，在解析域名时，也不能直接将其交给ParsedRoute类处理，而必须经过一定的转换：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static string &lt;/span&gt;ConvertDomainToPath(&lt;span style="color: #2b91af"&gt;Uri &lt;/span&gt;uri)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;uri.Scheme + &lt;span style="color: #a31515"&gt;"/" &lt;/span&gt;+ uri.Host.Replace(&lt;span style="color: #a31515"&gt;'.'&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;a href="http://www.cnblogs.com"&gt;http://www.cnblogs.com&lt;/a&gt;”这个域名转化为“http/www/cnblogs/com”这个可以ParsedRoute可以解析的域名。至此，解析用的Match方法便可以轻易得出：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;Match(&lt;span style="color: #2b91af"&gt;Uri &lt;/span&gt;uri)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;toParse = ConvertDomainToPath(uri);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;domainValues = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_parsedRoute.Match(toParse, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(domainValues == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;

    &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;RouteValueDictionary&lt;/span&gt;();
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;pair &lt;span style="color: blue"&gt;in &lt;/span&gt;domainValues)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;value = pair.Value &lt;span style="color: blue"&gt;as string&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(value != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            result.Add(pair.Key, value.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;else
        &lt;/span&gt;{
            result.Add(pair.Key, pair.Value);
        }
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;result;
}
&lt;/pre&gt;
&lt;p&gt;在使用ParsedRoute获得解析结果之后，我们会把其中每个值中的斜杠替换成“.”，这样便恢复了域名匹配前的模样。例如，我们使用{*domain}来匹配域名的尾部，这样domain便可以得到cnblogs.com这个值。&lt;/p&gt;
&lt;p id="ParsedDomain-with-QueryString"&gt;作为Parse的逆操作，DomainParser的Bind方法自然会用到ParsedDomain的Bind方法。不过需要注意的是，ParsedDomain在遇到那些模式中没有出现的部分时，会将他们作为URL的Query String处理。您可以通过下面的单元测试代码来观察这一点：&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;Bind()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;values = &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: #a31515"&gt;"controller"&lt;/span&gt;] = &lt;span style="color: #a31515"&gt;"Home"&lt;/span&gt;;
    values[&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;] = &lt;span style="color: #a31515"&gt;"Index"&lt;/span&gt;;
    values[&lt;span style="color: #a31515"&gt;"hello"&lt;/span&gt;] = &lt;span style="color: #a31515"&gt;"world"&lt;/span&gt;;
    values[&lt;span style="color: #a31515"&gt;"id"&lt;/span&gt;] = 5;

    &lt;span style="color: blue"&gt;var &lt;/span&gt;parsedRoute = &lt;span style="color: #2b91af"&gt;RouteParser&lt;/span&gt;.Parse(&lt;span style="color: #a31515"&gt;"{controller}/{action}/{*id}"&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;boundUrl = parsedRoute.Bind(&lt;span style="color: blue"&gt;null&lt;/span&gt;, values, &lt;span style="color: blue"&gt;null&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Home/Index/5?hello=world"&lt;/span&gt;, boundUrl.Url);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;因此，在交由ParsedRoute处理之前，我们会从数据源中提取必要的值，并填充新的acceptValues集合，再使用ParsedRoute获取拼装结果：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public string &lt;/span&gt;Bind(&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;currentValues, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values)
{
    currentValues = currentValues ?? &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;();
    values = values ?? &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;var &lt;/span&gt;acceptValues = &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;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;name &lt;span style="color: blue"&gt;in this&lt;/span&gt;.Segments)
    {
        &lt;span style="color: blue"&gt;object &lt;/span&gt;segmentValue;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(values.TryGetValue(name, &lt;span style="color: blue"&gt;out &lt;/span&gt;segmentValue) ||
            currentValues.TryGetValue(name, &lt;span style="color: blue"&gt;out &lt;/span&gt;segmentValue))
        {
            acceptValues.Add(name, segmentValue);
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;return null&lt;/span&gt;;
        }
    }
    
    &lt;span style="color: blue"&gt;var &lt;/span&gt;boundUrl = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_parsedRoute.Bind(&lt;span style="color: blue"&gt;null&lt;/span&gt;, acceptValues, &lt;span style="color: blue"&gt;null&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(boundUrl == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;

    &lt;span style="color: blue"&gt;return &lt;/span&gt;ConvertPathToDomain(boundUrl.Url);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;我们这里只利用了ParsedRoute的第二个参数，这意味着我们提供的每个“部分”没有默认值，没有约束，完全通过直接提供，这样便避免涉及到ParsedRoute中较为复杂的部分。如果您需要这些额外的功能，也可以自行修改相关代码。不过，ParsedRoute的Bind方法获得的是一个类似于“http/www/cnblogs.com”这样的url，因此在返回之前还要进行额外的转化：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static string &lt;/span&gt;ConvertUrlToDomain(&lt;span style="color: blue"&gt;string &lt;/span&gt;url)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;domainParts = url.Split(&lt;span style="color: #a31515"&gt;'/'&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;domain = domainParts[0];
    &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; domainParts.Length; i++)
    {
        domain += (i == 1 ? &lt;span style="color: #a31515"&gt;"://" &lt;/span&gt;: &lt;span style="color: #a31515"&gt;"."&lt;/span&gt;);
        domain += domainParts[i];
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;domain;
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这倒都是最最简单的字符串转化而已，相信没有什么值得讨论的。&lt;/p&gt;
&lt;p&gt;借助ParsedRoute的功能，我们构建了一个与现有匹配规则类似的DomainParser类，它可以帮助我们解析和组装域名，为接下去的功能做一些准备（这里可&lt;a href="http://gist.github.com/173811"&gt;获得它的完整代码&lt;/a&gt;）。可能您已经猜到了这个功能是什么，不过现在我们还是分享一下关于DomainParser的体会吧。对于现在的实现，您对此有什么想法呢？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/domain-parser-based-on-parsedroute.html#comments</comments>
      <pubDate>Mon, 24 Aug 2009 10:27:00 GMT</pubDate>
      <lastBuildDate>Mon, 24 Aug 2009 10:27: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/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>再谈ASP.NET Routing中的ParsedRoute</title>
      <link>http://blog.zhaojie.me/2009/08/more-on-parsedroute.html</link>
      <guid>http://blog.zhaojie.me/2009/08/more-on-parsedroute.html</guid>
      <description>&lt;p&gt;上午搬家，东西整理得头大，吃了很多灰，有些头晕，不过把东西积累着也不爽，就写了吧。&lt;/p&gt; &lt;p&gt;ParsedRoute是ASP.NET Routing中的内部类，作用是根据既定模式将一段URL解析为一个RouteValueDictionary。&lt;a href="http://blog.zhaojie.me/2009/08/use-the-internal-feature.html"&gt;上次的文章中&lt;/a&gt;我主要谈了如何利用反射使用类库的内部成员，而这次则想分享一些使用ParsedRoute时产生的一些想法。&lt;/p&gt; &lt;p&gt;首先，这里我想谈一下&lt;a href="http://home.cnblogs.com/83011/"&gt;徐风子&lt;/a&gt;同学在那篇文章后面所指出的问题。他认为我们不应该使用类库的内部成员，以下是他的原文评论：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;用内部函数的风险是，他既然没有推荐，那就有不推荐使用的理由：只适用于特定功能、有特定的局限性……还有版本升级带来的影响等等。&lt;/p&gt; &lt;p&gt;而对于根本不知道内部结构就贸然调用内部方法，再多的单元测试也不能保证正确，人家也根本没有保证过你任何东西，只是你的猜测。&lt;/p&gt; &lt;p&gt;如果真想用类似的功能，有两个途径：&lt;/p&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;例如，我们希望开发的功能便是在“扩展现有的实现”，这在一定程度上要求我们的实现与扩展目标较为接近。于是，我们需要的一些基础功能（例如字符串解析），可能也已经是现有实现的一部分，只是由于现有类库没有将其公开，我们往往需要重复开发相同的功能。如果有现成的公开实现，那么自然不会使用这种Hack的方式。但是如果没有，我就会倾向与使用类库内部已有的功能。正是基于这种考虑，我会复用ParsedRoute，因为我希望可以使用ASP.NET Routing中相同的模式，进行相同的字符串捕获和构造功能。&lt;/p&gt; &lt;p&gt;自然，在这之前我会阅读这部分实现的代码，确保它能够满足我的需求，再通过这种方式进行调用。徐同学认为，如果读完之后，应该将其复制出来，放入自己的系统，便于修改。但是有些功能，它对外的接口简单，但是内部实现可能涉及到数千行代码，十几组件之类的交互（例如运用&lt;a href="http://en.wikipedia.org/wiki/Facade_pattern"&gt;Facade模式&lt;/a&gt;的地方），与此相比，我还是倾向于简单地封装一下接口，并使用充足的单元测试来确保这些功能。&lt;/p&gt; &lt;p&gt;在使用内部功能的时候，单元测试尤其重要（当然平时单元测试也是很重要的）。由于内部功能的确是相对容易改变的地方，我们必须使用单元测试来保证正在使用的那部分功能不会出现问题——或者说，&lt;font color="#ff0000"&gt;一旦出现问题，我们可以立即发现&lt;/font&gt;，并将其替换成自己的实现，或改变内部方法的调用方式（毕竟完全大改的可能性也不太高）。如果您没有编写单元测试的习惯，也请务必为这部分功能写单元测试，否则还是放弃这种做法吧。&lt;/p&gt; &lt;p&gt;不过对于一些普遍使用的基础功能（例如一些数据结构，容器等等），我还是倾向与使用公开实现或直接将代码拷贝出来。例如，WPF中提供了internal的ReadOnlyDictionary类型，在需要的时候便会将其实现复制到自己的项目中。甚至于在使用公开的&lt;a href="http://msdn.microsoft.com/en-us/library/ms668604.aspx"&gt;ObservableCollection&amp;lt;T&amp;gt;&lt;/a&gt;类时也会这样，因为我不想在一个普通的项目中依赖一个WPF类库——写到这里，我忽然有些理解&lt;a href="http://www.infoq.com/cn/news/2007/11/criticism-from-microsoft-devlead"&gt;.NET框架中包含多个哈希表实现的做法&lt;/a&gt;了。&lt;/p&gt; &lt;p&gt;真的不容易。&lt;/p&gt; &lt;p&gt;还有便是ParsedRoute的功能，它其实拥有两个接口，Match和Bind：&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;ParsedRoute
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;Match(
        &lt;span style="color: blue"&gt;string &lt;/span&gt;virtualPath,
        &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;defaultValues)
    {
        ...
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BoundUrl &lt;/span&gt;Bind(
        &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;currentValues,
        &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values,
        &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;defaultValues,
        &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;constraints)
    {
        &lt;font color="#0000ff"&gt;...&lt;/font&gt;
    }
}
&lt;/pre&gt;
&lt;p&gt;Match方法的功能是将URL（即virtualPath）解析为一个RouteValueDictionary，而Bind的作用是根据几个RouteValueDictionary集合构造一个URL。但这里我认为，ParsedRoute类的设计是一个典型的反面教材。&lt;/p&gt;
&lt;p&gt;例如，在ASP.NET Routing中是这样使用ParsedRoute类的。开发人员使用的是公开的Route类，提供一个模式字符串（如"{controller}/{action}/{id}"）和其他一些默认值，约束（constraint）等设置，而Route会把一部分职责交给ParsedRoute完成。在URL解析阶段，Route类会使用Match方法解析字符串，然后在Route类内部进行约束控制。但是在Bind阶段，对于ParsedRoute却在直接使用这些约束。简单地说，ParsedRoute一会儿知道有“约束”这个东西，一会儿又不知道，它的职责在进行不同工作的时候具有比较明显的变化。因此我认为它设计的不合适。&lt;/p&gt;
&lt;p&gt;因此，我在使用ParsedRoute的时候，无论是解析还是构造URL时，都不会提供约束条件（今后会有更多相关内容）。&lt;/p&gt;
&lt;p&gt;还有便是，contstraints使用RouteValueDictionary进行保存，它其实是一个IDictionary&amp;lt;string, object&amp;gt;容器。在执行的时候，ASP.NET Routing中会判断这个object对象的类型，如果是字符串则把它作为正则表达式来使用，如果是IRouteConstraint对象则另有一番逻辑，否则就会抛出异常。我不喜欢这个设计，这个做法不够面向对象。虽然OO不是需要严格遵循的准则，但是现在的做法隐藏了太多，假设了太多，我并没有看出它有什么好处。难道仅仅是为了方便？&lt;/p&gt;
&lt;p&gt;您有什么想法呢？您会如何使用类库内部的功能？您对ParsedRoute的设计怎么看？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/more-on-parsedroute.html#comments</comments>
      <pubDate>Mon, 24 Aug 2009 06:10:00 GMT</pubDate>
      <lastBuildDate>Mon, 24 Aug 2009 06:10: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>支持Area的ControllerFactory</title>
      <link>http://blog.zhaojie.me/2009/08/controller-factory-with-area-supporting.html</link>
      <guid>http://blog.zhaojie.me/2009/08/controller-factory-with-area-supporting.html</guid>
      <description>&lt;p&gt;几个星期之前，有个朋友对我说，他的项目中需要将前后台区分开来，也就是类似分Area的功能。不过Area只在MVC 2中出现，因此现在想在1.0版本中先实现类似的功能了。他打算，根据Route中捕获的内容（如“area”），然后去找对应命名空间下的Controller。这样看来不难，似乎只要在Route上做点配置，而默认的DefaultControllerFactory已经对命名空间的查询提供支持了（可惜&lt;a href="http://blog.zhaojie.me/2009/08/asp-net-mvc-defaultcontrollerfactory-thread-unsafe.html"&gt;有线程安全的问题&lt;/a&gt;）。&lt;/p&gt; &lt;p&gt;不过他说，最后发现似乎这块功能不是他想象的那样，因此希望我可以看看到底是什么问题。由于当时没有扩展ASP.NET MVC的需求，后来我事情一多就忘了，现在先说声抱歉。最近开始对ASP.NET MVC动手动脚了，发现这样一个Area的功能非常有用，而且巧合的是，我也打算把Area和命名空间对应起来。&lt;/p&gt; &lt;p&gt;只是我选择的路和那位兄弟不一样，我打算自己写一个简单的ControllerFactory来替换掉默认的DefaultControllerFactory。这么做的主要原因是：我不知道DefaultControllerFactory已经提供对命名空间的支持了，微软默默地实现了却没有对外公开过，我也是后来阅读代码时才发现的。同时又意识到线程安全的问题，于是就还是打算自己写了。&lt;/p&gt; &lt;p&gt;好在ASP.NET MVC从设计之初就提供了扩展的能力，每个组件粒度都很小，大部分组件都是可以独立拔插的（Controller类除外，如果你使用自己的IController实现，就会发现大部分功能，如各Filter都失效了）。而要实现一个Controller Factory，只要实现一个简单的IControllerFactory就可以了（&lt;a href="http://blog.zhaojie.me/2009/08/awful-design-in-asp-net-routing.html"&gt;我喜欢接口&lt;/a&gt;）：&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;IControllerFactory
&lt;/span&gt;{&lt;span style="color: green"&gt;
    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IController &lt;/span&gt;CreateController(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: blue"&gt;string &lt;/span&gt;controllerName);&lt;span style="color: green"&gt;
    &lt;/span&gt;&lt;span style="color: blue"&gt;void &lt;/span&gt;ReleaseController(&lt;span style="color: #2b91af"&gt;IController &lt;/span&gt;controller);
}
&lt;/pre&gt;
&lt;p&gt;于是构建一个AreaControllerFactory也大致只需要以下一些代码：&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;AreaControllerFactory &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IControllerFactory
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IController &lt;/span&gt;CreateController(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: blue"&gt;string &lt;/span&gt;controllerName)
    {
        ...
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;ReleaseController(&lt;span style="color: #2b91af"&gt;IController &lt;/span&gt;controller)
    {
        &lt;span style="color: #2b91af"&gt;IDisposable &lt;/span&gt;disposable = controller &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IDisposable&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(disposable != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            disposable.Dispose();
        }
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;然后按照惯例，还是一步步谈起。首先是构造函数，我们的策略是根据不同的Area加载不同命名空间下的Controller类型。方便起见，我选择“基础命名空间”和“扩展部分”两块，它们从构造函数中传入：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;, &lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; m_areaPartMapping = &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: blue"&gt;string&lt;/span&gt;, &lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;();

&lt;span style="color: blue"&gt;public string &lt;/span&gt;NamespaceBase { &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;AreaControllerFactory(&lt;span style="color: blue"&gt;string &lt;/span&gt;namespaceBase)
    : &lt;span style="color: blue"&gt;this&lt;/span&gt;(namespaceBase, &lt;span style="color: blue"&gt;null&lt;/span&gt;)
{ }

&lt;span style="color: blue"&gt;public &lt;/span&gt;AreaControllerFactory(&lt;span style="color: blue"&gt;string &lt;/span&gt;namespaceBase, &lt;span style="color: #2b91af"&gt;IDictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;, &lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; areaPartMapping)
{
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.NamespaceBase = namespaceBase.EndsWith(&lt;span style="color: #a31515"&gt;"."&lt;/span&gt;) ? namespaceBase : namespaceBase + &lt;span style="color: #a31515"&gt;"."&lt;/span&gt;;

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(areaPartMapping != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;pair &lt;span style="color: blue"&gt;in &lt;/span&gt;areaPartMapping)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_areaPartMapping.Add(pair.Key.ToLowerInvariant(), pair.Value);
        }
    }
}
&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;var &lt;/span&gt;controllerFactory = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AreaControllerFactory&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"MyApp.Controllers"&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;ControllerBuilder&lt;/span&gt;.Current.SetControllerFactory(controllerFactory);
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如果在需要的时候，还可以指定Area与特定命名空间“部分”的映射关系。因此，我们需要从Area来获取这个“Part”：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private string &lt;/span&gt;GetNamespacePart(&lt;span style="color: blue"&gt;string &lt;/span&gt;area)
{
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.IsNullOrEmpty(area)) &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;""&lt;/span&gt;;

    &lt;span style="color: blue"&gt;string &lt;/span&gt;part;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_areaPartMapping.TryGetValue(area.ToLowerInvariant(), &lt;span style="color: blue"&gt;out &lt;/span&gt;part))
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;part;
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;area;
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这里我选择“配置”和“约定”相结合的方式。得到一个Area之后，我们会在映射表里进行查找Part，如果没有，则Area本身便是Part。根据Part和Controller名称，我们便可以获得Controller的类型：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReaderWriterLockSlim &lt;/span&gt;m_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 &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;&amp;gt; m_controllerTypes = &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: blue"&gt;string&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;&amp;gt;();

&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;GetControllerType(&lt;span style="color: blue"&gt;string &lt;/span&gt;area, &lt;span style="color: blue"&gt;string &lt;/span&gt;controllerName)
{
    &lt;span style="color: blue"&gt;string &lt;/span&gt;part = &lt;span style="color: blue"&gt;this&lt;/span&gt;.GetNamespacePart(area);

    &lt;span style="color: blue"&gt;string &lt;/span&gt;typeName = &lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.IsNullOrEmpty(part) ?
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.NamespaceBase + controllerName.ToLowerInvariant() + &lt;span style="color: #a31515"&gt;"Controller" &lt;/span&gt;:
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.NamespaceBase + part + &lt;span style="color: #a31515"&gt;"." &lt;/span&gt;+ controllerName.ToLowerInvariant() + &lt;span style="color: #a31515"&gt;"Controller"&lt;/span&gt;;

    &lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;type;

    &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_rwLock.EnterReadLock();
    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_controllerTypes.TryGetValue(typeName, &lt;span style="color: blue"&gt;out &lt;/span&gt;type))
        {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;type;
        }
    }
    &lt;span style="color: blue"&gt;finally
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_rwLock.ExitReadLock();
    }

    &lt;span style="color: red"&gt;type = Type.GetType(typeName, false, true);&lt;/span&gt;

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(type != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_rwLock.EnterWriteLock();
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_controllerTypes[typeName] = type;
        }
        &lt;span style="color: blue"&gt;finally
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_rwLock.ExitWriteLock();
        }
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;type;
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;由于我选择在应用程序中使用同一个AreaControllerFactory对象，因此线程安全是一定要有保证的。这里我们用到了读写锁，不过请注意，红色那句话并不保证对于每个相同的typeName只执行一次，也不保证相同的typeName对于m_controllerTypes字典只会进行一次写操作（所以我没有Add，而是使用了下标操作）。不过，由于这些“重复”不会造成问题，因此就没有去涉及太多这方面的考虑。&lt;/p&gt;
&lt;p&gt;最后，便是那CreateControlle方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IController &lt;/span&gt;CreateController(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext, &lt;span style="color: blue"&gt;string &lt;/span&gt;controllerName)
{
    &lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;controllerType;
    &lt;span style="color: blue"&gt;object &lt;/span&gt;area;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(requestContext.RouteData.Values.TryGetValue(&lt;span style="color: #a31515"&gt;"area"&lt;/span&gt;, &lt;span style="color: blue"&gt;out &lt;/span&gt;area))
    {
        controllerType = &lt;span style="color: blue"&gt;this&lt;/span&gt;.GetControllerType(area.ToString(), controllerName);
    }
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        controllerType = &lt;span style="color: blue"&gt;this&lt;/span&gt;.GetControllerType(&lt;span style="color: blue"&gt;null&lt;/span&gt;, controllerName);
    }

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(controllerType == &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;HttpException&lt;/span&gt;(404,
            &lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(
                &lt;span style="color: #a31515"&gt;"Controller of path {0} not found."&lt;/span&gt;,
                requestContext.HttpContext.Request.Path));
    }

    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;return &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;IController&lt;/span&gt;)&lt;span style="color: #2b91af"&gt;Activator&lt;/span&gt;.CreateInstance(controllerType);
    }
    &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;string &lt;/span&gt;message = &lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;"Error creating controller {0}" &lt;/span&gt;+ controllerType);
        &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InvalidOperationException&lt;/span&gt;(message, ex);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;似乎没有什么可谈的：我们从RouteData中获取出area对应的值，并且调用GetControllerType方法获得Controller的类型，并使用Activator.CreateInstance创建对象。在不合法的情况下，抛出合适的异常即可。&lt;/p&gt;
&lt;p&gt;至此，AreaControllerFactory就完成了，很容易，不是吗？很显然，这个组件的功能非常有限，例如为什么所有的Controller一定要在同一个命名空间下？没错，它其实只是符合“我要求”的一个东西。但是，在项目中很多东西都是如此，我只实现我够用的功能。例如，我可能不会向对外公开的API那样，严格检查每个问题，抛出严谨的异常。我可能&lt;a href="http://blog.zhaojie.me/2009/08/more-on-class-and-interface.html"&gt;倾向于在项目中使用接口，而不是使用抽象类&lt;/a&gt;。因为是我的项目，我可以快速反馈，需要修改的时候就修改吧。&lt;/p&gt;
&lt;p&gt;同样的，如果DefaultControllerFactory真在某些特别情况下有问题，或者支持的有些复杂。那么不如我们就自己动手吧。一次性投入，而且这样的小组件也花不了多少时间。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;完整代码：&lt;a href="http://gist.github.com/170798"&gt;http://gist.github.com/170798&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/controller-factory-with-area-supporting.html#comments</comments>
      <pubDate>Thu, 20 Aug 2009 03:33:00 GMT</pubDate>
      <lastBuildDate>Thu, 20 Aug 2009 03:33: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/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>复用类库内部已有功能</title>
      <link>http://blog.zhaojie.me/2009/08/use-the-internal-feature.html</link>
      <guid>http://blog.zhaojie.me/2009/08/use-the-internal-feature.html</guid>
      <description>&lt;p&gt;经常看我博客的人可能会知道，我是一个喜欢搞点小技巧来实现某个功能的人。例如博客的皮肤，自己花了不少时间定义，也是为了效果丰富一些。当然，搞得最多的是从框架或类库内部取出一点小功能来用用，节省自己开发的时间。这其实也是一种复用，尤其是开发一些“扩展”的时候，例如当时尝试为UpdatePanel增加上传功能，虽然最后的结果不是很理想，但是大部分的Hack以及前后端的交互是非常成功的（最大的问题在于跨浏览器实现iframe通信）。而现在也打算总结一次这方面的简单技巧，为以后的文章贡献点引用资源。&lt;/p&gt; &lt;p&gt;这次我们想“复用”的内容是ASP.NET URL Routing中“解析URL”的功能。具体一点地说，就是把一个字符串根据指定的Pattern拆分成键/值对的功能。从.NET Reflector反编译System.Web.Routing.dll的结果来看，这部分的解析工作是交由RouteParser和ParsedRoute两个类完成的。这里引用一下相关的使用代码，如果您感兴趣的话，也可以阅读它们完整的实现：&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;Route
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Url
    {
        &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;this&lt;/span&gt;._parsedRoute = &lt;span style="color: #2b91af"&gt;RouteParser&lt;/span&gt;.Parse(&lt;span style="color: blue"&gt;value&lt;/span&gt;);
            &lt;span style="color: blue"&gt;this&lt;/span&gt;._url = &lt;span style="color: blue"&gt;value&lt;/span&gt;;
        }
    }

    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteData &lt;/span&gt;GetRouteData(&lt;span style="color: #2b91af"&gt;HttpContextBase &lt;/span&gt;httpContext)
    {
        &lt;span style="color: blue"&gt;string &lt;/span&gt;virtualPath = ...
        &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values = &lt;span style="color: blue"&gt;this&lt;/span&gt;._parsedRoute.Match(virtualPath, &lt;span style="color: blue"&gt;this&lt;/span&gt;.Defaults);

        ...
    }

    ...
}
&lt;/pre&gt;
&lt;p&gt;从代码中可以看出，RouteParser的作用是将一个Pattern（如"{controller}/{action}/{id}"）转化成一个“解析器”，而这个解析器便是ParsedRoute类。在需要拆分一个URL字符串（如"Home/Index/5"）的时候，便会调用ParsedRoute类的Match方法，由此得到一个RouteValueDictionary对象，其中包含了Pattern中定义的名称，和一些值的映射关系。&lt;/p&gt;
&lt;p&gt;可能您也能够轻易实现这样的功能，不过既然微软已经帮我们做好了，我们也不妨直接使用一下，偶尔用来拆拆字符串也是挺方便的。只可惜RouteParser和ParsedRoute都是由internal修饰的，我们无法直接访问到。那么就用点小技巧吧……说实话，其实您会发现也就这么一回事，“反射”罢了。因此，我们便学着ASP.NET Routing的做法，构建两个类吧：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteParser
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParsedRoute &lt;/span&gt;Parse(&lt;span style="color: blue"&gt;string &lt;/span&gt;routeUrl) { ... }
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParsedRoute
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;Match(&lt;span style="color: blue"&gt;string &lt;/span&gt;virtualPath, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;defaultValues) { ... }
}
&lt;/pre&gt;
&lt;p&gt;我们目前的做法算是一种Hack，为了保证其可维护性，我会选择与目标类库/框架的接口尽可能完全一致的做法。这么做的好处在于，我可以很轻易地理解正在实现的功能，一旦出现了任何问题，就可以直接去找对应的内部实现，而不用在一堆堆的反射关系中“翱翔”。&lt;/p&gt;
&lt;p&gt;接着便可以实现我们需要的效果了。在这里，我使用了&lt;a href="http://blog.zhaojie.me/2009/02/fast-reflection-library.html"&gt;FastReflectionLib&lt;/a&gt;来加快反射调用的性能。虽然我不是一个追求性能极致的Geek，但是如果有一种几乎不耗费额外代价，就能得到数百倍的性能提升，何乐而不为呢？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteParser
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodInvoker &lt;/span&gt;s_parseInvoker;

    &lt;span style="color: blue"&gt;static &lt;/span&gt;RouteParser()
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;parserType = &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Route&lt;/span&gt;).Assembly.GetType(&lt;span style="color: #a31515"&gt;"System.Web.Routing.RouteParser"&lt;/span&gt;);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;parseMethod = parserType.GetMethod(&lt;span style="color: #a31515"&gt;"Parse"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.Static | &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.Public);
        s_parseInvoker = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodInvoker&lt;/span&gt;(parseMethod);
    }

    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParsedRoute &lt;/span&gt;Parse(&lt;span style="color: blue"&gt;string &lt;/span&gt;routeUrl)
    { 
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParsedRoute&lt;/span&gt;(s_parseInvoker.Invoke(&lt;span style="color: blue"&gt;null&lt;/span&gt;, routeUrl));
    }
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParsedRoute
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodInvoker &lt;/span&gt;s_matchInvoker;

    &lt;span style="color: blue"&gt;static &lt;/span&gt;ParsedRoute()
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;routeType = &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Route&lt;/span&gt;).Assembly.GetType(&lt;span style="color: #a31515"&gt;"System.Web.Routing.ParsedRoute"&lt;/span&gt;);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;matchMethod = routeType.GetMethod(&lt;span style="color: #a31515"&gt;"Match"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.Instance | &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.Public);
        s_matchInvoker = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodInvoker&lt;/span&gt;(matchMethod);
    }

    &lt;span style="color: blue"&gt;private object &lt;/span&gt;m_instance;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;ParsedRoute(&lt;span style="color: blue"&gt;object &lt;/span&gt;instance)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_instance = instance;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;Match(&lt;span style="color: blue"&gt;string &lt;/span&gt;virtualPath, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;defaultValues)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;RouteValueDictionary&lt;/span&gt;)s_matchInvoker.Invoke(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_instance, virtualPath, defaultValues);
    }
}
&lt;/pre&gt;
&lt;p&gt;两个类其实都是使用反射，从类库中获取合适的MethodInfo，然后交给MethodInvoker去执行。其他的……由于代码过于简单，我都不知道还需要解释什么东西。最后就使用xUnit测试一下吧：&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;ParseRouteTest
&lt;/span&gt;{
    [&lt;span style="color: #2b91af"&gt;Fact&lt;/span&gt;]
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Basic_Parsing()
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;parsedRoute = &lt;span style="color: #2b91af"&gt;RouteParser&lt;/span&gt;.Parse(&lt;span style="color: #a31515"&gt;"{controller}/{action}/{id}"&lt;/span&gt;);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;values = parsedRoute.Match(&lt;span style="color: #a31515"&gt;"Home/Index/5"&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
        &lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(&lt;span style="color: #a31515"&gt;"Home"&lt;/span&gt;, values[&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;, values[&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;"5"&lt;/span&gt;, values[&lt;span style="color: #a31515"&gt;"id"&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: #2b91af"&gt;Type&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"System.Web.Routing.ParsedRoute, ..."&lt;/span&gt;)]
&lt;span style="color: blue"&gt;interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IParsedRoute
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;Match(&lt;span style="color: blue"&gt;string &lt;/span&gt;virtualPath, &lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;defaultValues);
}

[&lt;span style="color: #2b91af"&gt;Type&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"System.Web.Routing.RouteParser, ..."&lt;/span&gt;)]
&lt;span style="color: blue"&gt;interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IRouteParser
&lt;/span&gt;{
    [&lt;span style="color: #2b91af"&gt;Static&lt;/span&gt;]
    &lt;span style="color: #2b91af"&gt;IParsedRoute &lt;/span&gt;Parse(&lt;span style="color: blue"&gt;string &lt;/span&gt;url);
}
&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;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;IRouteParser &lt;/span&gt;parser = &lt;span style="color: #2b91af"&gt;HackFactory&lt;/span&gt;.Create&amp;lt;&lt;span style="color: #2b91af"&gt;IRouteParser&lt;/span&gt;&amp;gt;();
&lt;span style="color: #2b91af"&gt;IParsedRoute &lt;/span&gt;route = parser.Parse(&lt;span style="color: #a31515"&gt;"{controller}/{action}/{id}"&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;RouteValueDictionary &lt;/span&gt;values = route.Match(&lt;span style="color: #a31515"&gt;"Home/Index/5"&lt;/span&gt;, &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;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/use-the-internal-feature.html#comments</comments>
      <pubDate>Wed, 19 Aug 2009 10:59:00 GMT</pubDate>
      <lastBuildDate>Wed, 19 Aug 2009 10:59:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <title>将Windows Live Writer打造为“所见即所得”编辑器</title>
      <link>http://blog.zhaojie.me/2009/08/windows-live-writer-wysiwyg.html</link>
      <guid>http://blog.zhaojie.me/2009/08/windows-live-writer-wysiwyg.html</guid>
      <description>&lt;h1&gt;前言&lt;/h1&gt; &lt;p&gt;&lt;a href="http://download.live.com/"&gt;Windows Live Writer&lt;/a&gt;（下称WLW）是博客写作利器，自从它发布之后，我几乎再也不使用博客园的在线编辑器写博客了。WLW的最大优势之一，便是可以自动获取目标博客的样式，然后让用户在特定的样式环境下编写文章。不过平心而论，WLW的“自动获取样式”功能并不怎么实用，因为在很多时候它都无法正确地获取到适合编辑的样式。这并不是WLW的错，因为不同网页的样式各有不同，我们有时候很难单独“取出”页面的一部分，同时保持着良好的样式——甚至有些样式还会牵涉到JavaScript。事实上，您目前看到的博客样式便无法由WLW自动得到，但是经过一定“手工打造”，我们还是可以在一个良好的样式环境中编写博客的。例如，以下便是我编写博客时的状态：&lt;/p&gt;&lt;a href="http://img.zhaojie.me/blog/168980/o_jeffz-wlw-edit.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/168980/r_jeffz-wlw-edit.png" width="450"&gt;&lt;/a&gt;  &lt;p&gt;它和您目前所看到样式是不是几乎一模一样？&lt;/p&gt; &lt;p&gt;事实上，我最近才忽然意识到，我们其实可以将其作为一个“HTML编辑器”，而不是一个“博客编辑器”使用。也就是说，我们可以利用它在特定样式环境下编写HTML内容。由于WLW已经具备了比较常用的功能，且生成出来的HTML代码非常干净，因此它的还是一个比较合适的HTML编辑器。事实上，我正打算以后就用WLW为&lt;a href="http://www.infoq.com/cn"&gt;InfoQ中文站&lt;/a&gt;写稿。因此目前这篇文章将会提供一个指南，希望可以帮助您打造一个合适的编辑环境。&lt;/p&gt; &lt;p&gt;补充一点：在这篇文章中，我们会多次提及“所见即所得”编辑方式，它在这里表示了“带有丰富样式”的编辑方式，而不是指最基础的编辑功能。&lt;/p&gt; &lt;h1&gt;为InfoQ中文站打造合适的编辑器&lt;/h1&gt; &lt;p&gt;首先，您需要&lt;a href="http://download.live.com/"&gt;下载&lt;/a&gt;并安装Windows Live Writer。在进行下一步之前，最好再&lt;a href="http://space.cnblogs.com/forum/topic/8550/"&gt;创建一个博客&lt;/a&gt;。因为我并没有在“全新”的环境下进行过尝试，因此严格来说，我无法百分之一百保证全新环境下进行操作不会出现问题。&lt;/p&gt; &lt;p&gt;完成了第一步之后，您可以&lt;a href="http://files.cnblogs.com/JeffreyZhao/WLW-InfoQ-Edit.zip"&gt;点此下载&lt;/a&gt;一个补丁包，您会发现其中包含两个文件：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;InfoQ-Edit.reg注册表文件  &lt;li&gt;BlogTemplates目录 &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;然后，依次进行以下几个步骤：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;打开注册表编辑器（regedit.exe），备份HKEY_CURRENT_USER\Software\Microsoft\Windows Live\Writer\Weblogs节点下的内容。  &lt;li&gt;将InfoQ-Edit.reg文件中的内容倒入注册表。  &lt;li&gt;将BlogTemplates目录复制到C:\Users\%your_account%\AppData\Roaming\Windows Live Writer中去（可能会随着您的系统有所不同，请注意AppData可能是隐藏文件夹）。如果已经存在了BlogTemplates目录，直接覆盖即可。 &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;打开Windows Live Writer，您会发现目前处在一个名为“InfoQ中文站”的博客中（如果没有，请在Blogs菜单中选择“InfoQ中文站”），但还是处于普通编辑模式下。现在，请选择View菜单下的Edit using theme选项（或使用Ctrl + F11），稍等片刻（正在下载样式文件），大公告成！如果没有意外的话，效果应该是这样的：&lt;/p&gt;&lt;a href="http://img.zhaojie.me/blog/168980/o_infoq-wlw-edit.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/168980/r_infoq-wlw-edit.png" width="450"&gt;&lt;/a&gt;  &lt;p&gt;尝试着修改标题和内容吧。自然，您是无法发布文章的，您可以将编辑模式切换至HTML，并将这些HTML复制到线上的编辑器里再发布。&lt;/p&gt; &lt;p&gt;接下来，我们就来进行一些简单分析。希望分析过后，您可以自由地将WLW打造成自己想要的样子——至少可以自定义编辑模板吧。另外，如果您在以上那些操作之后没有得到预期效果，也可以检查一下到底出了什么问题。&lt;/p&gt; &lt;h1&gt;注册表信息&lt;/h1&gt; &lt;p&gt;很明显，WLW是由注册表配合文件进行定制的，我们先从注册表这边入手。简单地说，注册表保存了您在WLW中定义的帐户信息。&lt;/p&gt; &lt;p&gt;InfoQ-Edit.reg文件向注册表内倒入多个数据。首先，它在HKCU\Software\Microsoft\Windows Live\Writer\Weblogs节点下定义了一个名为DefaultWeblog的字符串值，其中包含了默认帐户的GUID。默认帐户即您打开WLW之后所在的博客帐户。每个博客帐户都会在Weblogs节点下出现一个新的节点，节点的名称便是这个帐户的GUID。因此，我们下面关注的便是Weblogs节点下的db500b71-6eda-40d1-a6e3-9c196c68225f节点，它便表示了刚导入的“InfoQ中文站”这个帐户。&lt;/p&gt; &lt;p&gt;在这里我们也不会关注这个节点下的太多内容，您可以自行进行分析，大部分的数据我想还是比较容易猜到是什么作用的。因此，现在请将注意力放在EditorTemplate节点中，其中包含三个值：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;DisplayWebLayoutWarning：&lt;/strong&gt;1则表示在切换到“所见即所得”编辑方式时是否出现警告文字。我已经将其设为0，即不进行提示。  &lt;li&gt;&lt;strong&gt;EditUsingStyles：&lt;/strong&gt;1则表示使用“所见即所得”进行编辑，它反映的便是WLW菜单View中Edit using theme的状态。  &lt;li&gt;&lt;strong&gt;LastEditView：&lt;/strong&gt;表示上次关闭博客帐号时所处的状态，如Normal表示“普通编辑方式”，WebLayout则表示“所见即所得”编辑方式。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;EditorTemplate节点还包含了子节点，其中包含两个值：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Framed：&lt;/strong&gt;处于“所见即所得”编辑状态时所使用的模板文件名（目前是Edit.htm）。  &lt;li&gt;&lt;strong&gt;Webpage：&lt;/strong&gt;处于Preview状态时所使用的模板文件名（目前是Preview.htm）。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Framed和Webpage的区别在于，前者可能只提供了一个“编辑区”样式（如第一张图片中，我使用的博客编辑样式），而后者用于预览编辑效果，因此则可能是个完整页面。不过在您刚才安装的补丁中，您会发现Edit.htm和Preview.htm的内容完全相同。所以您会发现，即时是在编辑的时候，我们也可以看到InfoQ页面上几乎完整的内容。&lt;/p&gt; &lt;h1&gt;模板内容&lt;/h1&gt; &lt;p&gt;在注册表中定义的只是模板的“文件名”，而真正的模板是存放在硬盘上的。没错，就是在C:\Users\%your_account%\AppData\Roaming\Windows Live Writer\BlogTemplates中（可能随着系统有所不同），其中应该已经包含了用博客帐户的GUID为名称的文件夹。进入，您就会发现Edit.htm和Preview.htm两个文件。&lt;/p&gt; &lt;p&gt;这两个文件其实都是普通的文件夹，只不过包含了两个站位符{post-title}和{post-body}，作用不言自明。如之前所说那样，Edit.htm可能只包含了简单的输入区域，因此对于我的博客其中就包含了这样的代码：&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;div &lt;/span&gt;&lt;span style="color: red"&gt;id&lt;/span&gt;&lt;span style="color: blue"&gt;="container"&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;div &lt;/span&gt;&lt;span style="color: red"&gt;id&lt;/span&gt;&lt;span style="color: blue"&gt;="wrapper"&amp;gt;
        &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;div &lt;/span&gt;&lt;span style="color: red"&gt;id&lt;/span&gt;&lt;span style="color: blue"&gt;="content"&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;div &lt;/span&gt;&lt;span style="color: red"&gt;id&lt;/span&gt;&lt;span style="color: blue"&gt;="post" &lt;/span&gt;&lt;span style="color: red"&gt;class&lt;/span&gt;&lt;span style="color: blue"&gt;="post"&amp;gt;
                &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;{post-title}&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;h2&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
                &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;div &lt;/span&gt;&lt;span style="color: red"&gt;class&lt;/span&gt;&lt;span style="color: blue"&gt;="entry"&amp;gt;&lt;/span&gt;{post-body}&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;div&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;div&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;div&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;div&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;div&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;那么样式呢？与普通网页一样，样式是也是通过模板文件中的&amp;lt;link /&amp;gt;节点引入，或直接写在HTML的&amp;lt;style /&amp;gt;节点中的。至于引入的CSS文件的位置，可以是远端，也可以是本地。不过如果您选择引入本地的CSS，则需要提供一个绝对路径，例如：&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;link &lt;/span&gt;&lt;span style="color: red"&gt;rel&lt;/span&gt;&lt;span style="color: blue"&gt;="stylesheet" &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;="text/css" &lt;/span&gt;&lt;span style="color: red"&gt;href&lt;/span&gt;&lt;span style="color: blue"&gt;="file:///C:/Users/.../common.css" /&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;不过在目前的InfoQ的模板中，我在页面上直接引入了在线的CSS。因此，在切换至“所见即所得”编辑模式时可能需要等待一段时间，此时WLW正在下载CSS文件。如果您需要提高访问速度，则不妨将所有的CSS文件下载至本地，再修改Edit.htm中&amp;lt;link /&amp;gt;节点中引入的地址即可。&lt;/p&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;还有什么需要总结的呢？把工具进行合适的定制可以大幅提高我们的工作效率。例如，现在我们可以直接在写文章时看到样式（包括代码，图片浮动等等），而不用发表为“草稿”再去页面上浏览了，对于我这种博客样式的完美主义者非常有帮助。&lt;/p&gt;
&lt;p&gt;再说，定制工具难道不也是程序员的乐趣之一吗？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/windows-live-writer-wysiwyg.html#comments</comments>
      <pubDate>Mon, 17 Aug 2009 16:14:00 GMT</pubDate>
      <lastBuildDate>Mon, 17 Aug 2009 16: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>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>为ASP.NET MVC扩展异步Action功能（下）</title>
      <link>http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action-2.html</link>
      <guid>http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action-2.html</guid>
      <description>&lt;p&gt;本文分为上下两部分，您也可以从《&lt;a href="http://weblogs.asp.net/jeffreyzhao/archive/2009/01/30/extend-asp-net-mvc-for-asynchronous-action.aspx"&gt;Extend ASP.NET MVC for Asynchronous Action&lt;/a&gt;》获得全部内容。&lt;/p&gt;
&lt;h1&gt;执行Action方法&lt;/h1&gt; &lt;p&gt;对于执行同步Action的SyncMvcHandler，其实现十分简单而直接：&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;SyncMvcHandler &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IHttpHandler&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IRequiresSessionState
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;SyncMvcHandler(
        &lt;span style="color: #2b91af"&gt;IController &lt;/span&gt;controller,
        &lt;span style="color: #2b91af"&gt;IControllerFactory &lt;/span&gt;controllerFactory,
        &lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller = controller;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.ControllerFactory = controllerFactory;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.RequestContext = requestContext;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IController &lt;/span&gt;Controller { &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;RequestContext &lt;/span&gt;RequestContext { &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;IControllerFactory &lt;/span&gt;ControllerFactory { &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 virtual bool &lt;/span&gt;IsReusable { &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return false&lt;/span&gt;; } }

    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;ProcessRequest(&lt;span style="color: #2b91af"&gt;HttpContext &lt;/span&gt;context)
    {
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller.Execute(&lt;span style="color: blue"&gt;this&lt;/span&gt;.RequestContext);
        }
        &lt;span style="color: blue"&gt;finally
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.ControllerFactory.ReleaseController(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller);
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;而对于异步Action，我之前一直思考着怎么将框架的默认实现，也就是单个方法调用，转化成两个方法（BeginXxx/EndXxx）调用。曾经我想过自己实现一个新的ActionInvoker，但是这就涉及到了大量的工作，尤其是如果希望保持框架现有的功能（ActionFilter，ActionSelector等等），最省力的方法可能就是继承ControllerActionInvoker，并设法使用框架已经实现的各种辅助方法。但是在分析了框架代码之后我发现复用也非常困难，举例来说，ControllerActionInvoker判定一个方法为Action的依据之一是这个方法返回的是ActionResult类型或其子类，这意味着我无法直接使用这个方法来获取一个返回IAsyncResult的BeginXxx方法；同理，对于查找EndXxx方法，我可能需要在请求名为Abc的异步Action时，将EndAbc作为查找依据交由现成的方法来查询——但是，如果又有一个请求是直接针对一个名为EndAbc的同步Action的那又怎么办呢？&lt;/p&gt;
&lt;p&gt;由于这些问题存在，我在去年设法实现异步Action时几乎重写了整个ActionInvoker——其复杂程度可见一斑。而且那个实现对于一些特殊情况的处理依旧不甚友好，需要开发人员在一定程度上做出妥协。这个实现在TechED 2008 China的Session中公布时我就承认它并不能让我满意，建议大家不要将其投入生产环境中。而现在的实现，则非常顺利地解决了整个问题。虽然从理论上讲还不够“完美”，虽然还做出了一些让步。&lt;/p&gt;
&lt;p&gt;带来如此多问题的原因就在于我们在设法颠覆框架内部的关键性设计，也就是从单一的Action方法调用，转变为“符合APM的”二段式调用。等等，您是否感觉到了解决问题的关键？没错，那就是“符合APM的”。APM要求我们将一个行为分为BeginXxx和EndXxx两个方法，可是既然ASP.NET MVC框架只能让我们返回一个ActionResult对象……那么我们为什么不在这个对象里包含方法的引用——也就是一个委托对象呢？这虽然不符合正统的APM签名，但是完全可行，不是吗？&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;AsyncActionResult &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;ActionResult
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;AsyncActionResult(
        &lt;span style="color: #2b91af"&gt;IAsyncResult &lt;/span&gt;asyncResult,
        &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;IAsyncResult&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;ActionResult&lt;/span&gt;&gt; endDelegate)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.AsyncResult = asyncResult;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.EndDelegate = endDelegate;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IAsyncResult &lt;/span&gt;AsyncResult { &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;Func&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;IAsyncResult&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;ActionResult&lt;/span&gt;&gt; EndDelegate { &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 override void &lt;/span&gt;ExecuteResult(&lt;span style="color: #2b91af"&gt;ControllerContext &lt;/span&gt;context)
    {
        context.Controller
            .SetAsyncResult(&lt;span style="color: blue"&gt;this&lt;/span&gt;.AsyncResult)
            .SetAsyncEndDelegate(&lt;span style="color: blue"&gt;this&lt;/span&gt;.EndDelegate);
    }
}
&lt;/pre&gt;
&lt;p&gt;由于在Action方法中可以调用BeginXxx方法，我们在AsyncActionResult中只需保留Begin方法返回的IAsyncResult，以及另一个对于EndXxx方法的引用。在AsyncActionResult的ExecuteResult方法中将会保存这两个对象，以便在AsyncMvcHandler的EndProcessRequest方法中重新获取并使用。根据“惯例”，我们还需要定义一个扩展方法，方便开发人员在Action方法中返回一个AsyncActionResult。具体实现非常容易，在这里就展示一下异步Action的编写方式：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;AsyncAction&lt;/span&gt;]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;AsyncAction(&lt;span style="color: #2b91af"&gt;AsyncCallback &lt;/span&gt;asyncCallback, &lt;span style="color: blue"&gt;object &lt;/span&gt;asyncState)
{
    &lt;span style="color: #2b91af"&gt;SqlConnection &lt;/span&gt;conn = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SqlConnection&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"...;Asynchronous Processing=true"&lt;/span&gt;);
    &lt;span style="color: #2b91af"&gt;SqlCommand &lt;/span&gt;cmd = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SqlCommand&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"WAITFOR DELAY '00:00:03';"&lt;/span&gt;, conn);
    conn.Open();

    &lt;span style="color: blue"&gt;return this&lt;/span&gt;.Async(
        cmd.BeginExecuteNonQuery(asyncCallback, asyncState),
        (ar) =&gt;
        {
            &lt;span style="color: blue"&gt;int &lt;/span&gt;value = cmd.EndExecuteNonQuery(ar);
            conn.Close();
            &lt;span style="color: blue"&gt;return this&lt;/span&gt;.View();
        });
}
&lt;/pre&gt;
&lt;p&gt;至此，似乎AsyncMvcHandler也无甚秘密可言了：&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;AsyncMvcHandler &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IHttpAsyncHandler&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IRequiresSessionState
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;AsyncMvcHandler(
        &lt;span style="color: #2b91af"&gt;Controller &lt;/span&gt;controller,
        &lt;span style="color: #2b91af"&gt;IControllerFactory &lt;/span&gt;controllerFactory,
        &lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller = controller;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.ControllerFactory = controllerFactory;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.RequestContext = requestContext;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Controller &lt;/span&gt;Controller { &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;RequestContext &lt;/span&gt;RequestContext { &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;IControllerFactory &lt;/span&gt;ControllerFactory { &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;HttpContext &lt;/span&gt;Context { &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;IAsyncResult &lt;/span&gt;BeginProcessRequest(
        &lt;span style="color: #2b91af"&gt;HttpContext &lt;/span&gt;context,
        &lt;span style="color: #2b91af"&gt;AsyncCallback &lt;/span&gt;cb,
        &lt;span style="color: blue"&gt;object &lt;/span&gt;extraData)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Context = context;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller.SetAsyncCallback(cb).SetAsyncState(extraData);

        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            (&lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IController&lt;/span&gt;).Execute(&lt;span style="color: blue"&gt;this&lt;/span&gt;.RequestContext);
            &lt;span style="color: blue"&gt;return&lt;/span&gt; &lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller.GetAsyncResult();
        }
        &lt;span style="color: blue"&gt;catch
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.ControllerFactory.ReleaseController(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller);
            &lt;span style="color: blue"&gt;throw&lt;/span&gt;;
        }
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;EndProcessRequest(&lt;span style="color: #2b91af"&gt;IAsyncResult &lt;/span&gt;result)
    {
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: #2b91af"&gt;HttpContext&lt;/span&gt;.Current = &lt;span style="color: blue"&gt;this&lt;/span&gt;.Context;
            &lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;actionResult = &lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller.GetAsyncEndDelegate()(result);
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(actionResult != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
            {
                actionResult.ExecuteResult(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller.ControllerContext);
            }
        }
        &lt;span style="color: blue"&gt;finally&lt;/span&gt;
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.ControllerFactory.ReleaseController(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Controller);
        }
    }&lt;span style="color: blue"&gt;
&lt;/span&gt;}
&lt;/pre&gt;
&lt;p&gt;在BeginProcessRequest方法中将保存当前Context——这点很重要，HttpContext.Current是基于CallContext的，一旦经过一次异步回调HttpContext.Current就变成了null，我们必须重设。接着将接收到的AsyncCallback和AsyncState保留，并使用框架中现成的Execute方法执行控制器。当Execute方法返回时一整个Action方法的调用流程已经结束，这意味着其调用结果——即IAsyncResult和EndDelegate对象已经保留。于是将IAsyncResult对象取出并返回。至于EndProcessRequest方法，只是将BeginProcessRequest方法中保存下来的EndDelegate取出，调用，把得到的ActionResult再执行一遍即可。&lt;/p&gt;
&lt;p&gt;以上的代码只涉及到普通情况下的逻辑，而在完整的代码中还会包括对于Action方法被某个Filter终止或替换等特殊情况下的处理。此外，无论在BeginProcessRequest还是EndProcessRequest中都需要对异常进行合适地处理，使得Controller Factory能够及时地对Controller对象进行释放。&lt;/p&gt;
&lt;h1&gt;ModelBinder支持&lt;/h1&gt;
&lt;p&gt;其实您到目前为止还不能使用异步Action，因为您会发现方法的AsyncCallback参数得到的永远是null。这是因为默认的Model Binder无法得知如何从一个上下文环境中得到一个AsyncCallback对象。这一点倒非常简单，我们只需要构造一个AsyncCallbackModelBinder，而它的BindModel方法仅仅是将AsyncMvcHandler.BeginProcessRequest方法中保存的AsyncCallback对象取出并返回：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public sealed class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncCallbackModelBinder &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.Controller.GetAsyncCallback();
    }
}
&lt;/pre&gt;
&lt;p&gt;其使用方式，便是在应用程序启动时将其注册为AsyncCallback类型的默认Binder：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;protected void &lt;/span&gt;Application_Start()
{
    RegisterRoutes(&lt;span style="color: #2b91af"&gt;RouteTable&lt;/span&gt;.Routes);
    &lt;span style="color: #2b91af"&gt;ModelBinders&lt;/span&gt;.Binders[&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;AsyncCallback&lt;/span&gt;)] = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncCallbackModelBinder&lt;/span&gt;();&lt;span style="color: green"&gt;
&lt;/span&gt;}
&lt;/pre&gt;
&lt;p&gt;对于asyncState参数您也可以使用类似的做法，不过这似乎有些不妥，因为object类型实在过于宽泛，并不能明确代指asyncState参数。事实上，即使您不为asyncState设置binder也没有太大问题，因为对于一个异步ASP.NET请求来说，其asyncState永远是null。如果您一定要指定一个binder，我建议您在每个Action方法的asyncState参数上标记如下的Attribute，它和AsyncStateModelBinder也已经被一并建入项目中了：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;AttributeUsage&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;AttributeTargets&lt;/span&gt;.Parameter, AllowMultiple = &lt;span style="color: blue"&gt;false&lt;/span&gt;, Inherited = &lt;span style="color: blue"&gt;false&lt;/span&gt;)]
&lt;span style="color: blue"&gt;public sealed class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncStateAttribute &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;CustomModelBinderAttribute
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncStateModelBinder &lt;/span&gt;s_modelBinder = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncStateModelBinder&lt;/span&gt;();

    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IModelBinder &lt;/span&gt;GetBinder()
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;s_modelBinder;
    }
}
&lt;/pre&gt;
&lt;p&gt;使用方式如下：&lt;/p&gt;&lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;AsyncAction&lt;/span&gt;]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;AsyncAction(&lt;span style="color: #2b91af"&gt;AsyncCallback &lt;/span&gt;cb, [&lt;span style="color: #2b91af"&gt;AsyncState&lt;/span&gt;]&lt;span style="color: blue"&gt;object &lt;/span&gt;state) { ... }&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;其实，基于Controller的扩展方法GetAsyncCallback和GetAsyncState均为公有方法，您也可以让Action方法不接受这两个参数而直接从Controller中获取——当然这种做法降低了可测试性，不值得提倡。&lt;/p&gt;
&lt;h1&gt;限制和缺点&lt;/h1&gt;
&lt;p&gt;如果这个解决方案没有缺陷，那么相信它已经被放入ASP.NET MVC 1.0中，而轮不到我在这里扩展一番了。目前的这个解决方案至少有以下几点不足：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没有严格遵守.NET中的APM模式，虽然不影响功能，但这始终是一个遗憾。 
&lt;li&gt;由于利用了框架中的现成功能，所有的Filter只能运行在BeginXxx方法上。 
&lt;li&gt;由于EndXxx方法和最终ActionResult的执行都没有Filter支持，因此如果在这个过程中抛出了异常，将无法进入ASP.NET MVC建议的异常处理功能中。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;根据ASP.NET MVC框架的Roadmap，ASP.NET MVC框架1.0之后的版本中将会支持异步Action，相信以上这些缺陷到时候都能被弥补。不过这就需要大量的工作，这只能交给ASP.NET MVC团队去慢慢执行了。事实上，您现在已经可以在ASP.NET MVC RC源代码的MvcFutures项目中找到异步Action处理的相关内容。它添加了IAsyncController，AsyncController，IAsyncActionInvoker，AsyncControllerActionInvoker等许多扩展。虽说它们都“继承”了现有的类，但是与我之前的判断相似，如AsyncControllerActionInvoker几乎完全重新实现了一遍ActionInvoker中的各种功能——我还没有仔细阅读代码，因此无法判断出这种设计是否优秀，只希望它能像ASP.NET MVC本身那样的简单和优雅。&lt;/p&gt;
&lt;p&gt;接下来，我打算为现在的代码的EndXxx方法也加上Filter支持，我需要仔细阅读ASP.NET MVC的源代码来寻找解决方案。希望它能够成为ASP.NET MVC正式支持异步Action之前较好的替代方案。&lt;/p&gt;
&lt;h1&gt;更多资料&lt;/h1&gt;
&lt;p&gt;&lt;a href="http://code.msdn.microsoft.com/AsyncMvc"&gt;完整的项目代码&lt;/a&gt;已经放置在&lt;a href="http://code.msdn.microsoft.com"&gt;MSDN Code Gallery&lt;/a&gt;中，您可以在这里访问到关于它的“&lt;a href="http://code.msdn.microsoft.com/AsyncMvc/Wiki/View.aspx?title=Benchmark&amp;referringTitle=Home"&gt;性能测试&lt;/a&gt;”等更多信息。这篇文章着重讲解了扩展的设计原理，省略了涉及特殊状况处理以及程序健壮性等实现细节的描述，欢迎您&lt;a href="http://code.msdn.microsoft.com/AsyncMvc/Release/ProjectReleases.aspx?ReleaseId=2197"&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/02/extend-asp-net-mvc-for-asynchronous-action.html"&gt;为ASP.NET MVC扩展异步Action功能（上）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;为ASP.NET MVC扩展异步Action功能（下）&lt;/li&gt;&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action-2.html#comments</comments>
      <pubDate>Wed, 04 Feb 2009 01:04:00 GMT</pubDate>
      <lastBuildDate>Wed, 04 Feb 2009 01:04: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>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>为ASP.NET MVC扩展异步Action功能（上）</title>
      <link>http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action.html</link>
      <guid>http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action.html</guid>
      <description>&lt;p&gt;本文将分为上下两部分，您也可以从《&lt;a href="http://weblogs.asp.net/jeffreyzhao/archive/2009/01/30/extend-asp-net-mvc-for-asynchronous-action.aspx"&gt;Extend ASP.NET MVC for Asynchronous Action&lt;/a&gt;》获得全部内容。&lt;/p&gt; &lt;p&gt;异步请求处理是ASP.NET 2.0中引入的高级特性，它依托IO Complete Port，对于提高IO密集型应用程序的吞吐量非常重要（详见&lt;a href="http://blog.zhaojie.me/2008/02/use-async-operation-properly.html"&gt;原理描述&lt;/a&gt;和&lt;a href="http://blog.zhaojie.me/2009/01/lab-async-request.html"&gt;性能测试&lt;/a&gt;）。但是目前ASP.NET MVC框架缺少异步Action功能，这也就是老赵经常挂在嘴边的那个“目前ASP.NET MVC所缺少的非常重要的功能”。在TechED 2008 China的Session中我曾经给出过一个所谓的“解决方案”，但是它复杂性之高使那个解决方案有太多限制。为了弥补TechED上的遗憾，以及准备.NET开发大会上的ASP.NET MVC最佳实践的Session，我在春节休假期间仔细思考了一下这方面的问题，得出了一个相对不错的扩展：完整，方便，并且非常轻巧——核心逻辑代码只有200行左右，这意味着绝大部分功能将会委托给框架中现成的内容，确保了扩展的稳定，高效并且拥有较好的向后兼容性。&lt;/p&gt; &lt;p&gt;值得一提的是，我在1/26号便基于ASP.NET MVC的Beta版本写出了这个扩展的第一个版本，而在不久之后微软发布了ASP.NET MVC RC。我在移植解决方案的过程中发现ASP.NET MVC RC在框架设计上进行了较大的改进，这使得我在构建扩展时的策略发生了些许变化。令人欣喜的是，RC版本的这些变化对于构建一个扩展，尤其是现在这种“低端”级别的扩展变得更加容易。ASP.NET MVC框架实现了它“到处可扩展”的承诺。&lt;/p&gt; &lt;p&gt;那么我们现在就来详细分析一下这个扩展的实现方式。&lt;/p&gt; &lt;h1&gt;请求处理方式的改变&lt;/h1&gt; &lt;p&gt;在制定基本改造策略之前，我们需要了解ASP.NET MVC框架目前的架构及请求处理流程。如下：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;在应用程序启动时（此时还没有接受任何请求），将针对MVC请求的Route策略注册至ASP.NET Routing模块。此时每个Route策略（即Route对象）中的RouteHandler属性为ASP.NET MVC框架中的MvcRouteHandler。  &lt;li&gt;当ASP.NET Routing模块接收到一个匹配某个Route策略的HTTP请求时，将会调用该Route对象中RouteHandler对象的GetHttpHandler以获取一个HttpHandler，并交由ASP.NET执行。MvcRouteHandler永远将返回一个MvcHandler对象。  &lt;li&gt;MvcHandler在执行时，将取出RouteData中的controller值，并以此构建一个实现了IController接口的控制器对象，并调用IController接口的Execute方法执行该控制器。  &lt;li&gt;对于一个ASP.NET MVC应用程序来说，大部分控制器将会继承System.Web.Mvc.Controller类型。Controller类将会从RouteData获取action值，并交给实现IActionInvoker接口的对象来执行一个Action。  &lt;li&gt;……&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;如果我们要将这个流程改造成异步处理，那么就要让它符合ASP.NET架构中的异步处理方式。ASP.NET架构对于异步请求的处理可以体现在好几种方式上，例如异步页面，异步Http Module等，而最适合目前场合的做法自然是异步Http Handler。为实现一个异步Handler，我们需要让处理请求的Handler实现IHttpAsyncHandler接口，而不是传统的IHttpHandler接口。IHttpAsyncHandler接口中的BeginProcessRequest和EndProcessRequest两个方法构成了.NET中的APM（Aynchronous Programming Model，异步编程模型）模式，可以使用“二段式”的异步调用来处理一个HTTP请求。&lt;/p&gt; &lt;p&gt;您应该已经发现，如果我们要支持异步Action，就必须根据当前的请求信息来确认究竟是执行一个IHttpHandler对象还是IHttpAsyncHandler对象。而在ASP.NET MVC框架在默认情况下是在Http Handler（即MvcHandler对象）内部进行控制器的检查，构造和调用。这为时已晚，我们必须讲这些逻辑提前到Routing过程中才行。幸运的是，ASP.NET Routing所支持的IRouteHandler就像是ASP.NET中的IHttpHandlerFactory，可以根据情况生成不同的Handler来执行。因此，我们只要构建一个新的IRouteHandler类型即可。于是就诞生了AsyncMvcRouteHandler——可以想象的出，其中的部分代码与框架中的MvcHandler相同，因为在一定程度上我们的确只是把原本在MvcHandler里做的事情给提前了：&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;AsyncMvcRouteHandler &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IRouteHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IHttpHandler &lt;/span&gt;GetHttpHandler(&lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext)
    {
        &lt;span style="color: blue"&gt;string &lt;/span&gt;controllerName = requestContext.RouteData.GetRequiredString(&lt;span style="color: #a31515"&gt;"controller"&lt;/span&gt;);

        &lt;span style="color: blue"&gt;var&lt;/span&gt;&lt;span style="color: #2b91af"&gt; &lt;/span&gt;factory = &lt;span style="color: #2b91af"&gt;ControllerBuilder&lt;/span&gt;.Current.GetControllerFactory();
        &lt;span style="color: blue"&gt;var&lt;/span&gt;&lt;span style="color: #2b91af"&gt; &lt;/span&gt;controller = factory.CreateController(requestContext, controllerName);
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(controller == &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;InvalidOperationException&lt;/span&gt;(...);
        }

        &lt;span style="color: blue"&gt;var&lt;/span&gt;&lt;span style="color: #2b91af"&gt; &lt;/span&gt;coreController = controller &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Controller&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(coreController == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SyncMvcHandler&lt;/span&gt;(controller, factory, requestContext);
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{

            &lt;span style="color: blue"&gt;string &lt;/span&gt;actionName = requestContext.RouteData.GetRequiredString(&lt;span style="color: #a31515"&gt;"action"&lt;/span&gt;);
            &lt;span style="color: blue"&gt;return&lt;/span&gt; IsAsyncAction(coreController, actionName, requestContext) ?
                (&lt;span style="color: #2b91af"&gt;IHttpHandler&lt;/span&gt;)&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncMvcHandler&lt;/span&gt;(coreController, factory, requestContext) :
                (&lt;span style="color: #2b91af"&gt;IHttpHandler&lt;/span&gt;)&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SyncMvcHandler&lt;/span&gt;(controller, factory, requestContext);
        }
    }

    &lt;span style="color: blue"&gt;internal static bool &lt;/span&gt;IsAsyncAction(
        &lt;span style="color: #2b91af"&gt;Controller &lt;/span&gt;controller, &lt;span style="color: blue"&gt;string &lt;/span&gt;actionName, &lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext)
    {
        ...
    }
}
&lt;/pre&gt;
&lt;p&gt;在GetHttpHandler方法中，我们先从RouteData的controller字段中获取控制器的名字，并通过注册在ControllerBuilder上的Factory来创建一个实现了IController接口的控制器对象。由于我们需要使用Controller类中包含的ActionInvoker来辅助检测Action的异步需求，因此我们会设法将其转化为Controller类型。如果转换成功，就会取出RouteData中的action字段的值，并通过IsAsyncAction方法来确认当前Action是否应该异步执行。如果是，则返回一个实现了IHttpAsyncHandler的AsyncMvcHandler对象，否则就返回一个实现IHttpHandler的SyncMvcHandler对象。&lt;/p&gt;
&lt;p&gt;至于AsyncMvcRouteHandler的使用，只需在MapRoute时将Route Handler重新设置一下即可：&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.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;).RouteHandler = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AsyncMvcRouteHandler&lt;/span&gt;();
}
&lt;/pre&gt;
&lt;h1&gt;检查是否为异步Action&lt;/h1&gt;
&lt;p&gt;从上面的代码中我们已经形成了一个约定：如果要执行一个异步Action，那么控制器对象必须为Controller类型。这个约定的目的是为了使用Controller类中包含的IActionInvoker——确切地说，是ControllerActionInvoker类型里的功能。因此，另一个约定便是Controller的ActionInvoker对象必须返回一个ControllerActionInvoker的实例。&lt;/p&gt;
&lt;p&gt;ControllerActionInvoker中有一些辅助方法，能够返回对于一个Controller或Action的描述对象。从一个Action描述对象中我们可以获取关于这个Action的各种信息，而它是否被标记了AsyncActionAttribute，就是我们判断这个Action是否应该被异步执行的依据。如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private static object &lt;/span&gt;s_methodInvokerMutex = &lt;span style="color: blue"&gt;new object&lt;/span&gt;();
&lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodInvoker &lt;/span&gt;s_controllerDescriptorGetter;

&lt;span style="color: blue"&gt;internal static bool &lt;/span&gt;IsAsyncAction(
    &lt;span style="color: #2b91af"&gt;Controller &lt;/span&gt;controller, &lt;span style="color: blue"&gt;string &lt;/span&gt;actionName, &lt;span style="color: #2b91af"&gt;RequestContext &lt;/span&gt;requestContext)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;actionInvoker = controller.ActionInvoker &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ControllerActionInvoker&lt;/span&gt;;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(actionInvoker == &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;(s_controllerDescriptorGetter == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        &lt;span style="color: blue"&gt;lock &lt;/span&gt;(s_methodInvokerMutex)
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(s_controllerDescriptorGetter == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
            {
                &lt;span style="color: #2b91af"&gt;BindingFlags &lt;/span&gt;bindingFlags = &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.Instance | &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.NonPublic;
                &lt;span style="color: #2b91af"&gt;MethodInfo &lt;/span&gt;method = &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ControllerActionInvoker&lt;/span&gt;).GetMethod(
                    &lt;span style="color: #a31515"&gt;"GetControllerDescriptor"&lt;/span&gt;, bindingFlags);
                s_controllerDescriptorGetter = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodInvoker&lt;/span&gt;(method);
            }
        }
    }

    &lt;span style="color: blue"&gt;var &lt;/span&gt;controllerContext = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ControllerContext&lt;/span&gt;(requestContext, controller);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;controllerDescriptor = (&lt;span style="color: #2b91af"&gt;ControllerDescriptor&lt;/span&gt;)s_controllerDescriptorGetter.Invoke(
        actionInvoker, controllerContext);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
    &lt;span style="color: blue"&gt;return &lt;/span&gt;actionDescriptor == &lt;span style="color: blue"&gt;null &lt;/span&gt;? &lt;span style="color: blue"&gt;false &lt;/span&gt;:
        actionDescriptor.GetCustomAttributes(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;AsyncActionAttribute&lt;/span&gt;), &lt;span style="color: blue"&gt;false&lt;/span&gt;).Any();
}
&lt;/pre&gt;
&lt;p&gt;ControllerActionInvoker类型中有个protected方法GetControllerDescriptor，它接受一个ControllerContext类型的参数，并返回一个ControllerDescriptor对象来描述当前控制器，而从该描述对象中可以通过FindAction方法获得一个ActionDescriptor对象来描述即将执行的Action。如果是一个不存在的Action，那么就返回false，最后就通过SyncMvcHandler对象来执行默认的行为。当且仅当该Action上拥有AsyncActionAttribute标记时，才说明它应该被异步执行，返回true。此外，这段代码中用到了MethodInvoker，这是一个辅助类，它来源于&lt;a href="http://www.codeplex.com/FastReflectionLib"&gt;Fast Reflection Library&lt;/a&gt;，它实现了反射调用功能，但是它的性能十分接近于方法的直接调用，我在&lt;a href="http://weblogs.asp.net/jeffreyzhao/archive/2009/01/27/fast-reflection-library.aspx"&gt;这篇文章&lt;/a&gt;中详细描述了这个项目的功能和使用。&lt;/p&gt;
&lt;p&gt;这段代码便涉及到ASP.NET MVC RC版本在Beta版本基础上的改进。在原先的ControllerActionInvoker类中只有获取Action方法的MethodInfo，而没有RC中各描述对象这样的抽象类型。从目前的设计上来看，我们使用的都是基于反射的抽象描述类型的子类。例如默认情况下，我们通过ActionDescriptor抽象类型访问的实际上是ReflectedActionDescriptor类型的实例。这是一个很有用的改进，由于我们通过描述对象进行抽象，于是我们就可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用不同的实现方式来描述各对象，默认情况下是使用基于反射（也就是“约定”）的实现，如果需要的话我们也可以使用基于配置文件的方式替换现有实现。 
&lt;li&gt;使用特定对象的描述方式可以不拘泥于内部细节，例如一个异步的Action可能就由两个方法组成。 
&lt;li&gt;有了特定的描述对象，也方便添加额外的属性，例如该Action是否应该异步执行，是否应该禁用Session State等等。 
&lt;li&gt;……&lt;/li&gt;&lt;/ul&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;为ASP.NET MVC扩展异步Action功能（上）&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action-2.html" title="为ASP.NET MVC扩展异步Action功能（下）"&gt;为ASP.NET MVC扩展异步Action功能（下）&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/02/extend-asp-net-mvc-for-asynchronous-action.html#comments</comments>
      <pubDate>Mon, 02 Feb 2009 01:22:00 GMT</pubDate>
      <lastBuildDate>Mon, 02 Feb 2009 01:22:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <title>Fast Reflection Library</title>
      <link>http://blog.zhaojie.me/2009/02/fast-reflection-library.html</link>
      <guid>http://blog.zhaojie.me/2009/02/fast-reflection-library.html</guid>
      <description>&lt;p&gt;全文英文版：&lt;a href="http://weblogs.asp.net/jeffreyzhao/archive/2009/01/27/fast-reflection-library.aspx"&gt;Fast Reflection Library&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这是我在CodePlex上创建的一个项目，它的网址是&lt;a href="http://www.codeplex.com/FastReflectionLib"&gt;http://www.codeplex.com/FastReflectionLib&lt;/a&gt;，使用Microsoft Public License (Ms-PL)，您可以随意在自己的产品中使用它的全部或部分代码。这个项目用到了我在《&lt;a href="http://blog.zhaojie.me/2008/11/invoke-method-by-lambda-expression.html"&gt;方法的直接调用，反射调用与Lambda表达式调用&lt;/a&gt;》和《&lt;a href="http://blog.zhaojie.me/2009/01/dynamicpropertyaccessor-and-fasteval.html"&gt;这下没理由嫌Eval的性能差了吧？&lt;/a&gt;》两篇文章里用到的做法，并加以提炼和扩展发布的项目——随便搞搞，留个印记，也供以后参考。&lt;/p&gt;

&lt;h1&gt;基本使用方式&lt;/h1&gt; &lt;p&gt;反射是.NET中非常重要的功能。使用反射来构造对象、调用方法或是访问属性是某些项目中常用的做法之一（例如ORM框架）。众所周知，与一个成员的直接访问相比，反射调用的性能要低好几个数量级。FastReflectionLib提供了一种简便的方式，使一些常用反射调用的性能得到大幅提高。如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;using &lt;/span&gt;System;
&lt;span style="color: blue"&gt;using &lt;/span&gt;System.Reflection;
&lt;span style="color: blue"&gt;using &lt;/span&gt;FastReflectionLib;

&lt;span style="color: blue"&gt;namespace &lt;/span&gt;SimpleConsole
{
    &lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Program
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
        {
            &lt;span style="color: #2b91af"&gt;PropertyInfo &lt;/span&gt;propertyInfo = &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;string&lt;/span&gt;).GetProperty(&lt;span style="color: #a31515"&gt;"Length"&lt;/span&gt;);
            &lt;span style="color: #2b91af"&gt;MethodInfo &lt;/span&gt;methodInfo = &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;string&lt;/span&gt;).GetMethod(&lt;span style="color: #a31515"&gt;"Contains"&lt;/span&gt;);

            &lt;span style="color: blue"&gt;string &lt;/span&gt;s = &lt;span style="color: #a31515"&gt;"Hello World!"&lt;/span&gt;;

            &lt;span style="color: green"&gt;// get value by normal reflection
            &lt;/span&gt;&lt;span style="color: blue"&gt;int &lt;/span&gt;length1 = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)propertyInfo.GetValue(s, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
            &lt;span style="color: green"&gt;// get value by the extension method from FastReflectionLib,
            // which is much faster
            &lt;/span&gt;&lt;span style="color: blue"&gt;int &lt;/span&gt;length2 = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)propertyInfo.FastGetValue(s);

            &lt;span style="color: green"&gt;// invoke by normal reflection
            &lt;/span&gt;&lt;span style="color: blue"&gt;bool &lt;/span&gt;result1 = (&lt;span style="color: blue"&gt;bool&lt;/span&gt;)methodInfo.Invoke(s, &lt;span style="color: blue"&gt;new object&lt;/span&gt;[] { &lt;span style="color: #a31515"&gt;"Hello" &lt;/span&gt;});
            &lt;span style="color: green"&gt;// invoke by the extension method from FastReflectionLib,
            // which is much faster
            &lt;/span&gt;&lt;span style="color: blue"&gt;bool &lt;/span&gt;result2 = (&lt;span style="color: blue"&gt;bool&lt;/span&gt;)methodInfo.FastInvoke(s, &lt;span style="color: blue"&gt;new object&lt;/span&gt;[] { &lt;span style="color: #a31515"&gt;"Hello" &lt;/span&gt;});
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;在得到了PropertyInfo或MethodInfo对象之后，我们可以使用GetValue或Invoke方法来访问属性或调用方法。在FastReflectionLib中为PropertyInfo、MethodInfo等对象定义了对应的扩展方法，于是我们就可以使用这些扩展方法（从代码上看来，基本上只是在原来的方法之前加上“Fast”）来进行调用，与之前的方法相比，新的扩展方法性能有极大的提高。&lt;/p&gt;
&lt;h1&gt;直接使用各工作对象&lt;/h1&gt;
&lt;p&gt;各FastXxx方法实际上是将PropertyInfo等对象作为Key去一个Cache中获取对应的工作对象，然后调用工作对象上对应的方法。因此，直接调用工作对象可以获得更好的性能。各工作对象类型的对应关系如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PropertyInfo：IPropertyAccessor 
&lt;li&gt;MethodInfo：IMethodInvoker 
&lt;li&gt;ConstructorInfo：IConstructorInvoker 
&lt;li&gt;FieldInfo：IFieldAccessor&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;我们可以使用FastReflectionCaches.MethodInvokerCache来获取一个IMethodInvoker对象：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Execute(&lt;span style="color: #2b91af"&gt;MethodInfo &lt;/span&gt;methodInfo, &lt;span style="color: blue"&gt;object &lt;/span&gt;instance, &lt;span style="color: blue"&gt;int &lt;/span&gt;times)
{ 
    &lt;span style="color: #2b91af"&gt;IMethodInvoker &lt;/span&gt;invoker = &lt;span style="color: #2b91af"&gt;FastReflectionCaches&lt;/span&gt;.MethodInvokerCache.Get(methodInfo);
    &lt;span style="color: blue"&gt;object&lt;/span&gt;[] parameters = &lt;span style="color: blue"&gt;new object&lt;/span&gt;[0];
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &lt; times; i++)
    {
        invoker.Invoke(instance, parameters);
    }
}
&lt;/pre&gt;
&lt;h1&gt;工作对象的默认实现与扩展&lt;/h1&gt;
&lt;p&gt;在FastReflectionLib中，已经提供了IPropertyAccessor等接口的默认实现。该实现将会构造一颗表达式树（Expression Tree）并将其编译（调用其Compile方法）以获得一个与反射方法签名相同的委托对象。这是一种简单、通用而安全的实现，由于Compile方法使用了Emit，其性能也较为令人满意（可见下面的性能测试）。但是这并不是性能最高的做法，如果使用Emit生成最优化的代码，其性能甚至会高于方法的直接调用（例如&lt;a href="http://www.codeplex.com/Dynamic"&gt;Dynamic Reflection Library&lt;/a&gt;）。如果您想使用更好的实现来替换，则可以自行构造一个工作对象接口的实现，并替换对应的Factory：&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;BetterPropertyAccessor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IPropertyAccessor
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;BetterPropertyAccessor(&lt;span style="color: #2b91af"&gt;PropertyInfo &lt;/span&gt;propertyInfo) { ... }

    ...
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BetterPropertyAccessorFactory &lt;/span&gt;:
    &lt;span style="color: #2b91af"&gt;IFastReflectionFactory&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;PropertyInfo&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IPropertyAccessor&lt;/span&gt;&gt;
{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPropertyAccessor &lt;/span&gt;Create(&lt;span style="color: #2b91af"&gt;PropertyInfo &lt;/span&gt;key)
    {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BetterPropertyAccessor&lt;/span&gt;(key);
    }
}

&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Program
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
    {
        &lt;span style="color: #2b91af"&gt;FastReflectionFactories&lt;/span&gt;.PropertyAccessorFactory =
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BetterPropertyAccessorFactory&lt;/span&gt;();

        ...
    }
}
&lt;/pre&gt;
&lt;h1&gt;缓存的默认实现与扩展&lt;/h1&gt;
&lt;p&gt;在FastReflectionLib中使用基于System.Collections.Generic.Dictionary&lt;TKey, TValue&gt;类型编写的缓存容器。每次调用FastXxx扩展方法时，类库将从对应的缓存容器中获取工作对象。如果缓存容器中还没有所需的工作对象，那么它就会调用合适的Factory来构造新的工作对象。从下面的性能测试来看，许多时间是消耗在缓存查找上的，如果您有更好的缓存实现，可以使用以下的方法替换默认的缓存的容器：&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;BetterMethodInvokerCache &lt;/span&gt;:
    &lt;span style="color: #2b91af"&gt;IFastReflectionCache&lt;/span&gt;&lt;&lt;span style="color: #2b91af"&gt;MethodInfo&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;IMethodInvoker&lt;/span&gt;&gt;
{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMethodInvoker &lt;/span&gt;Get(&lt;span style="color: #2b91af"&gt;MethodInfo &lt;/span&gt;key) { ... }
}

&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Program
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
    {
        &lt;span style="color: #2b91af"&gt;FastReflectionCaches&lt;/span&gt;.MethodInvokerCache = 
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BetterMethodInvokerCache&lt;/span&gt;();

        ...
    }
}
&lt;/pre&gt;
&lt;h1&gt;根据需要自行缓存工作对象&lt;/h1&gt;
&lt;p&gt;FastReflectionLib中通过PropertyInfo等对象作为Key，对PropertyAccessor等工作对象进行缓存。但是在某些场景下，您也可以选择合适的方式来自行缓存工作对象。与FastReflectionLib源码同时发布的CustomCache示例网站中包含了一个FastEval扩展，在某些场景下，我们可以使用这个更高效的方法来替换内置的Eval方法。这个示例的特点如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用对象的类型和属性名同时作为缓存的Key获取对应的PropertyAccessor对象 
&lt;li&gt;使用PropertyAccessor获取“匿名对象”中的属性值 
&lt;li&gt;缓存的作用域为特定页面，而不是整个AppDomain。&lt;/li&gt;&lt;/ul&gt;
&lt;h1&gt;性能测试&lt;/h1&gt;
&lt;p&gt;FastReflectionLib源码中包含了一个性能测试项目，您可以从中看出FastReflectionLib对于反射的性能改进。摘录部分数据如下（测试在我的笔记本上运行，Release编译）。&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;Test
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;MethodWithArgs(&lt;span style="color: blue"&gt;int &lt;/span&gt;a1, &lt;span style="color: blue"&gt;string &lt;/span&gt;a2) { }
}
&lt;/pre&gt;
&lt;p&gt;进行一百万次调用，结果如下：&lt;/p&gt;
&lt;table cellspacing="0" cellpadding="5" border="1"&gt;
&lt;tbody align="middle"&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;调用方式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;消耗时间（秒）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;方法直接调用&lt;/td&gt;
&lt;td&gt;0.0071397&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内置反射调用&lt;/td&gt;
&lt;td&gt;1.4936181&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工作对象调用&lt;/td&gt;
&lt;td&gt;0.0468326&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast方法调用&lt;/td&gt;
&lt;td&gt;0.1373712&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;</description>
      <comments>http://blog.zhaojie.me/2009/02/fast-reflection-library.html#comments</comments>
      <pubDate>Sun, 01 Feb 2009 01:25:00 GMT</pubDate>
      <lastBuildDate>Sun, 01 Feb 2009 01:25:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <title>StyledAutoComplete - 支持丰富样式的自动补全</title>
      <link>http://blog.zhaojie.me/2007/06/styleautocomplete.html</link>
      <guid>http://blog.zhaojie.me/2007/06/styleautocomplete.html</guid>
      <description>&lt;p&gt;几个月前，我&lt;a href="http://blog.zhaojie.me/2006/10/jeffz-ui-autocompletebehavior.html" target="_blank"&gt;扩展了Atlas CTP时期的的AutoCompleteBehavior&lt;/a&gt;，到了ASP.NET AJAX正式版之后这个扩展自然就不能用了。其实这段时间内有不少朋友问我该如何做到像Google Suggest那样带丰富样式的自动补全功能，但是由于各种原因（比如时间不够，还有现在的AutoCompleteBehavior复杂的许多，或者仅仅是“懒”……），我没有深入地研究它，更别提对它的扩展了。&lt;/p&gt; &lt;p&gt;&lt;img src="http://img.zhaojie.me/blog/JeffzAutoCompleteBehavior.jpg"&gt; &lt;/p&gt; &lt;p&gt;最近手头正好有些时间，也就逼迫自己耐着性子读了读AutoCompleteBehavior的代码。不得不承认Ajax Control Tookit本身也在不停的发展之中，现在无论服务器端还是客户端都提供了非常强大的基础组件，解决了大量常见问题，使得开发工作变得简单了许多。与此形成鲜明对比的是文档的缺乏，建议对于深入ASP.NET AJAX感兴趣的朋友读一下Ajax Control Toolkit的代码，这对于您理解ASP.NET的控件模型与客户端组件的开发大有碑益。&lt;/p&gt; &lt;p&gt;我扩展了现有的AutoCompleteBehavior，使AutoComplete的功能能够轻松支持丰富的样式。我在这里暂时不对实现方法进行分析，不过大家可以下载代码并进行一些测试，如果有任何问题还可以反馈给我。另外，我认为目前StyledAutoCompleteBehavior的设计还不够灵活，如果有什么需要但是无法实现的功能请您告诉我，也欢迎大家给我一些功能设计方面的建议。自动补全是一个很常用的功能，可惜Ajax Control Toolkit中提供的功能实在不够用。&lt;/p&gt; &lt;p&gt;在我的扩展中，StyledAutoCompleteExtender继承了AutoCompleteExtender，StyledAutoCompleteBehavior继承了AutoCompleteBehavior，在编写时我也尽可能的保持了原有的功能不变，因此大家在使用时完全可以将AutoCompleteExtender的标签直接改为StyledAutoComplteExtender而不会影响到任何功能。与AutoCompleteExtender相比，StyledAutoCompleteExtender多了一个属性ItemTemplate用于指定自动补全中每一项的模板。例如我们要实现Google Suggest那样的功能，则需要这样使用StyledAutoCompleteExtender：&lt;/p&gt; &lt;p&gt; &lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;jeffz&lt;/span&gt;:&lt;span style="color: #800000"&gt;StyledAutoCompleteExtender&lt;/span&gt;
    &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; 
    &lt;span style="color: #ff0000"&gt;BehaviorID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"AutoCompleteEx"&lt;/span&gt;
    &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"autoComplete1"&lt;/span&gt; 
    &lt;span style="color: #ff0000"&gt;TargetControlID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"myTextBox"&lt;/span&gt;
    &lt;span style="color: #ff0000"&gt;ServicePath&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"AutoComplete.asmx"&lt;/span&gt; 
    &lt;span style="color: #ff0000"&gt;ServiceMethod&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"GetSearchCompletionList"&lt;/span&gt;
    &lt;span style="color: #ff0000"&gt;MinimumPrefixLength&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"2"&lt;/span&gt; 
    &lt;span style="color: #ff0000"&gt;CompletionInterval&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"1000"&lt;/span&gt;
    &lt;span style="color: #ff0000"&gt;EnableCaching&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"true"&lt;/span&gt;
    &lt;span style="color: #ff0000"&gt;CompletionSetCount&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"10"&lt;/span&gt;
    &lt;span style="color: #ff0000"&gt;CompletionListCssClass&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"completionListElement"&lt;/span&gt; 
    &lt;span style="color: #ff0000"&gt;CompletionListItemCssClass&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"listItem"&lt;/span&gt; 
    &lt;span style="color: #ff0000"&gt;CompletionListHighlightedItemCssClass&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"highlightedListItem"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;ItemTemplate&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;span&lt;/span&gt; &lt;span style="color: #ff0000"&gt;style&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"float:left;"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;class&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"keywords"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;{0}&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;span&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;span&lt;/span&gt; &lt;span style="color: #ff0000"&gt;style&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"float:right;"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;class&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"result"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;{1}&amp;amp;nbsp;results&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;span&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;div&lt;/span&gt; &lt;span style="color: #ff0000"&gt;style&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"clear:both;"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;div&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;ItemTemplate&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;jeffz&lt;/span&gt;:&lt;span style="color: #800000"&gt;StyledAutoCompleteExtender&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;请注意ItemTemplate里的内容，它将作为自动补全每一项的HTML。而诸如{0}、{1}等则作为占位符，最终在显示时将会使用服务器端返回的内容进行替换。为了配合这个功能，服务器端的Web Service方法返回值也有所变化：&lt;/p&gt;
&lt;pre class="code"&gt;[WebMethod]
&lt;span style="color: #0000ff"&gt;public&lt;/span&gt; IList&amp;lt;&lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[]&amp;gt; GetSearchCompletionList(&lt;span style="color: #0000ff"&gt;string&lt;/span&gt; prefixText, &lt;span style="color: #0000ff"&gt;int&lt;/span&gt; count)
{
    &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (count == 0)
    {
        count = 10;
    }

    Random random = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; Random();
    IList&amp;lt;&lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[]&amp;gt; items = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; List&amp;lt;&lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[]&amp;gt;(count);
    &lt;span style="color: #0000ff"&gt;for&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;int&lt;/span&gt; i = 0; i &amp;lt; count; i++)
    {
        &lt;span style="color: #0000ff"&gt;char&lt;/span&gt; c1 = (&lt;span style="color: #0000ff"&gt;char&lt;/span&gt;)random.Next(65, 90);
        &lt;span style="color: #0000ff"&gt;char&lt;/span&gt; c2 = (&lt;span style="color: #0000ff"&gt;char&lt;/span&gt;)random.Next(97, 122);
        &lt;span style="color: #0000ff"&gt;char&lt;/span&gt; c3 = (&lt;span style="color: #0000ff"&gt;char&lt;/span&gt;)random.Next(97, 122);

        items.Add(&lt;span style="color: #0000ff"&gt;new&lt;/span&gt; &lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[] { prefixText + c1 + c2 + c3, random.Next(10000, 300000) });
    }

    &lt;span style="color: #0000ff"&gt;return&lt;/span&gt; items;
}&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;方法的返回值变成了一个存放object数组的列表，每个object数组将会作为自动补全中每一项的信息发送到客户端。object数组的每个元素将用于替换模板中的占位符（事实上，我使用了String.format方法来获得每一项的HTML，因此事实上您也可以在这里使用本地化的功能等等）。&lt;/p&gt;
&lt;p&gt;模拟Google Suggest的效果如下（&lt;a href="http://www.jeffzon.net/Samples/StyledAutoComplete/GoogleSuggest.aspx" target="_blank"&gt;点击这里&lt;/a&gt;查看示例）：&lt;/p&gt;
&lt;p&gt;&lt;img src="http://img.zhaojie.me/blog/StyledAutoComplete/fake_google_suggest.png"&gt; &lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;新的控件将“补全的内容”和“显示的内容”进行了分离。目前的控件将object数组的第一个元素（下标为0）作为“补全的内容”，在显示的时候，我们可以将其忽略。例如，下面的代码将会模拟Windows Live Mail中Email提示的功能（事实上，Live Mail的这部分自动补全是纯客户端功能，我这里模拟的仅仅是“样式”）。&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;head&lt;/span&gt; &lt;span style="color: #ff0000"&gt;id&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"Head1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;title&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;Untitled Page&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;title&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;style&lt;/span&gt; &lt;span style="color: #ff0000"&gt;type&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"text/css"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        .completionListElement 
        {  
            visibility : hidden;
            margin : 0px!important;
            background-color : inherit;
            color : windowtext;
            border : buttonshadow;
            border-width : 1px;
            border-style : solid;
            cursor : pointer;
            overflow : auto;
            text-align : left; 
            list-style-type : none;
            font-family : Verdana;
            font-size: 11px;
            padding : 0;
        }
        .listItem 
        {
            background-color: white;
            padding : 1px;
        }        
        .highlightedListItem
        {
            background-color: #e9f5f7;
            padding : 1px;
        }
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;style&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;head&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;body&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;form&lt;/span&gt; &lt;span style="color: #ff0000"&gt;id&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"form1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ScriptManager&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"ScriptManager1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ScriptManager&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;TextBox&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"myTextBox"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;Width&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"500"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;style&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"height:14px; border: solid 1px gray; font-family:Verdana; font-size: 11px;"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
        
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;jeffz&lt;/span&gt;:&lt;span style="color: #800000"&gt;StyledAutoCompleteExtender&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; 
            &lt;span style="color: #ff0000"&gt;BehaviorID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"AutoCompleteEx"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"autoComplete1"&lt;/span&gt; 
            &lt;span style="color: #ff0000"&gt;TargetControlID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"myTextBox"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;ServicePath&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"AutoComplete.asmx"&lt;/span&gt; 
            &lt;span style="color: #ff0000"&gt;ServiceMethod&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"GetEmailCompletionList"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;MinimumPrefixLength&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"2"&lt;/span&gt; 
            &lt;span style="color: #ff0000"&gt;CompletionInterval&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"1000"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;EnableCaching&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"true"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;CompletionSetCount&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"10"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;CompletionListCssClass&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"completionListElement"&lt;/span&gt; 
            &lt;span style="color: #ff0000"&gt;CompletionListItemCssClass&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"listItem"&lt;/span&gt; 
            &lt;span style="color: #ff0000"&gt;CompletionListHighlightedItemCssClass&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"highlightedListItem"&lt;/span&gt;
            &lt;span style="color: #ff0000"&gt;DelimiterCharacters&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;",;"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
            &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;ItemTemplate&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;{1}&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;ItemTemplate&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;jeffz&lt;/span&gt;:&lt;span style="color: #800000"&gt;StyledAutoCompleteExtender&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;form&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;body&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在这里，ItemTemplate的内容很简单，只是将服务器端返回的每个object数组的第二个元素显示在页面上，而第一个元素则作为补全的文本。它对应的Web Service方法如下所示：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;private&lt;/span&gt; &lt;span style="color: #0000ff"&gt;static&lt;/span&gt; &lt;span style="color: #0000ff"&gt;string&lt;/span&gt;[] emails = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; &lt;span style="color: #0000ff"&gt;string&lt;/span&gt;[]
{
    "&lt;span style="color: #8b0000"&gt;\"jeffrey zhao at yahoo\" &amp;amp;lt;jeffz&amp;#64;yahoo.com&amp;amp;gt;&lt;/span&gt;",
    "&lt;span style="color: #8b0000"&gt;\"jeffrey zhao at yahoo china\" &amp;amp;lt;jeffz&amp;#64;yahoo.com.cn&amp;amp;gt;&lt;/span&gt;",
    "&lt;span style="color: #8b0000"&gt;\"jeffrey zhao at live mail\" &amp;amp;lt;jeffz&amp;#64;live.com&amp;amp;gt;&lt;/span&gt;",
    "&lt;span style="color: #8b0000"&gt;\"jeffrey zhao at gmail\" &amp;amp;lt;jeffz&amp;#64;gmail.com&amp;amp;gt;&lt;/span&gt;"
};


[WebMethod]
&lt;span style="color: #0000ff"&gt;public&lt;/span&gt; IList&amp;lt;&lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[]&amp;gt; GetEmailCompletionList(&lt;span style="color: #0000ff"&gt;string&lt;/span&gt; prefixText, &lt;span style="color: #0000ff"&gt;int&lt;/span&gt; count)
{
    IList&amp;lt;&lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[]&amp;gt; items = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; List&amp;lt;&lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[]&amp;gt;();

    prefixText = prefixText.Trim();
    &lt;span style="color: #0000ff"&gt;foreach&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;string&lt;/span&gt; email &lt;span style="color: #0000ff"&gt;in&lt;/span&gt; emails)
    {
        &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (email.Contains(prefixText))
        {
            &lt;span style="color: #0000ff"&gt;string&lt;/span&gt; encoded = email.Replace(prefixText, "&lt;span style="color: #8b0000"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;" + prefixText + "&lt;span style="color: #8b0000"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;");
            items.Add(&lt;span style="color: #0000ff"&gt;new&lt;/span&gt; &lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[] { HttpUtility.HtmlDecode(email) + "&lt;span style="color: #8b0000"&gt;;&lt;/span&gt;", encoded });
        }
    }
        
    &lt;span style="color: #0000ff"&gt;return&lt;/span&gt; items;
}&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;服务器端传回的每个object数组长度为2，第一个元素用于自动补全，第二个元素用于显示，因此需要将关键字部分进行加粗。使用效果如下（&lt;a href="http://www.jeffzon.net/Samples/StyledAutoComplete/LiveMail.aspx" target="_blank"&gt;点击这里&lt;/a&gt;查看示例）：&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;img src="http://img.zhaojie.me/blog/StyledAutoComplete/fake_live_mail.png"&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;　　另外，正像Live Mail的功能一样，StyleAutoCompleteBehavior也支持输入多个Email——这是新版AutoCompleteBehavior提供的功能，请注意AutoCompleteBehavior的DelimiterCharacters属性，它是一个字符串，字符串中的每个字符即为分隔符。&lt;/p&gt;
&lt;p&gt;目前StyledAutoCompleteBehavior只在FireFox和IE下进行了测试。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jeffzon.net/Samples/StyledAutoComplete/GoogleSuggest.aspx" target="_blank"&gt;点击这里&lt;/a&gt;查看Google Suggest模拟示例&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jeffzon.net/Samples/StyledAutoComplete/LiveMail.aspx" target="_blank"&gt;点击这里&lt;/a&gt;查看Windows Live Mail模拟示例&lt;/p&gt;
&lt;p&gt;&lt;a href="http://files.cnblogs.com/JeffreyZhao/StyleAutoComplete.zip" target="_blank"&gt;点击这里&lt;/a&gt;下载StyleAutoCompleteExtender/Behavior控件以及示例代码。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2007/06/styleautocomplete.html#comments</comments>
      <pubDate>Tue, 12 Jun 2007 10:15:00 GMT</pubDate>
      <lastBuildDate>Tue, 12 Jun 2007 10:15:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>让UpdatePanel支持文件上传（5）：支持页面重定向的HttpModule</title>
      <link>http://blog.zhaojie.me/2007/04/let-updatepanel-support-file-uploading-the-module-that-support-page-redirection.html</link>
      <guid>http://blog.zhaojie.me/2007/04/let-updatepanel-support-file-uploading-the-module-that-support-page-redirection.html</guid>
      <description>&lt;p&gt;我们现在试用一下这个组件。&lt;/p&gt; &lt;p&gt;首先，我们将AjaxUploadHelper控件放置在页面中，紧跟在ScriptManager之后，因为AjaxUploadHelpe需要在第一时间告诉ScriptManager目前正处在一个异步刷新的过程中。&lt;/p&gt; &lt;pre class="code"&gt;&lt;span style="color: black; background-color: #ffff00"&gt;&amp;lt;%&amp;#64; Register Assembly="AjaxFileUploadHelper" Namespace="Jeffz.Web" TagPrefix="jeffz" %&amp;gt;&lt;/span&gt;

//...
&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ScriptManager&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"ScriptManager1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;jeffz&lt;/span&gt;:&lt;span style="color: #800000"&gt;AjaxFileUploadHelper&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"AjaxFileUploadHelper1"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
//...&lt;/pre&gt; 
&lt;p&gt;接着，在页面上添加一个UpdatePanel，并在其中放置一个FileUpload控件，一个按钮以及一个Label。为了更容易地看出异步刷新的效果，我们在页面上添加两个时间：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: black; background-color: #ffff00"&gt;&amp;lt;%&lt;/span&gt;= DateTime.Now &lt;span style="color: black; background-color: #ffff00"&gt;%&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;UpdatePanel&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"UpdatePanel1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;ContentTemplate&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: black; background-color: #ffff00"&gt;&amp;lt;%&lt;/span&gt;= DateTime.Now &lt;span style="color: black; background-color: #ffff00"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;FileUpload&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"FileUpload1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;Button&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"Button1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;Text&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"Upload"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;OnClick&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"Button1_Click"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;Label&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"Label1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;Text&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;""&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;Label&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;ContentTemplate&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;UpdatePanel&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt; 
&lt;p&gt;在Code Behind代码中，我们为Button添加Event handler：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;protected&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; Button1_Click(&lt;span style="color: #0000ff"&gt;object&lt;/span&gt; sender, EventArgs e)
{
    &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;this&lt;/span&gt;.FileUpload1.PostedFile != &lt;span style="color: #0000ff"&gt;null&lt;/span&gt;)
    {
        &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;.Label1.Text = &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;.FileUpload1.PostedFile.ContentLength + "&lt;span style="color: #8b0000"&gt; bytes&lt;/span&gt;";
    }
    &lt;span style="color: #0000ff"&gt;else&lt;/span&gt;
    {
        &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;.Label1.Text = "&lt;span style="color: #8b0000"&gt;&lt;/span&gt;";
    }
}&lt;/pre&gt; 
&lt;p&gt;打开页面，我们可以看到页面中显示了那些控件和两个时间。&lt;/p&gt;
&lt;p&gt;&lt;img alt="1" src="http://img.zhaojie.me/blog/UploadFileInUpdatePanel/1.jpg"&gt;&lt;/p&gt;
&lt;p&gt;选择一个文件并点击Upload按钮，我们可以发现只有UpdatePanel内部的时间被改变了，文件大小也显示在了页面上：&lt;/p&gt;
&lt;p&gt;&lt;img alt="2" src="http://img.zhaojie.me/blog/UploadFileInUpdatePanel/2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;很震撼吧？但是如果我们改变Code Behind中的代码：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;protected&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; Button1_Click(&lt;span style="color: #0000ff"&gt;object&lt;/span&gt; sender, EventArgs e)
{
    &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;.Response.Redirect("&lt;span style="color: #8b0000"&gt;AnotherPage.aspx&lt;/span&gt;", &lt;span style="color: #0000ff"&gt;true&lt;/span&gt;);
}&lt;/pre&gt; 
&lt;p&gt;刷新页面，点击按钮，您就会发现……失败了？为什么？&lt;/p&gt;
&lt;p&gt;原因如下：在一个“普通”的PostBack时，如果我们在执行了Redirect方法，浏览器将会接受到一个Status Code为302的Response，以及一个跳转目标。接着浏览器就会将用户带去指定的目标页面。当XHR发出的请求得到这样一个Response之后，它将会自动重新请求而不会告诉客户端究竟发生了什么。这时，客户端只能获得目标跳转之后的资源，而并非起初请求的资源。&lt;/p&gt;
&lt;p&gt;因此，ASP.NET AJAX提供了一个组件来支持异步PostBack时的跳转。这个组件就是ScriptModule，我们可以在web.config文件中找到它的注册信息。&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;system.web&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #008000"&gt;&amp;lt;!-- other configurations --&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;httpModules&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;add&lt;/span&gt; &lt;span style="color: #ff0000"&gt;name&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"ScriptModule"&lt;/span&gt; 
	    &lt;span style="color: #ff0000"&gt;type&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"System.Web.Handlers.ScriptModule, System.Web.Extensions, ..."&lt;/span&gt;&lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;httpModules&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #008000"&gt;&amp;lt;!-- other configurations --&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;system.web&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #008000"&gt;&amp;lt;!-- for IIS 7 --&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;system.webServer&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #008000"&gt;&amp;lt;!-- other configurations --&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;modules&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;add&lt;/span&gt; &lt;span style="color: #ff0000"&gt;name&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"ScriptModule"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;preCondition&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"integratedMode"&lt;/span&gt; 
	    &lt;span style="color: #ff0000"&gt;type&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"System.Web.Handlers.ScriptModule, System.Web.Extensions, ..."&lt;/span&gt;&lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;modules&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #008000"&gt;&amp;lt;!-- other configurations --&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;system.webServer&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt; 
&lt;p&gt;下面的代码片断就是它解决这个问题的实现：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;class&lt;/span&gt; ScriptModule : IHttpModule
{
    &lt;span style="color: #0000ff"&gt;protected&lt;/span&gt; &lt;span style="color: #0000ff"&gt;virtual&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; Init(HttpApplication context)
    {
        context.PreSendRequestHeaders += &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; EventHandler(PreSendRequestHeadersHandler);
        &lt;span style="color: #008000"&gt;// ...&lt;/span&gt;
    }

    &lt;span style="color: #0000ff"&gt;private&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; PreSendRequestHeadersHandler(&lt;span style="color: #0000ff"&gt;object&lt;/span&gt; sender, EventArgs args)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpResponse response = application.Response;

        &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (response.StatusCode == 302)
        {
            &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (PageRequestManager.IsAsyncPostBackRequest(application.Request.Headers))
            {   
                &lt;span style="color: #0000ff"&gt;string&lt;/span&gt; redirectLocation = response.RedirectLocation;

                List&amp;lt;HttpCookie&amp;gt; cookies = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; List&amp;lt;HttpCookie&amp;gt;(response.Cookies.Count);
                &lt;span style="color: #0000ff"&gt;for&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;int&lt;/span&gt; i = 0; i &amp;lt; response.Cookies.Count; i++) {
                    cookies.Add(response.Cookies[i]);
                }

                response.ClearContent();
                response.ClearHeaders();

                &lt;span style="color: #0000ff"&gt;for&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;int&lt;/span&gt; i = 0; i &amp;lt; cookies.Count; i++)
                {
                    response.AppendCookie(cookies[i]);
                }

                response.Cache.SetCacheability(HttpCacheability.NoCache);
                response.ContentType = "&lt;span style="color: #8b0000"&gt;text/plain&lt;/span&gt;";
                &lt;font color="#ff0000"&gt;PageRequestManager.EncodeString(response.Output, "pageRedirect&lt;font color="#8b0000"&gt;&lt;/font&gt;", 
                    String.Empty, redirectLocation);&lt;/font&gt;
            }
            &lt;span style="color: #0000ff"&gt;else&lt;/span&gt; &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; &lt;span style="color: #008000"&gt;//...&lt;/span&gt;
        }
    }
}&lt;/pre&gt; 
&lt;p&gt;我们响应了PreSendRequestHeaders事件，它将会在服务器端发送Header信息之前被触发。此时，如果Status Code为302（表示Response将要使客户端跳转到另一个页面去），则会清除所有即将发送的内容，并重新指定传输的信息。在这里最重要的修改就是Response Body的内容。因为客户端将要解析收到的字符串，因此我们必须发送格式为“length|type|id|content”。请注意上方红色的代码，它将会发送一段格式合法的字符串，例如“16|pageRedirect||/AnotherPage.aspx|”。&lt;/p&gt;
&lt;p&gt;在客户端，我们可以找到下面的实现，它的作用是在收到页面重定向的信息之后跳转页面。请注意下方红色的代码：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;function&lt;/span&gt; Sys$WebForms$PageRequestManager$_onFormSubmitCompleted(sender, eventArgs)
{
    &lt;span style="color: #008000"&gt;// ...&lt;/span&gt;

    &lt;span style="color: #0000ff"&gt;for&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;var&lt;/span&gt; i = 0; i &amp;lt; delta.&lt;span style="color: #0000ff"&gt;length&lt;/span&gt;; i++) {
        &lt;span style="color: #0000ff"&gt;var&lt;/span&gt; deltaNode = delta[i];
        &lt;span style="color: #0000ff"&gt;switch&lt;/span&gt; (deltaNode.type) {
            &lt;span style="color: #0000ff"&gt;case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;updatePanel&lt;/span&gt;":
                &lt;span style="color: #0000ff"&gt;Array&lt;/span&gt;.add(updatePanelNodes, deltaNode);
                &lt;span style="color: #0000ff"&gt;break&lt;/span&gt;;

	    &lt;span style="color: #008000"&gt;// ...&lt;/span&gt;

            &lt;font color="#ff0000"&gt;case "pageRedirect":
                window.location.href = deltaNode.content;
                return;&lt;/font&gt;
            &lt;span style="color: #008000"&gt;//...&lt;/span&gt;
        }
    }

    &lt;span style="color: #008000"&gt;// ...&lt;/span&gt;
}&lt;/pre&gt; 
&lt;p&gt;明白了这点之后，我们也就能够轻松地编写一个这样的模块了：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;class&lt;/span&gt; AjaxFileUploadModule : IHttpModule
{
    &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; Init(HttpApplication context)
    {
        context.PreSendRequestHeaders += &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; EventHandler(PreSendRequestHeadersHandler);
    }

    &lt;span style="color: #0000ff"&gt;private&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; PreSendRequestHeadersHandler(&lt;span style="color: #0000ff"&gt;object&lt;/span&gt; sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpResponse response = application.Response;

        &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (response.StatusCode == 302 &amp;amp;&amp;amp; 
            &lt;font color="#ff0000"&gt;AjaxFileUploadUtility.IsInIFrameAsyncPostBack(application.Request.Params)&lt;/font&gt;)
        {
            &lt;span style="color: #0000ff"&gt;string&lt;/span&gt; redirectLocation = response.RedirectLocation;
            List&amp;lt;HttpCookie&amp;gt; cookies = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; List&amp;lt;HttpCookie&amp;gt;(response.Cookies.Count);

            &lt;span style="color: #0000ff"&gt;for&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;int&lt;/span&gt; i = 0; i &amp;lt; response.Cookies.Count; i++)
            {
                cookies.Add(response.Cookies[i]);
            }

            response.ClearContent();
            response.ClearHeaders();

            &lt;span style="color: #0000ff"&gt;for&lt;/span&gt; (&lt;span style="color: #0000ff"&gt;int&lt;/span&gt; i = 0; i &amp;lt; cookies.Count; i++)
            {
                response.AppendCookie(cookies[i]);
            }

            response.Cache.SetCacheability(HttpCacheability.NoCache);
            response.ContentType = "&lt;span style="color: #8b0000"&gt;text/plain&lt;/span&gt;";

            &lt;font color="#ff0000"&gt;AjaxFileUploadUtility.WriteScriptBlock(response, true);
            
            StringBuilder sb = new StringBuilder();
            TextWriter writer = new StringWriter(sb);
            AjaxFileUploadUtility.EncodeString(writer, "pageRedirect", 
                String.Empty, redirectLocation);
            response.Write(sb.Replace("*/", "*//*").ToString());

            AjaxFileUploadUtility.WriteScriptBlock(response, false);&lt;/font&gt;

            response.End();
        }
    }

    &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; Dispose() {}
}&lt;/pre&gt; 
&lt;p&gt;上方红色的代码为我们的Module与ASP.NET AJAX中的ScriptModule之间唯一的区别。我们在web.config文件中注册了AjaxFileUploadModule之后，我们在服务器端调用Redirect方法之后，在客户端就能进行跳转了。此时客户端接收到的文本如下：&lt;/p&gt;
&lt;pre class="code"&gt;&amp;lt;script type='text/javascript' language='javascript'&amp;gt;window.__f__=function(&lt;font color="#0000ff"&gt;&lt;/font&gt;)
    {&lt;font color="#ff0000"&gt;/*16|pageRedirect||/AnotherPage.aspx|*/&lt;/font&gt;}&amp;lt;/script&amp;gt;&lt;/pre&gt; 
&lt;p&gt;现在，我们终于完成了所有的组件。您也不妨将其下载之后尝试一下吧，如果遇到了问题，请及时&lt;a href="mailto:jeffz&amp;#64;live.com"&gt;和我联系&lt;/a&gt;。:)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://files.cnblogs.com/JeffreyZhao/AjaxFileUploadHelper.zip"&gt;点击这里&lt;/a&gt;下载整个项目&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jeffzon.net/Blog/post/Let-UpdatePanel-support-file-uploading-5-The-Module-that-Support-Page-Redirection.aspx"&gt;English Version&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2007/04/let-updatepanel-support-file-uploading-the-module-that-support-page-redirection.html#comments</comments>
      <pubDate>Thu, 12 Apr 2007 03:51:00 GMT</pubDate>
      <lastBuildDate>Thu, 12 Apr 2007 03:51:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>让UpdatePanel支持文件上传（4）：数据传输与解析机制</title>
      <link>http://blog.zhaojie.me/2007/04/let-updatepanel-support-file-uploading-the-mechanism-of-sending-and-parsing-data.html</link>
      <guid>http://blog.zhaojie.me/2007/04/let-updatepanel-support-file-uploading-the-mechanism-of-sending-and-parsing-data.html</guid>
      <description>&lt;p&gt;现在就要开始整个项目中最有技巧的部分了。如果我们的组件需要在多种浏览器中正常的运行，我们必须好好考虑一下发送和解析数据的方式。如果我们把这部分的机制完全交给ASP.NET AJAX原有的行为来执行，则会遇到问题。下面的代码片断就是IE 7和FireFox在收到服务器端的数据之后，iframe中的DOM结构：&lt;/p&gt; &lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;html&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;head&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;head&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;body&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;pre&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;33|updatePanel|ctl00_Main_UpdatePanel1|...&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;pre&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;body&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;html&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt; 
&lt;p&gt;很显然，这段代码的意图是为了在页面中直接显示服务器端发送过来的数据。在这种情况下，我们就可以通过“&amp;lt;pre /&amp;gt;”元素的innertText属性（IE 7）或者textContent属性（FireFox）来直接获得这段文字。不幸的是，IE6的行为非常奇怪，与前两者可谓大相径庭。IE 6会把这段文字按照XML来解析，接着很自然的显示出错误信息，告诉我们这段文本不是一个有效的XML文档。这非常不合理，因为Response的“Content-Type”是“text/plain”而不是“text/xml”。这是我们要兼容多个浏览器时最头疼的情况。&lt;/p&gt;
&lt;p&gt;还记得我们在向客户段输出真实的数据前后都调用了WriteScriptBlock方法吗？下面就是这个方法的实现：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;internal&lt;/span&gt; &lt;span style="color: #0000ff"&gt;static&lt;/span&gt; &lt;span style="color: #0000ff"&gt;class&lt;/span&gt; AjaxFileUploadUtility
{
    &lt;span style="color: #0000ff"&gt;internal&lt;/span&gt; &lt;span style="color: #0000ff"&gt;static&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; WriteScriptBlock(HttpResponse response, &lt;span style="color: #0000ff"&gt;bool&lt;/span&gt; begin)
    {
        &lt;span style="color: #0000ff"&gt;string&lt;/span&gt; scriptBegin = 
            "&lt;span style="color: #8b0000"&gt;&amp;lt;script type='text/javascript' language='javascript'&amp;gt;window.__f__=function(){/*&lt;/span&gt;";
        &lt;span style="color: #0000ff"&gt;string&lt;/span&gt; scriptEnd = "&lt;span style="color: #8b0000"&gt;*/}&amp;lt;/script&amp;gt;&lt;/span&gt;";

        response.Write(begin ? scriptBegin : scriptEnd);
    }
}&lt;/pre&gt; 
&lt;p&gt;IE 6和IE 7会将使用&amp;lt;script /&amp;gt;来包含的文本作为一段脚本代码来处理。我们这里在真实的数据两边加上了脚本定义的内容，使它成为了客户端iframe中“__f__”方法的一段注释，因此我们可以通过调用这个方法的toString函数来获得这个方法的文本内容。请注意在RenderPageCallback方法中，我们把文本进行了编码，将“*/”替换为“*//*”，然后再将其发送到客户端，这么做的目的是使这段文本能够成为合法的JavaScirpt代码。&lt;/p&gt;
&lt;pre class="code"&gt;StringBuilder sb = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; StringBuilder();
HtmlTextWriter innerWriter = &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; HtmlTextWriter(&lt;span style="color: #0000ff"&gt;new&lt;/span&gt; StringWriter(sb));
renderPageCallbackMethodInfo.Invoke(
    &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;.PageRequestManager, 
    &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; &lt;span style="color: #0000ff"&gt;object&lt;/span&gt;[] { innerWriter, pageControl });

writer.Write(sb.Replace("&lt;span style="color: #8b0000"&gt;*/&lt;/span&gt;", "&lt;span style="color: #8b0000"&gt;*//*&lt;/span&gt;").ToString());&lt;/pre&gt; 
&lt;p&gt;等一下，我们在这里把异步刷新运行正常时输出的文本进行了编码，但是我们在异常情况下的输出并没有这么做，不是吗？没错。因为在异常状况下，错误信息会通过Response的Write方法直接输出（请看PageRequestManager类的OnPageError方法），因此我们无法向前面的代码那样获得它输出的结果。我们现在只能希望错误信息中不要出现“*/”这样的字符串吧（当然，我们可以使用反射机制来重写整个逻辑，但是这样做实在比较复杂）。&lt;/p&gt;
&lt;p&gt;下面，我们就要在客户端的_iframeLoadComplete方法中重新获取这段文本了：&lt;/p&gt;
&lt;pre class="code"&gt;_iframeLoadComplete : &lt;span style="color: #0000ff"&gt;function&lt;/span&gt;()
{
    &lt;span style="color: #008000"&gt;//...&lt;/span&gt;

    &lt;span style="color: #0000ff"&gt;try&lt;/span&gt;
    {    
        &lt;span style="color: #0000ff"&gt;var&lt;/span&gt; f = iframe.contentWindow.__f__;
        &lt;span style="color: #0000ff"&gt;var&lt;/span&gt; responseData = f ? &lt;font color="#ff0000"&gt;this._parseScriptText(f.toString())&lt;/font&gt; : 
            &lt;font color="#ff0000"&gt;this._parsePreNode(iframe.contentWindow.document.body.firstChild)&lt;/font&gt;;
            
        &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (responseData.indexOf("&lt;span style="color: #8b0000"&gt;\r\n&lt;/span&gt;") &amp;lt; 0 &amp;amp;&amp;amp; responseData.indexOf("&lt;span style="color: #8b0000"&gt;\n&lt;/span&gt;") &amp;gt; 0)
        {
            responseData = responseData.replace(/\n/g, "&lt;span style="color: #8b0000"&gt;\r\n&lt;/span&gt;");
        }
            
        &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;._responseData = responseData;
        &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;._statusCode = 200;
        &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;._responseAvailable = &lt;span style="color: #0000ff"&gt;true&lt;/span&gt;;
    }
    &lt;span style="color: #0000ff"&gt;catch&lt;/span&gt; (e)
    {
        &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;._statusCode = 500;
        &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;._responseAvailable = &lt;span style="color: #0000ff"&gt;false&lt;/span&gt;;
    }
    
    &lt;span style="color: #008000"&gt;// ...&lt;/span&gt;
},

_parseScriptText : &lt;span style="color: #0000ff"&gt;function&lt;/span&gt;(scriptText)
{
    &lt;span style="color: #0000ff"&gt;var&lt;/span&gt; indexBegin = scriptText.indexOf("&lt;span style="color: #8b0000"&gt;/*&lt;/span&gt;") + 2;
    &lt;span style="color: #0000ff"&gt;var&lt;/span&gt; indexEnd = scriptText.lastIndexOf("&lt;span style="color: #8b0000"&gt;*/&lt;/span&gt;");
    &lt;span style="color: #0000ff"&gt;var&lt;/span&gt; encodedText = scriptText.substring(indexBegin, indexEnd);
    &lt;span style="color: #0000ff"&gt;return&lt;/span&gt; encodedText.replace(/\*\/\/\*/g, "&lt;span style="color: #8b0000"&gt;*/&lt;/span&gt;");
},&lt;/pre&gt; 
&lt;p&gt;我们在这里将判断iframe的window对象中是否存在“__f__”方法，而不是直接判断浏览器的类型来决定下面要做的事情，因为这样可以带来更多的浏览器兼容性。&lt;/p&gt;
&lt;p&gt;FireFox的行为则完全不是这样的，它依旧使用我们一开始提到的那种DOM结构，把从服务器端得到的文本显示在iframe中，这种做法比较合理，因为Response的Content-Type为“text-plain”。因此，我们会使用另一种方法来得到这段文本：&lt;/p&gt;
&lt;pre class="code"&gt;_parsePreNode : &lt;span style="color: #0000ff"&gt;function&lt;/span&gt;(preNode)
{
    &lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (preNode.tagName.toUpperCase() !== "&lt;span style="color: #8b0000"&gt;PRE&lt;/span&gt;") &lt;span style="color: #0000ff"&gt;throw&lt;/span&gt; &lt;span style="color: #0000ff"&gt;new&lt;/span&gt; Error();
    &lt;span style="color: #0000ff"&gt;return&lt;/span&gt; &lt;span style="color: #0000ff"&gt;this&lt;/span&gt;._parseScriptText(preNode.textContent || preNode.innerText);
},&lt;/pre&gt;
&lt;p&gt;请注意，“_iframeLoadComplete”方法中还有几行非常重要的代码：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;if&lt;/span&gt; (responseData.indexOf("&lt;span style="color: #8b0000"&gt;\r\n&lt;/span&gt;") &amp;lt; 0 &amp;amp;&amp;amp; responseData.indexOf("&lt;span style="color: #8b0000"&gt;\n&lt;/span&gt;") &amp;gt; 0)
{
    responseData = responseData.replace(/\n/g, "&lt;span style="color: #8b0000"&gt;\r\n&lt;/span&gt;");
}&lt;/pre&gt;
&lt;p&gt;由于从服务器端得到的脚本将会被分割为多个部分，每个部分的格式为“length|type|id|content”，因此字符串的长度是在解析文本时非常重要的属性。因此，我们将会把所有的“\r”替换成“\r\n”，以此保持内容和长度的一致，否则解析过程将会失败。而且事实上，这样的替换只会出现在FireFox中。（未完待续）&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://files.cnblogs.com/JeffreyZhao/AjaxFileUploadHelper.zip"&gt;点击这里&lt;/a&gt;下载整个项目&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jeffzon.net/Blog/post/Let-UpdatePanel-support-file-uploading-4-The-Mechanism-of-Sending-and-Parsing-Data.aspx"&gt;English Version&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2007/04/let-updatepanel-support-file-uploading-the-mechanism-of-sending-and-parsing-data.html#comments</comments>
      <pubDate>Wed, 11 Apr 2007 07:02:00 GMT</pubDate>
      <lastBuildDate>Wed, 11 Apr 2007 07:02:00 GMT</lastBuildDate>
    </item>
  </channel>
</rss>
