Hello World
Spiga

不妨来做个尝试:UpdatePanel for ASP.NET MVC

2008-04-27 23:59 by 老赵, 30146 visits

先来发一通牢骚。

其实这是一篇迟发布近2个月的文章。事实上在ASP.NET MVC Preview 2发布之前我就已经将这篇文章的所有内容准备完毕了。当时想,就等Preview 2发布吧,而真一旦Preview 2发布之后却又懒得进行移植——移植了之后却又懒得写文章。这一拖就是近2个月,毫无长进。可能工作等其他事情的确多了些,但是扪心自问,也并没有忙到不可开交。时间往往都是在点点滴滴间浪费的。唉,可能是自视太高,越来越不愿意写一些普普通通的介绍性文章,导致可写的东西大大减少。不过话说回来,其实打算写的,甚至多次说过要写得东西也并不少,为什么就就是没有动笔呢?其实还是一个“懒”字——当年的勤奋劲儿到哪里去了呢?

言归正传。先解释一下标题,什么是“UpdatePanel for ASP.NET MVC”呢?ASP.NET AJAX中的UpdatePanel相信大家都有所了解。可惜的是,ASP.NET MVC框架的诞生“毁灭”了大量基于PostBack的控件,首当其冲地可能就是UpdatePanel了。如果没有PostBack,UpdatePanel就失去了全部作用,甚至不如一些绑定控件,至少它们还能够用于展示。为UpdatePanel长吁短叹之后,我们不禁又开始怀念UpdatePanel的优势:“透明”。在UpdatePanel的帮助下,实现AJAX操作对于开发人员几乎完全透明。我们要做的仅仅是将需要AJAX更新的内容用UpdatePanel包装起来,一切都是那么优雅。

我们能否在ASP.NET MVC中拯救UpdatePanel呢?也许是可以的吧,但这更像是一个“不可能完成的任务”。我不是传说中的阿汤哥,因此重新为ASP.NET MVC量身定制一个AJAX解决方案似乎更为可行。虽然我们不会苛求一个新生事物从诞生开始就趋向完美,但即使只是一个原型,它也必须严格遵守的一些原则:

  • 不得破坏MVC中的协议(协作,职责等等)
  • 对开发人员尽可能地透明

Nikhil Kothari曾经提出了他在ASP.NET MVC框架下的AJAX解决方案。如果您还不了解他的做法,那么我先在这里进行一点概括。Nikhil扩展了Controller使之支持一种Ajax操作,于是我们在代码中就可以写如下代码:

public class TaskListController : AjaxController {
    ...
    public void CompleteTask(int taskID) {
        if (String.IsNullOrEmpty(Request.Form["deleteTask"]) == false) {
            InvokeAction("DeleteTask");
            return;
        }
 
        Task task = _taskDB.GetTask(taskID);
        if (task != null) {
            _taskDB.CompleteTask(task);
        }
 
        if (IsAjaxRequest) {
            if (task != null) {
                RenderPartial("TaskView", task);
            }
        }
        else {
            RedirectToAction("List");
        }
    }
    ...
}

与AjaxController类似,Nikhil也为ViewPage和ViewControl提供了一些扩展方法,因此目前在View(List.aspx)中我们就能看到如下的代码:

<div id="taskList">
    <% foreach (Task task in Tasks) { %>
        <div>
          <% this.RenderPartial("TaskView", task); %>
        </div>
    <% } %>
</div>

在View和Controller中都存在对于RenderPartiel方法的调用,它们的作用就是向客户端输出一个“Partial Template”生成的HTML代码。而在ASP.NET MVC的默认配置中,Partial Template即为User Control。而在TaskView这个Partial Template中可以看到一些辅助方法:

<div id="taskItem<%= Task.ID %>" class="taskPanel">
<% Ajax.Initialize(); %>
<% this.RenderBeginAjaxForm(
      Url.Action("CompleteTask"),
      new {
          Update = "taskItem" + Task.ID,
          UpdateType = "replace",
          Completed = "endUpdateTask"}); %>

    <input type="hidden" name="taskID" value="<%= Task.ID %>" />
    <input type="submit" class="completeButton" name="completeTask" value="Done!" />
    <input type="submit" class="deleteButton" name="deleteTask" value="Delete" />
    <span><%= Html.Encode(Task.Name) %></span>
 
<% this.RenderEndForm(); %>
<% Ajax.RenderScripts(); %>
</div>

这些辅助方法的作用是生成一些触发AJAX更新的标签及脚本,当用户点击RenderBeginAjaxForm与RenderEndForm方法生成的tag之间的提交按钮时,网页将会向服务器端发出一个AJAX请求,而服务器端的Action并最终会通过RenderPartial方法输出一个Partial Template生成的HTML。服务器端最终输出的HTML将会被替换或添加到页面的某个元素内。这就形成了一个AJAX效果。这个解决方案从某些方面看上去很酷,尤其是生成的代码可以添加到某个元素中,而不单单是如同UpdatePanel的替换,例如Nikhil在他的例子中就使用了这个特性实现了一个添加功能。不过如果使用之前提出的原则来衡量的话,似乎这个解决方案并不十分理想。

原因很简单,因为不够透明。

也有评论认为,Controller中的逻辑不该根据一个请求AJAX与否而进行不同处理(Nikhil的解决方案使用RenderPartial来替代RenderView为AJAX操作进行输出),因此这个解决方案破坏了MVC的职责。我不这么认为,但是我希望能做到这一点,因为做到这一点即意味着绝对的透明。绝对透明则意味着Controller将一个应用程序是否AJAX的决定权完全交给了客户端,这点非常理想,因为AJAX完全是一个表现层的概念。ASP.NET AJAX中的UpdatePanel在这方面的表现可圈可点(虽然还远不够完美),因此我最后决定也为ASP.NET MVC开发一款类似UpdatePanel的组件。值得庆幸的是,ASP.NET MVC默认使用WebForm页面作为视图模板,在这个强大的模型之下,构建出这样一个AJAX解决方案(的原形)似乎并不十分困难。

