大约在很早很早的时候,我们就可以使用MvcContrib为我们的ASP.NET MVC程序引入NVelocity模板引擎的支持了。但是从严格意义上来说,这个支持仅仅是有限支持,因为大部分原本可以在Monorail里面使用的功 能都使用不了了,不仅如此,整个程序还有一大堆bug。总的来说,这个程序集堪称“超级杀脑细胞王3000”,让你用得绝望。百般无奈之下,只好自己修改 这个程序集,那么这篇文章就权当一个opening吧。
如果我们在Preview 5版本的MVC中使用诸如$html.renderaction或者$html.renderpartial这种返回值是void的方法时,我们会发现根 本没有效果。不过还好,这个bug已经在beta 1版本中解决了,但是…但是解决的方法并不怎么优雅…
先看看Preview 5的实现代码
这 里传进Render方法的writer实际上就是一个httpwriter,重点在处理layout那一段,如果有layout的话就把子 template,也就是代码中的_viewTemplate,解析之后放到内存流中间去缓存,然后把处理的结果赋给childContent变量,再让 父template解析一次,这样就完成了对整个页面的解析。但是如果使用这种方法的话,helper中所有返回值为void的渲染方法都会失效。因为在 Helper中所有返回值为void的方法都是调用HttpWriter把结果直接写进response.OutputStream中的。如果向上面那样 自己用一个StringWriter来缓存输出流的话,相当于所有返回值为void的helper方法都被剥离出了NVelocity的解析树(这种情况 下有2个流,一个是response.OutputStream,一个是StringWriter的Stream),结果当然会不正确啦。
所以在Beta 1的MvcContrib中,代码被改成了下面这样
这 里不再使用StringWriter去缓存流,而是直接使用writer来写入response.OutputStream中,由于在 context.Put中送入的是子template的名字(Preview 5是解析后的子template),所以我们在layout.vm中不能再像以前一样直接用$childContent来占位,而是要 用#parse($childContent),虽然改变了长期以来的使用习惯,但是好歹还是能够正常使用了,直到……
昨 天XX强烈要求我把#capturefor给弄出来(这个方法是monorail里面的,在ASP.NET MVC里面用不了),于是我把Monorail的源代码大致看了一下,发现其实#capturefor是monorail自定义的一条 directive(指令),也就是说NVelocity引擎是可以支持自定义Directive的。
首先定义一个继承自Directive的类
然后定义一个继承自DirectiveManager的类
并在RegisterCustomDirectives方法中注册,最后在NVelocityViewFactory中注册这个自定义的DirectiveManager类
运 行一下看看?囧…竟然没有将变量解析出来,不过至少在vm文件中使用#capturefor关键字不会报错了,也就是我们的Directive还是起作用 了。经过Debug发现问题其实还是出在最后的渲染步骤上。在template.merge的时候,NVelocity引擎会按照文档的顺序来解析模板并 将其输出到response.OutputStream中,而capturefor的参数一般是在父template中定义的,也就是说如果按照顺序解析 的话是绝对解析不出来的。但是如果按照Preview 5中那样先解析子template又会有另外的bug.
不过好在我们仍有办法解决这个问题。首先建立一个response filter,让它去截获response的输出流,不让这个流输出到客户端,而是存到我们自定义的一个变量中。然后,嘿嘿…看图,应该很明了了
还 是用HttpWriter来写结果,这样可以保证顺序问题和Void方法问题,但是并不把这个结果输出到客户端,而是放到一个字符变量中,然后我们再调用 父template的merge方法来完成解析。这样做不但解决了自定义directive的问题,而且我们现在也可以像以前那样在layout中使用 回$childContent方法而不是#parse($childContent)了。一举两得!