我将这个控件命名为MvcAjaxPanel。MvcAjaxPanel与UpdatePanel最大的区别在于后者接收的是PostBack,而前者接收的只是普通的HTTP请求。Post“Back”意味着Post过后回到了原来的Page,而ASP.NET MVC的请求往往会被引导至不同的页面。因此如何跨页面进行内容更新是MvcAjaxPanel首要解决的问题。最终我选择了为每个MvcAjaxPanel指定一个UpdateAreaID的做法。

<mvc:MvcAjaxPanel runat="server" ID="mvcAjaxPanel" UpdateAreaID="Header">
    ...
</mvc:MvcAjaxPanel>

当页面向服务器端发出一个AJAX请求时将会附带页面中的UpdateAreaID信息,而服务器端的Action并不会意识到这一点,因此依旧按照寻常逻辑指定一个视图模版并输出HTML。不过,如果视图模板中的MvcAjaxPanel发现这个请求实际上是一个符合约定的AJAX请求(请注意,只有View组件意识到这是个请求的性质),则会使用新的方法来替换标准的输出。这时候模板就会根据客户端传递过来的UpdateAreaID,寻找页面上具有同样属性值的MvcAjaxPanel,有选择性地输出内容。在客户端就会有对应的JavaScript代码接收服务器端的数据,并且更新页面中的相应区域。

很明显,MvcAjaxPanel的工作原理与UpdatePanel有颇多相似之处,也做到了一定程度上的透明。而且与Nikhil的解决方案相比,一个非常重要的优势就是可以一次更新页面中的多个区域——其实这也就是UpdatePanel的特性之一。而且这种对Controller透明的做法又有一个天然的特点,那就是能够轻松地在不支持AJAX的浏览器中使用传统的方式切换页面。

服务器端的实现原理并不复杂,不过作为解决方案的另一个关键部分,如何在客户端触发一个AJAX提交也是一个值得思考的话题。UpdatePanel的方式可谓“全自动”:页面加载时将会把服务器端的Trigger信息输出至客户端,然后在客户端截获form的提交事件,并通过UniqueID或DOM结构等方式来判断这次提交是否该转化为AJAX方式。不过在一个ASP.NET MVC页面中几乎不会出现产生PostBack的元素,相反会有大量的普通链接,它们才是AJAX更新的主要截获目标。

为此我提供了一些JavaScript代码,截获一个链接原本的目标地址并将其转化为一个AJAX请求。我在这里通过示例中的代码来展示这种使用方式(这个示例源于Brad Abrams提供的ASP.NET MVC示例,不过我舍弃了Northwind数据库与Entity Framework,取而代之的是XML数据以及自定义的简单Model。此外,我也将其移植到ASP.NET MVC框架的0416 Build中):

<%  foreach (var category in this.ProductCategories)
    { %>
        <li>
            <%= Html.ActionLink<ProductsController>(
                    c => c.List(category, 1),
                    category,
                    new { onclick = "mvcAjax.get(this, event)" })%>
        </li><%
    } %>

这段代码来自分类列表页。与AJAX改进之前的代码相比,唯一的区别就是额外指定了元素的onclick事件(加粗部分)。在onclick事件执行中,这个链接默认的跳转行为将被取消,取而代之的是一个AJAX请求,请求的目标便是ProductsController中名为List的Action。

我们可以使用上面的方式应对普通链接,那么又该如何将一个客户端from的提交行为也变成AJAX操作呢?以下依旧是示例中的代码:

<form method="post"
    action="<%= Url.Action("Update", new { id = this.Product.ProductID }) %>"
    onsubmit="mvcAjax.submit(this, event);">
 
    <table>
        <tr>
            <td>Name:</td>
            <td><%= Html.TextBox("Name", this.Product.Name) %></td>
        </tr>
        ...
    </table>
 
    <input type="submit" value="Save" />
</form>

在截获了form的submit事件之后,客户端将会收集该form中的所有input、select等值,组成一个请求的body,并且以HTTP POST的方式发出一个AJAX请求。余下的事情和之前就没有什么区别了。

与UpdatePanel相比,MvcAjaxPanel的客户端截获方式可谓“纯手工”,但是我并不认为这会造成什么问题。ASP.NET MVC强调的就是职责分离,而这种分离并不仅仅体现在代码上,也体现在开发人员的职责上。在开发ASP.NET MVC应用程序时,负责View的是前端开发工程师,对他们来说JavaScript与AJAX可谓是再熟悉不过的技术。在合时的地方手动编写一些JavaScript调用反而会让他们得到无比的自由性。例如在之前的代码示例中,调用mvcAjax.get或mvcAjax.submit方法时完全可以在前后自由地加入额外操作或者条件判断。这就不会像使用UpdatePanel时,如果需要使用JavaScript提交一个AJAX更新,还需要借助不登大雅之堂的trick

也正因为如此,Nikhil提出的解决方案非常不错,它能够和前台开发人员的自定义逻辑进行灵活地结合。此外,通过阅读ASP.NET MVC框架0416 Build的代码,我发现在新版本的ASP.NET MVC中似乎将会内置这种AJAX解决方案了——不过这也的确符合微软的一贯做法,不是吗?:)

这个AJAX解决方案原型的使用方式和工作原理已经描述完了,如果您对其具体实现感兴趣,或者想亲自尝试一下,可以下载文章末尾的附件。附件中的解决方案包含三个项目,MvcAjax为提供MvcAjaxPanel的项目,而MvcWebApp是一个普通的ASP.NET MVC示例程序,而MvcAjaxWebApp自然就是添加AJAX效果之后的结果了。在示例中,我还在Master Page中定义的菜单(即页面左侧的菜单)里显示了一块当前时间,这是为了体现MvcAjaxPanel的“一次提交,多处更新”的特点。

不过需要强调的是,这仅仅是个原型。或者说这只是一种实现上尝试,在很多细节方面并没有作太多追求。如果要成为一个完善的AJAX解决方案,还需要作大量的改进。例如:

  • 提供一些客户端的hook供前台开发人员使用(如提交前、接受后、或者处理一个提交还没有返回,客户端就发起另一个请求的情况等等)。
  • 更强大的功能,更好的开发体验(如客户端触发机制)
  • 异常处理
  • 支持脚本
  • 支持跳转(Redirection)
  • ...

此外,作为面向ASP.NET MVC特有的AJAX解决方案,也有一些额外的问题需要考虑。最典型的问题之一就是在使用ASP.NET MVC时很少使用模板控件,而更多的使用页面中的循环,那么如何让MvcAjaxPanel在循环内容生效?我也产生过一些想法,但是如果要真正确定下来最终的实现方式,很多东西还需要进一步思考。如果您对于这个AJAX解决方案有什么建议或其他任何想法,也请尽快告诉我。

最后再说一件有趣的事情:在我实现了这个原型之后的某一天,忽然意识到这个控件似乎不光可以为ASP.NET MVC使用,也能够用于普通的WebForms应用程序。这真是一个令人意外的发现。

 

附件:源代码及使用示例

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

88 条回复

  1. marcll JJ
    *.*.*.*
    链接

    marcll JJ 2008-04-26 01:55:00

    受教了.

  2. 任力
    *.*.*.*
    链接

    任力 2008-04-26 02:24:00

    老赵,注意身体哦~~

  3. 笨→鸟(Bird)
    *.*.*.*
    链接

    笨→鸟(Bird) 2008-04-26 02:44:00

    老赵。为把身体保养好,零点以前睡觉觉。

  4. 老赵
    admin
    链接

    老赵 2008-04-26 03:18:00

    @笨→鸟(Bird)
    0点睡觉太奢侈,3点,3点,呵呵。

  5. TT.Net
    *.*.*.*
    链接

    TT.Net 2008-04-26 04:26:00

    学习中~~感谢老赵

  6. TT.Net
    *.*.*.*
    链接

    TT.Net 2008-04-26 04:45:00

    很完美,最好能加上游览器的history,那就更完美了

  7. TT.Net
    *.*.*.*
    链接

    TT.Net 2008-04-26 05:01:00

    相对于Nikhil的MVC ajax解决方案。我更喜欢老赵的,开发人员不太需要去定义那些AJAX的controller,只要在view上用上updatepanel,就像webform中一样,如果和老赵说的那样微软要把 Nikhil的方法集成到下一版本MVC中,感觉没有老赵这个方案更适合MVC的推广应用~

  8. LanYo
    *.*.*.*
    链接

    LanYo 2008-04-26 08:04:00

    3点??强人就是强人!

  9. wuhang
    *.*.*.*
    链接

    wuhang 2008-04-26 09:20:00

    支持,最近好像要出MVC的Webcast了?

  10. 秸杆-老赵粉丝的昵称
    *.*.*.*
    链接

    秸杆-老赵粉丝的昵称 2008-04-26 09:26:00

    好,学习了!老赵的东西肯定有巨大价值,先收着慢慢品。

    其实,俺一直没用过UpdatePanel,也不知ASP.NET MVC是啥,自己汗一个

  11. 黑色幽默[未注册用户]
    *.*.*.*
    链接

    黑色幽默[未注册用户] 2008-04-26 09:31:00

    有时间就用用看!

  12. Indigo Dai
    *.*.*.*
    链接

    Indigo Dai 2008-04-26 10:14:00

    是不是Nikhil提到的位于ASP.NET MVC上的 AJAX解决方案最终会进入到官方的发布中呢?好像他专门搞这方面的,好像ASP.NET AJAX框架就是他架构的。如果是这样,最终版本肯定是有所改进的,现在我估计是放出来听听“风声”,微软现在知道社区的重要性了。看看那些开源的开发框架多符合口味,要啥有啥!

  13. SZW
    *.*.*.*
    链接

    SZW 2008-04-26 11:49:00

    我也正好准备在http://www.cnblogs.com/szw/archive/2008/04/12/1150353.html之后继续写一些UpdatePanel、Repeater之类的“控件”,已经有了个大致路,和MvcAjaxPanel不同的是尽量不去用runat=server,全部靠自身的js库(以及约定)去实现,老赵最后提出的“如何让MvcAjaxPanel在循环内容生效”的问题在我这个模型里面应该不难实现。只是现在还在考虑是否使用josn+模板的形式还是这样直接输出html的形式,所以迟迟没有开始写,目前我更倾向使用前者,但是这样做还有一些细节方面的特殊情况需要针对处理。

    现在这里说下我的思路:
    对于用json方式:View第一次通过Get请求输出数据,同时这个数据(当然包含html标签)构成了一个现成的"模板",第二次通过ajax接受json的时候直接利用这个现成的“模板”进行填充(当然我前面已经说了还有些细节需要处理,但总体实现是没有问题的)。(PS:这种方法效率很高,但并非“万能”,如果有些UpdatePanel传回的内容形式是极为不确定的(完全颠覆之前格式的,其实总体来看还只是少数),可以考虑同时或单独使用以下这种html方式。)

    对于html方式:和老赵的最终实现方式类似,其中一种方式是真个页面重新生成,然后到客户端替换,但是我觉得这样做如果只为一小块区域(比如一个<span>的信息)的话,效率不够高(老赵的源代码我还没有细细看,不知是否是这样?)。所以又想到了第二种方法:让UpdatePanel中的代码单独运行,然后传回html,直接替换客户端标签内容。目前我能想到的综合考虑最“实惠”的方法是使用mvc的ascx,好处是可以单独呈现,并且实现相对json方法更简单,而且可以在不同场合复用(特别是浮动层,我为此做了一个简单的demo,用起来太爽了),可谓一举多得。用ascx有一点不太好的地方是有时候编辑的时候不如在一个页面中直观。

    老赵对此是否有什么建议?
    以上两个方法也同样适合在ASP.NET MVC/WebForms中使用。

  14. xjb
    *.*.*.*
    链接

    xjb 2008-04-26 12:08:00

    楼主强人呀,3点才睡觉,难道早上10点上班?

  15. Q.Lee.lulu
    *.*.*.*
    链接

    Q.Lee.lulu 2008-04-26 13:11:00

    呵呵,老赵果然爱学习啊,0416的代码都已经研究了.....
    SZW说的尽量不要去使用runat="server"这个想法不错,对于ASP.NET MVC来说确实应该如此,期待你的文章.

    另问一个问题:一般的web form如果用JS库(例如jQuery)来实现Ajax,如何来使用postback呢 ?望解答偶的菜鸟问题

  16. 笨→鸟(Bird)
    *.*.*.*
    链接

    笨→鸟(Bird) 2008-04-26 14:05:00

    @Jeffrey Zhao
    呵呵.我是新兵.学习中.以后还望多指点.

  17. 老赵
    admin
    链接

    老赵 2008-04-26 14:16:00

    @SZW
    你说的这个方式不是和Nikhil的方式差不多吗?他没有用JSON,用的是直接输出HTML,你可以看看他的做法,他在设计上还考虑了一些使用时的变化。其实一般项目中我也基本上是用的你说的第一种做法,不过没有封装成控件而已。我以前写过用ascx作为模板生成HTML的文章,就是为了这么做。
    我目前没有整页Render,只输出需要的部分,这就好像你说的UpdatePanel内部的代码单独运行,生成HTML。但是我认为如果可用性要更高的话很可能就需要这样做,因为一个MvcAjaxPanel所处的位置往往和页面上下文息息相关。最典型的例子,就是我说的一个MvcAjaxPanel放在一个循环内部。
    至于性能其实没有什么差别。这些方面所谓的性能在实际应用中都基本上可以忽略不计。而且即使用UserControl生成HTML,还记得怎么做吗?不就是把它放入一个Page,然后Execute一下就获得代码吗?还是在执行一个完整的页面,只是页面内容少了点,相差的只是一些method call,没有数据访问,没有复杂计算。
    而且我思考过,如果真care这点的话,也只要用一个新的View来代替一个复杂无比的View。因为MvcAjaxPanel是可以跨页Rendering的,这也就是和UpdatePanel最大的区别。

  18. 老赵
    admin
    链接

    老赵 2008-04-26 14:20:00

    @Q.Lee.lulu
    我不觉得ASP.NET MVC就应该尽量不使用runat=server。runat=server是模板引擎的特性,使用它也不会有任何缺陷。
    你问的如何进行postback是什么意思啊?是指什么呢?

  19. asp.net CMS[未注册用户]
    *.*.*.*
    链接

    asp.net CMS[未注册用户] 2008-04-26 14:22:00

    学习ing

    厉害

  20. 老赵
    admin
    链接

    老赵 2008-04-26 14:22:00

    @Indigo Dai
    Nikhil是ASP.NET团队的架构师,但ASP.NET AJAX不知道是不是他设计的,至少所谓的“UpdatePanel之父”不是他,呵呵。

  21. 老赵
    admin
    链接

    老赵 2008-04-26 14:24:00

    @TT.Net
    我的设计目标和Nikhil的方式在理念上是不同的,目标是共存,而不是替换。
    这点不像ASP.NET AJAX与AJAX.NET,前者一出现后者就差不多被完全替换掉了……

  22. Areyan
    *.*.*.*
    链接

    Areyan 2008-04-26 14:40:00

    受教了,继续关注。

  23. 老赵
    admin
    链接

    老赵 2008-04-26 15:07:00

    @SZW
    其实你说的和Nikhil的方法都和RoR比较接近,那就是基于Partial Template(在ASP.NET MVC里自然就是User Control)的AJAX解决方案。不过既然是ASP.NET MVC,我就想多利用点现有模型,搞出一点不同于其他技术的东西。

  24. SZW
    *.*.*.*
    链接

    SZW 2008-04-26 15:59:00

    @Jeffrey Zhao
    大概看了下你的Nikhil的代码,我觉得你的方法比Nikhil的更“实用”一些。赞:)
    我的想法和Nikhil还是有区别的,虽然在Views里面看上去有些相似,我觉得在Controller里面的Ajax和non-Ajax差别操作少一点更好。

    依靠HTML实现“刷新”这条路子上面,需要单独运行一段html的方法其实还能想到不少,之所以我觉得ascx(我上面强调的是mvc中的ascx)挺好,也是出于你上面提到的一种事实——Nikhil那样或者近似的方法使得Ajax请求在整个Controller中显得过于“特殊”,而ASP.NET MVC恰巧在这个环节上为我们提供了一个很大的便利——RenderView可以同时返回aspx或者ascx(本质也都是Execute,aspx优先于ascx),而不需要任何特殊处理——这种便利归功于MVC结构本身。同时aspx和ascx也都接受ViewData(ascx在aspx中使用的时候自动传承aspx的ViewData,“兼容性”很好)的模型,从这点上来说,acsx成为一个单独“页面”或者HTML段更为便捷和“无差异化”。从使用、调用上来看,也更加灵活。

  25. 老赵
    admin
    链接

    老赵 2008-04-26 16:06:00

    @SZW
    不觉得有什么差别,呵呵。Nikhil的Controller代码把AJAX操作和非AJAX操作混在一起了,这主要是因为他是将一个普通应用程序给“AJAX化”的。他也完全可以只为AJAX输出,这样就不用判断什么IsAjaxRequest了,而且其实他现在的代码也执行不到IsAjaxRequest等于false的情况。不过他判断IsAjaxRequest也有一个好处,那就是“能够轻松地在不支持AJAX的浏览器中使用传统的方式切换页面”。

  26. SZW
    *.*.*.*
    链接

    SZW 2008-04-26 16:08:00

    @Jeffrey Zhao
    写完上面这段话突然想起以前想到的一个问题,jsp之所以在MVC框架的发展上面相对asp.net如此“兴旺”,应该也和其本身的架构有很大的关系,jsp“天生”就更加容易去实现这一系列C-V分离的需求,不知是否有同感?

  27. 老赵
    admin
    链接

    老赵 2008-04-26 16:15:00

    @SZW
    我也不知道是不是“天生”,不过这的确应该涉及到一个演变规律的问题。
    不管WebForm和MVC孰优孰劣,WebForm模型的复杂度远甚于MVC应该是一件大家都承认的事情。从当年诞生开始WebForms就很强大,就走上了组件化,事件模型等等这一条路。
    而JSP它和ASP.NET WebForms相比我觉得真也只能算是一个“HTML显示引擎”,功能远不及后者,而把逻辑混在这个引擎里使用本来就属于一种滥用。为了解决滥用问题,于是有人发展出了MVC这种协作方式,把JSP单纯作为一个模板引擎,逻辑由另外组件来承受。
    所以我觉得要说JSP是“天生容易实现MVC”(你看在ASP.NET里实现MVC也不困难啊),还不如说是天生有缺陷所以被强迫着改变。。

  28. SZW
    *.*.*.*
    链接

    SZW 2008-04-26 16:36:00

    @Jeffrey Zhao
    首先我觉得ASP.NET(WebForms)里实现MVC很简单还不如说是实现MVP比较简单,WebForms(使用其功能、特性的前提)框架下面就决定了哪怕实现了所谓“MVC结构”,也不会在实质上达到“MVC架构”的要求,这一点在开发环节上就已经很明显了。
    其次ASP也有同样的诟病(当然和jsp架构上面引发的缺点是不同方面的),为什么ASP的MVC相对jsp没那么兴盛和完善呢?这个一直是我以前开发ASP的时候对ASP十分疑虑的问题,我个人的感觉是“架构”这个“天生”的方面发展了也同时制约了很多东西。现在回过头来看ASP我都想笑……

  29. 老赵
    admin
    链接

    老赵 2008-04-26 17:13:00

    @SZW
    技术架构决定容不容易,有了这个保证之后实现逻辑架构其实只要有聪明人就能实现格框架。剩下的就是特定框架在设计上的优劣,这个容易取长补短。
    还有我是说ASP.NET实现MVC简单,而不是说WebForms。ASP.NET的技术架构很好,以前只是往WebForms发展了,现在出一个MVC也非常简单。现在ASP.NET MVC是一个例子,以前Monorail也是一个例子。
    至于说ASP为什么没有发展MVC,是因为它被抛弃了,人们停止思考了。而微软选择了走WebForms这条路,Java选择了走MVC。当然ASP的技术架构的确不完善,要搞(现在这种)MVC估计不容易。
    至于你说得在WebForms里实现MVC不容易,是因为你一定要保持所有的WebForms特性,还要求是目前ASP.NET MVC这种MVC方式。如果能保留一部分,舍弃一部分,实现一个可用的MVC我觉得也不是什么大问题。当然MVP诞生起就是面向事件驱动界面使用的模式,当然非常适合WebForms。
    前一段时间太懒,希望接下来有动力可以写写这方面东西……

  30. 狼Robot
    *.*.*.*
    链接

    狼Robot 2008-04-26 18:11:00

    学习

  31. Indigo Dai
    *.*.*.*
    链接

    Indigo Dai 2008-04-26 22:20:00

    @Jeffrey Zhao

    我感觉Nikhil对AJAX颇有研究。

  32. 王孟军!
    *.*.*.*
    链接

    王孟军! 2008-04-26 22:59:00

    学习吧

  33. Q.Lee.lulu
    *.*.*.*
    链接

    Q.Lee.lulu 2008-04-27 00:13:00

    @Jeffrey Zhao
    就是在Ajax进行postback的时候,如何直接使用页面的viewstate来进行postback

  34. Shiny Zhu
    *.*.*.*
    链接

    Shiny Zhu 2008-04-27 00:46:00

    这个扩展得不错~
    比我用mootools自己写script来处理request方便得多了,但页面里还是有不好看的源代码。厄,为什么我总是看不习惯那些WebResoure.axd什么什么的呢?

  35. 老赵
    admin
    链接

    老赵 2008-04-27 00:51:00

    @Q.Lee.lulu
    自己收集页面上所有该提交的数据组成个body就可以了。

  36. 老赵
    admin
    链接

    老赵 2008-04-27 00:51:00

    @Shiny Zhu
    追求WebResource.axd好看不好看实在没有必要了吧,把脚本嵌在程序集里可是个good practice,要保证组件的内聚性这点必不可少。

  37. 帅帅001[未注册用户]
    *.*.*.*
    链接

    帅帅001[未注册用户] 2008-04-27 04:18:00

    支持楼主,
    收藏慢慢看,
    一如既往支持老赵,

  38. 留恋星空
    *.*.*.*
    链接

    留恋星空 2008-04-27 09:18:00

    虽然不全明白,但还是尝试着看。

  39. 1111111195ee[未注册用户]
    *.*.*.*
    链接

    1111111195ee[未注册用户] 2008-04-27 10:39:00

    已经拜读
    互联网创业下载中心:
    http://down.163k.com
    还有这个:
    http://biz.163k.com

  40. Zhuang miao
    *.*.*.*
    链接

    Zhuang miao 2008-04-27 11:42:00

    我现在基本不看你的文章了,看不懂。。。。

  41. SZW
    *.*.*.*
    链接

    SZW 2008-04-27 11:53:00

    @Jeffrey Zhao
    是的,我说的“简单”也就是这个意思,并不是单纯纵向看有多少潜力和发挥余地(这点上我对ASP.NET MVC还是抱有信心的)。

    @Q.Lee.lulu
    ViewState被编码后的内容都被存在input:hidden里面,用jquery只要对整个form的内容进行序列化(或者手动也可以,获取name="__VIEWSTATE"等),然后再使用jquery的ajax方式的form提交就可以了,我以前用这个方法做过一个只靠jquery的无刷新上传(WebForms下),在服务器端感觉不到有什么区别(连Files也能获取,这个通过普通的post好像是取不到的)。

  42. Shiny Zhu
    *.*.*.*
    链接

    Shiny Zhu 2008-04-27 14:24:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @Shiny Zhu
    追求WebResource.axd好看不好看实在没有必要了吧,把脚本嵌在程序集里可是个good practice,要保证组件的内聚性这点必不可少。
    --------------------------------------------------------

    的确没必要的,这一点我现在已经接受了。把脚本嵌在程序集里的确很大程度上提高了编程的容易程度~

  43. 老赵
    admin
    链接

    老赵 2008-04-27 14:27:00

    @SZW
    AJAX的Post是做不到上传文件的,但是jquery能够实现无刷新上传?我对这个很感兴趣,它是怎么做到的阿?应该是iframe但是我对与iframe接受服务器端信息总是做不好,主要是跨浏览器的问题……

  44. SZW
    *.*.*.*
    链接

    SZW 2008-04-27 15:00:00

    @Jeffrey Zhao
    那里面没有用iframe,我对ajax里面还要靠iframe实现“ajax效果”的做法还是比较回避的(除了为了实现“后退”等功能,也只能这样),要么干脆全部用iframe,这里面很多缺点(以及很多功能其实完全可以不用iframe)这里就不展开了。

    用jquery+webforms实现上传是我考虑到了这么几个因素:
    其一,webforms依靠postback的一系列操作实现文件上传实在是方便,功能也很强大,问题是你必须要postback才能获得Files的Collection。
    其二,jquery的ajax功能也强大易用,问题是这个post你就这么直接用的话怎么也不会有postback的效果(以及之后对服务器控件、数据的操作、捕获),因为所有的viewstate都在这里断掉了。

    问题如你所说,最简单常用的方法是用iframe,那样其实用ajax与否并不是什么关键问题了,同样,我也没有必要用别的js库去做了。我之所以让这两个搭配,就是要摆脱iframe(webforms和jquery在这里的作用都只是简化操作而已,而且是我觉得比较好的搭配,并不是非要这么用——其实我不应该太强到jquery的,只是Q.Lee.lulu正好提到了,请不要误会)。

    为了使这个方法同时兼顾webforms(环境)和MVC中的使用,我最后决定在这个ajax请求没有特殊的对服务器控件的操作的情况下,让一个ashx去完成接受这个post过来的任务(返回当前页面也是可以的——那样是更纯粹的postback),在post之前,还需要对页面form当中的内容加以处理,也就是我上面说的对form中的内容进行序列化(关键是这里,配合jquery的一系列form和ajax功能做可以比较方便),比如达到这样的效果(只是个外观而已):__VIEWSTATE=abcd&File1=C:\x.jpg……。(这里__VIEWSTATE值只是为了说明一系列PostBack的特征参数)
    如果不这样做,服务器是不会收集HttpFileCollection的(这个是我反复测试的结果,我对其中种种原理还没有全部了解清楚,如果有什么不对请指出),当然也无法用webforms的方法去实现文件上传。剩下的工作和普通的方法没有什么差别了,最后返回一个success的信息给页面就ok了

  45. 侯垒
    *.*.*.*
    链接

    侯垒 2008-04-27 15:58:00

    什么都不说了,赞一个,再学习.

  46. 老赵
    admin
    链接

    老赵 2008-04-27 16:06:00

    @SZW
    没听懂。我再问的直接点吧,不说jquery,webform之类的东西了,说这些对于现在的问题只会混淆视听。
    现在的结论就是用XMLHttpRequest对象没法上传一个文件。有什么解决方法或者workaround吗?
    如果要保持不刷新上传,那么就需要iframe或富客户端(Flash/Silverlight)的支持,有别的方法吗?
    或者再纯粹点的问法,如果要支持上传文件的无刷新提交,你会怎么做?

  47. 老赵
    admin
    链接

    老赵 2008-04-27 16:08:00

    @SZW
    其实Anthem.NET的功能非常满足我的要求,但是我一直没有看它的实现方式,还是懒。

  48. Q.Lee.lulu
    *.*.*.*
    链接

    Q.Lee.lulu 2008-04-27 16:18:00

    @Jeffrey Zhao
    @SZW
    谢谢两位的解答,我再捕获下ASP.NET AJAX用UudatePanle进行Ajax请求所Post给服务器的参数(__VIEWSTATE等等),再研究下。我是想直接利用页面的ViewState来Postback回去给服务器,然后在后台就可以直接取页面上控件的值(不用自己手动在后台对Post回来的值进行处理,就跟同步提交时一样的取值,如 name=txtName.Text ).

  49. SZW
    *.*.*.*
    链接

    SZW 2008-04-27 16:27:00

    @Jeffrey Zhao
    你说的这些问题我上面都有答案阿:
    ====================
    如果要保持不刷新上传,那么就需要iframe或富客户端(Flash/Silverlight)的支持,有别的方法吗?
    ====================
    我上面这样的方法就不需要任何别的iframe或者flash,sl的支持。
    当然我个人认为这个方法在效率上是比较高的,但是不一定是所有方法中最好的方式,只是我对于使用jquery的ajax和webforms配合上面的一些尝试。

    这么说确实太抽象了,我找时间做一个实例发上来吧。

  50. 老赵
    admin
    链接

    老赵 2008-04-27 16:53:00

    @SZW
    在body里拼接一个File1=C:\x.jpg就可以了?这个不可能上传内容的,上传的文件是需要被序列化至数据流的,而且这时候body的enocde也是multipart/form-data,而不是默认的application/x-url-encoded……

  51. SZW
    *.*.*.*
    链接

    SZW 2008-04-27 17:03:00

    @Jeffrey Zhao
    具体在文件传输这个单个的环节上面,jquery是否使用了别的方式暂时我还不太清楚,但是肯定是用到了multipart/form-data、form序列化以及服务器端对File的正常操作,当然还有jquery的form方式提交ajax请求,这个我已经实现了,没有发现有什么问题啊。
    也许这个单个的环节上面jquery使用了别的什么方式,这个我当时也没有太过关心,但是实现的效果确实是通过ajax请求,不依靠iframe无刷新的,并且也能完成postback一样的上传操作的。我当时想的重点就是在实现上面,具体里面的流程是怎么样的,可以等我发上来了之后好好研究研究,我也想弄个究竟。

  52. 老赵
    admin
    链接

    老赵 2008-04-27 17:14:00

    @SZW
    哈,不过我还是不信。JQuery也不是神也不能违反JavaScript限制的……AJAX的一个重要特点就是无法上传文件,如果你真能让XMLHttpRequest上传,那真是一个重大突破了。话说这个特点有没有官方文档可以看看?

  53. SZW
    *.*.*.*
    链接

    SZW 2008-04-27 17:24:00

    @Jeffrey Zhao
    上传环节它内部是怎么弄的,是不是XMLHttpRequest,如何XMLHttpRequest,或者还是服务器本身就是通过别的什么渠道获取的,目前还不清楚,亦或这个jquery.ajax后面还隐藏了什么(不会直接就不用XMLHttpRequest吧,那jquery让我太伤心了)。这个“搭配”是我自己想出来的,我没有看到过任何jquery官方说明说XMLHttpRequest可以实现文件上传。当时我也只是尝试了一下,没想到倒也就成了,你说的那些问题当时我也很纳闷阿:)

  54. SZW
    *.*.*.*
    链接

    SZW 2008-04-27 17:35:00

    @Jeffrey Zhao
    我当时的感觉是jquery.ajax是提交了一个请求,这个请求几乎、一定是ajax方式的,到了服务器端后,可能还是通过普通的(原本PostBack的)方式获取了HttpFileCollection(只是猜测,里面到底怎么做的我还没去深入了解),而接下去的操作反正就和ajax没有关系了,等到完成后,jquery.ajax还是获取返回的信息,至少整个表面上还是如一个普通的Ajax过程一样平静,也没有用到iframe。
    或者说得简单一点,是不是有这种可能:这个"Ajax"请求里面包含了一个普通的Post请求,而这个请求从一开始到结束,都被jquery监视着,这种“无刷新”的效果并非来自XMLHttpRequest,而是因为这个请求本身就被强制控制着?

  55. 老赵
    admin
    链接

    老赵 2008-04-27 17:44:00

    @SZW
    还是快给个例子看吧,最简单的就可以,呵呵……

  56. SZW
    *.*.*.*
    链接

    SZW 2008-04-27 17:50:00

    @Jeffrey Zhao
    刚空下来,正在弄!嘿!

  57. 镜涛
    *.*.*.*
    链接

    镜涛 2008-04-27 19:38:00

    看完楼主的文章,哎,自己的差距还是很大啊!

  58. 老赵
    admin
    链接

    老赵 2008-04-27 20:23:00

    @SZW
    还是iframe啊,呵呵。

  59. 老赵
    admin
    链接

    老赵 2008-04-27 23:59:00

    描述上作了一些修改。

  60. ruimingde[未注册用户]
    *.*.*.*
    链接

    ruimingde[未注册用户] 2008-04-28 00:12:00

    很困,没仔细看完、、

  61. 老赵
    admin
    链接

    老赵 2008-04-28 00:26:00

    @ruimingde
    睡醒再看,呵呵。

  62. 秸杆-看评论
    *.*.*.*
    链接

    秸杆-看评论 2008-04-28 08:10:00

    再路过

  63. TT.Net
    *.*.*.*
    链接

    TT.Net 2008-04-28 08:27:00

    呵呵,AJAX上传文件。国外的牛人都说不行的

  64. Enzo
    *.*.*.*
    链接

    Enzo 2008-04-28 09:03:00

    拜读! o(∩_∩)o...

  65. 老赵
    admin
    链接

    老赵 2008-04-28 09:11:00

    @TT.Net
    大哥,国内牛人也没比国外牛人差,“国外牛人”也有错误的时候,而且也不是一次两次了。

  66. SZW
    *.*.*.*
    链接

    SZW 2008-04-28 11:01:00

    @Jeffrey Zhao
    是啊,jquery.ajaxForm它里面还使用了iframe,不过也在情理之中,至少发现了一个很便捷的方法,再扩展一下也能模拟出些更多的postback的效果来。实践才能出真知阿……

  67. STS[未注册用户]
    *.*.*.*
    链接

    STS[未注册用户] 2008-04-28 11:27:00

    ASP.NET的MVC本来就可以在外面包一层AJAX PANEL.
    这个不需要任何尝试. 更加不需要做任何东西.

    但是问题是这个做法可以说是完全没有用.
    1. 这会引起非常多的兼容性问题. 主要是客户端资源加载方面.
    2. MVC依然无法直接使用控件. 如果要间接使用控件, 哪么MVC就和WEBFORM的逻辑耦合在一起, MVC的意义就直接丢失.

  68. STS[未注册用户]
    *.*.*.*
    链接

    STS[未注册用户] 2008-04-28 11:29:00

    如果大家对AJAX上传文件感兴趣, 可以去看看ajaxuploader.com
    可惜就是那个东西要钱. 目前好像没有破解版. 应该是知名度不高的原因.

  69. 老赵
    admin
    链接

    老赵 2008-04-28 11:48:00

    引用--------------------------------------------------
    STS: 如果大家对AJAX上传文件感兴趣, 可以去看看ajaxuploader.com
    可惜就是那个东西要钱. 目前好像没有破解版. 应该是知名度不高的原因.

    --------------------------------------------------------
    AJAX上传解决方案很多我也写过文章,但我要的不是这个。
    我觉得您回复前应该看清楚文章或评论的内容和目的。

  70. 老赵
    admin
    链接

    老赵 2008-04-28 11:49:00

    --引用--------------------------------------------------
    STS: ASP.NET的MVC本来就可以在外面包一层AJAX PANEL.
    这个不需要任何尝试. 更加不需要做任何东西.

    但是问题是这个做法可以说是完全没有用.
    1. 这会引起非常多的兼容性问题. 主要是客户端资源加载方面.
    2. MVC依然无法直接使用控件. 如果要间接使用控件, 哪么MVC就和WEBFORM的逻辑耦合在一起, MVC的意义就直接丢失.
    --------------------------------------------------------
    你的说法明显是错误的,你给我个例子试试看?请注意AJAX请求后Render成不同View,Render回去的不算。而我这个东西就是为了解决Render到不同View的问题。而且正像你说的,用UpdatePanel一定程度上丧失了用MVC的意义,而我就是为了解决这个问题的。

  71. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-04-28 11:49:00

    @SZW
    jquery 上传文件用的是动态创建的 iframe 和普通的 form 提交。
    并且动态复制了一下 input type="file" 控件。

  72. 老赵
    admin
    链接

    老赵 2008-04-28 11:52:00

    @木野狐(Neil Chen)
    嗯,发现了,可惜还是不行,实现的问题比较大。

  73. 吉它谱中文娱乐网[未注册用户]
    *.*.*.*
    链接

    吉它谱中文娱乐网[未注册用户] 2008-04-28 13:25:00

    很好!

  74. SZW
    *.*.*.*
    链接

    SZW 2008-04-28 20:41:00

    @木野狐(Neil Chen)
    是的,现在里面的机制搞明白了。直接用jquery.ajaxForm的好处是你不需要去关心那些iframe,直接用就可以了,当他不存在,简单。缺点也在这里,当你想关心的时候,心有余而力不足。

  75. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2008-04-29 20:20:00

    IsAjaxRequest+RenderPartial,和RoR的做法很类似噢!

  76. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2008-04-29 20:28:00

    你觉得controller不应该知道这是不是Ajax请求,对不对?RoR的做法很简单,在HTTP header放一个标记,说明这是Ajax请求,见到标记就局部呈现,见不到标记就全页呈现。

  77. 老赵
    admin
    链接

    老赵 2008-04-29 22:45:00

    @Cat Chen
    我觉得Nikhil就是学RoR的。
    还有就算放一个标记在Header里,Controller也要知道是Render一个Partial还是一个Page。这个和Controller知不知道AjaxRequest一回事情阿,依旧逃不过“每个Action都要知道自己是为一个AJAX还是普通Request服务”。
    我就是为了逃过这点,呵呵。

  78. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2008-05-06 12:29:00

    @Jeffrey Zhao
    这要分两种情况讨论:

    1.partial rendering的话,还是和UpdatePanel一样,不需要知道是否为partial rendering,因为整个process都是按照page rendering来做的,只是最后才决定是page rendering还是partial rendering。

    具体例子是,一个GridView删除一行,操作有Delete这个Action负责,那么在.NET里面这个Action就是进行SQL操作然后重新DataBind,在RoR里面没有DataBind不过也是按照template来重新呈现table。总之,呈现过程一致,到最后再把GridView剪裁出来局部发送。

    2.script rendering的话,就必然很明确区分是否为Ajax请求了,甚至非Ajax请求就无法处理。

    这个在.NET里面估计没有对应实现。在RoR里面,还是table删除一行的例子,你可以在SQL操作后用rjs输出一段DOM操作,把删除的行fade特效,然后再彻底从DOM删除。

  79. 老赵
    admin
    链接

    老赵 2008-05-06 16:13:00

    @Cat Chen
    Script Rendering很有可能内置到ASP.NET MVC框架中去。

  80. 夕阳夕下
    *.*.*.*
    链接

    夕阳夕下 2008-05-25 16:58:00

    赵哥啊,要多注意休息啊,身体才是革命的本钱,虽然我们常这样对别人说,但是自己都很难做到哦,往往是凌晨几点睡,多注意身体。

  81. 减速机[未注册用户]
    *.*.*.*
    链接

    减速机[未注册用户] 2008-06-01 19:29:00

    身体才是革命的本钱

  82. 防静电工作台[未注册用户]
    *.*.*.*
    链接

    防静电工作台[未注册用户] 2008-06-01 19:30:00

    我也觉得Nikhil就是学RoR的。

  83. 孤星赏月
    *.*.*.*
    链接

    孤星赏月 2008-07-04 14:34:00

    这正是我所想要得功能?学习了!

  84. xyun[未注册用户]
    *.*.*.*
    链接

    xyun[未注册用户] 2008-07-12 11:14:00

    很好的功能。
    如果需要返回后执行javascript,如何实现?
    是否需要再添加一个ScriptPanel,提供javascript代码执行的功能

  85. 黄伏奇
    *.*.*.*
    链接

    黄伏奇 2008-07-17 22:51:00

    赵老师,您好!
    用您的框架在 VS2008 + XP SP2 + IIS6下调试没有问题,但在 VS2008 + Windows Server 2008 + IIS7发现有问题,找不到mvcAjax对象了,甚至Sys对象也找不到。后来发现问题出在配置节
    <httpModules>
    <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    </httpModules>
    如果去掉"UrlRoutingModule"节,则可以运行的服务器控件UpdatePanel的局部异步更新操作,但MVC的路径就会找不到;如果去掉"ScriptModule"节,则可以找到路径,但Sys和mvcAjax对象找不到了。后来发现不改变配置节,在目录C:\Inetpub\wwwroot\aspnet_client\system_web\2_0_50727\CrystalReportWebFormViewer4\js下把MvcAjax.js放到好像可以了,但一些UpdatePanel封装的控件又出现问题,"非法的对象"。不知赵老师能不能就这块详细说明一下?

  86. kxl[未注册用户]
    *.*.*.*
    链接

    kxl[未注册用户] 2008-09-17 11:31:00

    我把程序中的Views下的Home中的index.aspx设置为起始页,怎么总是报错呢,错误如下
    无法找到资源。
    说明: HTTP 404。您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确。

    请求的 URL: /Views/Home/Index.aspx


    --------------------------------------------------------------------------------
    版本信息: Microsoft .NET Framework 版本:2.0.50727.1433; ASP.NET 版本:2.0.50727.1433

  87. 烟雨平生
    *.*.*.*
    链接

    烟雨平生 2008-10-11 10:47:00

    在mvc p5中,如果页面中使用了RenderPartial将无法工作。郁闷!

  88. kuibono
    *.*.*.*
    链接

    kuibono 2009-12-06 00:13:00

    我是来学MVC有,顺便鄙视一下北大青鸟

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我