[b]Web 应用的 MVC 和经典的 MVC 显著不同[/b]
MVC 早期出现在桌面应用中。由于桌面应用的各个部分都运行在同一个进程(即便是后来流行的 C/S 应用,很大程度上也可以假想为同一逻辑进程)中,所以应用程序各个部分之间的交互都视为“可信任的”。
经典的 MVC 是 C 返回一个合适的 V。V 里面调用 M 获得数据,如果有什么 UI 上的事件发生,也是 V 里面直接去操作 M 了。因此在经典 MVC 中,应用程序逻辑是主要放到 V 中的。C 只完成应用程序逻辑中的小部分(分发不同的 V)。由于大部分应用程序逻辑是在 V 里面实现,所以 V 还要维持一个内部状态。
就拿大家前面的例子:输入框如果输入错误的数据,应该变成红色。
这个需求是典型的应用程序逻辑(更可以具体为一个 UI 逻辑),所以最好的实现场所就是 V 里面。在经典 MVC 中,控件输入数据会引发一系列的事件,而 V 可以拦截控件的 onchange 事件,从而设置控件的颜色。后来更进一步,将控件封装为对象,从而让控件对象负责自己的逻辑,V 就不用操心这些细节了。
[b]到了 Web 时代,MVC 就变了[/b]
最关键的一点就是应程序被分成了浏览器端和服务端两部分。
对 MVC 的影响就是 V 现在也被分成了两部分,一部分放在浏览器端,一部分放在服务端。
还是那个输入框的例子,Web 应用处理起来有多种方式:
1、输入改变后,什么都不管,直到提交表单后,服务端对数据进行验证。如果验证失败,再重新构造整个用户界面,并且将输入框的属性改成红色。这个过程中,V 完全是被动的,仅仅是负责渲染 HTML 而已。
2、输入改变后,通过 Ajax 请求提交这个控件的数据,让服务端对数据进行验证。服务端返回特定的数据(例如一个 JSON)指示验证是否通过,V 里面的 JS 代码则根据返回结果更新输入框的颜色。
3、输入改变后,提交整个表单。然后服务端根据客户端的事件调用服务端的事件处理方法。事件处理方法中对控件数据进行验证,再更新控件的属性。最后服务端重新构造整个页面。
4、输入改变后,客户端的 JS 直接验证并更新控件状态。但整个表单提交到服务器后,还是得一一验证。
第一种是最传统的,也是最常用的。第二种则是 Ajax 兴起后流行的,不过现在大家也在反思这种模式是不是造成太多对服务器的请求。第三种则是 ASP.NET 的 POSTBACK 机制,JSF 我不了解,看介绍也是类似的机制。第四种实际上只是第一种的变形。
第一种模式:浏览器端完全不负责任何逻辑,仅仅是提供一个输入数据的接口,然后可以把数据提交到服务端。应用程序逻辑完全在服务端里面实现。
这种模式里面,V 实际上只是一个模板。服务端的 C 负责响应请求,然后验证浏览器端提交的数据,再调用 M 进行处理。最后根据处理结果,载入模板,渲染出 HTML 返回给浏览器。
第一种模式容易理解,而且实现简单,对浏览器端没有特殊要求,所以一直以来都是最主要的处理模式。但缺点就是用户体验不好,对用户操作不会有及时的反馈,而且没有办法根据用户操作实时的更新 UI。
第二种模式:浏览器端稍微主动点,会把 UI 事件报告给服务端。服务端处理后再反馈特定信息给浏览器端,浏览器端从而可以更新 UI。
比起第一种模式,第二种模式中的 V 会包含一些 JavaScript 代码。这些 JS 代码负责响应 UI 事件,然后把事件及相关的数据传递到服务端,并等待服务端的响应。服务端还是由 C 来处理,只不过处理完成之后仅仅是把处理结果返回给浏览器端(XML、JSON、字符串或者 JS 代码)。浏览器端的 JS 收到服务端的响应后,根据处理结果对 UI 做相应的更新。
这种模式的相对第一种来说有了不小的进步。用户操作可以马上得到反馈,而且可以构造非常动态的 UI。缺点则是需要编写大量的 JS 代码,服务端也要增加对应的响应代码。而且不同浏览器对 JS 的支持程度也不同,实践起来即使有各种 JS 框架的帮助也是比较繁琐的。
第三种模式:这种模式一个页面就是一个表单,JS 会去响应表单中的 UI 事件,然后根据事件类型决定是立即提交整个表单,还是稍后提交。
当表单提交后,服务端就不再是一个常规意义上的 C 来处理了,而是一个页面控制器(可以看作更复杂的 C)。页面控制器会根据定义,构造出整个页面的对象体系(例如 Page 对象中有两个 Textbox 对象)。构造好对象体系后,页面控制器会解析提交的表单中包含的事件。然后调用各个控件的事件响应方法。此时,就和桌面应用非常接近了。我们可以给各个控件编写事件响应代码,并在事件响应代码中调用 M 处理数据。当所有的事件都处理完成后,页面控制器会载入模板,然后传入对象体系,并通过复杂的渲染机制,将 UI 内容渲染出来返回给浏览器。
这种模式的好处很明显,那就是应用程序逻辑都在 V 里面完成。而且控件封装为了对象,控件的逻辑可以自己负责。缺点就是每次提交都是提交整个表单,数据量较大时明显会增加通讯量和延迟。而且早期的 Web 应用事件驱动模型会导致每次提交都重新构造整个页面,用户体验和效率也不怎么样。好在后来都有所改进,使用 Ajax 来提交表单和更新用户界面。
在上述三种模式中,我们会发现一个共同点:绝不在浏览器端实现应用程序逻辑。
因为浏览器端是不可控的运行环境,如果应用程序逻辑在浏览器端实现,那么安全性就得不到保证了。比如批量删除操作这个应用程序逻辑如果在浏览器端实现,用户完全可以自己分析浏览器端的代码和逻辑,直接构造一个表单来提交,从而绕过各种验证。
但是不在浏览器端实现应用程序逻辑,不代表不可以在 V 里面实现应用程序逻辑。要记住在 Web 应用中,V 是可以分成多个部分的,并且分别运行在服务端和浏览器端的,而且在服务端实现 V 也有很多种模式。实际上第三种模式就是典型,V 运行在服务端,并且完成所有的应用程序逻辑。
[b]在服务端 V 中实现应用程序逻辑[/b]
比如我们可以在模板中写入应用程序逻辑,然后模板引擎解析模板时,通过嵌入模板的 tag 来调用 M 获得要呈现的数据。这种方式已经非常非常多的实现了,实际应用时对于显示各种数据是很方便的。但是这种模式没办法主动响应用户操作。所以后来出现的事件驱动才是最完整(但不是最完善)的解决方案。
可惜由于 Web 应用的天然特性,事件驱动不管怎么改善,都绕不开将整个表单提交到服务器的过程。所以事件驱动在 Web 应用上一直都没有成为主流。
MVC 早期出现在桌面应用中。由于桌面应用的各个部分都运行在同一个进程(即便是后来流行的 C/S 应用,很大程度上也可以假想为同一逻辑进程)中,所以应用程序各个部分之间的交互都视为“可信任的”。
经典的 MVC 是 C 返回一个合适的 V。V 里面调用 M 获得数据,如果有什么 UI 上的事件发生,也是 V 里面直接去操作 M 了。因此在经典 MVC 中,应用程序逻辑是主要放到 V 中的。C 只完成应用程序逻辑中的小部分(分发不同的 V)。由于大部分应用程序逻辑是在 V 里面实现,所以 V 还要维持一个内部状态。
就拿大家前面的例子:输入框如果输入错误的数据,应该变成红色。
这个需求是典型的应用程序逻辑(更可以具体为一个 UI 逻辑),所以最好的实现场所就是 V 里面。在经典 MVC 中,控件输入数据会引发一系列的事件,而 V 可以拦截控件的 onchange 事件,从而设置控件的颜色。后来更进一步,将控件封装为对象,从而让控件对象负责自己的逻辑,V 就不用操心这些细节了。
[b]到了 Web 时代,MVC 就变了[/b]
最关键的一点就是应程序被分成了浏览器端和服务端两部分。
对 MVC 的影响就是 V 现在也被分成了两部分,一部分放在浏览器端,一部分放在服务端。
还是那个输入框的例子,Web 应用处理起来有多种方式:
1、输入改变后,什么都不管,直到提交表单后,服务端对数据进行验证。如果验证失败,再重新构造整个用户界面,并且将输入框的属性改成红色。这个过程中,V 完全是被动的,仅仅是负责渲染 HTML 而已。
2、输入改变后,通过 Ajax 请求提交这个控件的数据,让服务端对数据进行验证。服务端返回特定的数据(例如一个 JSON)指示验证是否通过,V 里面的 JS 代码则根据返回结果更新输入框的颜色。
3、输入改变后,提交整个表单。然后服务端根据客户端的事件调用服务端的事件处理方法。事件处理方法中对控件数据进行验证,再更新控件的属性。最后服务端重新构造整个页面。
4、输入改变后,客户端的 JS 直接验证并更新控件状态。但整个表单提交到服务器后,还是得一一验证。
第一种是最传统的,也是最常用的。第二种则是 Ajax 兴起后流行的,不过现在大家也在反思这种模式是不是造成太多对服务器的请求。第三种则是 ASP.NET 的 POSTBACK 机制,JSF 我不了解,看介绍也是类似的机制。第四种实际上只是第一种的变形。
第一种模式:浏览器端完全不负责任何逻辑,仅仅是提供一个输入数据的接口,然后可以把数据提交到服务端。应用程序逻辑完全在服务端里面实现。
这种模式里面,V 实际上只是一个模板。服务端的 C 负责响应请求,然后验证浏览器端提交的数据,再调用 M 进行处理。最后根据处理结果,载入模板,渲染出 HTML 返回给浏览器。
第一种模式容易理解,而且实现简单,对浏览器端没有特殊要求,所以一直以来都是最主要的处理模式。但缺点就是用户体验不好,对用户操作不会有及时的反馈,而且没有办法根据用户操作实时的更新 UI。
第二种模式:浏览器端稍微主动点,会把 UI 事件报告给服务端。服务端处理后再反馈特定信息给浏览器端,浏览器端从而可以更新 UI。
比起第一种模式,第二种模式中的 V 会包含一些 JavaScript 代码。这些 JS 代码负责响应 UI 事件,然后把事件及相关的数据传递到服务端,并等待服务端的响应。服务端还是由 C 来处理,只不过处理完成之后仅仅是把处理结果返回给浏览器端(XML、JSON、字符串或者 JS 代码)。浏览器端的 JS 收到服务端的响应后,根据处理结果对 UI 做相应的更新。
这种模式的相对第一种来说有了不小的进步。用户操作可以马上得到反馈,而且可以构造非常动态的 UI。缺点则是需要编写大量的 JS 代码,服务端也要增加对应的响应代码。而且不同浏览器对 JS 的支持程度也不同,实践起来即使有各种 JS 框架的帮助也是比较繁琐的。
第三种模式:这种模式一个页面就是一个表单,JS 会去响应表单中的 UI 事件,然后根据事件类型决定是立即提交整个表单,还是稍后提交。
当表单提交后,服务端就不再是一个常规意义上的 C 来处理了,而是一个页面控制器(可以看作更复杂的 C)。页面控制器会根据定义,构造出整个页面的对象体系(例如 Page 对象中有两个 Textbox 对象)。构造好对象体系后,页面控制器会解析提交的表单中包含的事件。然后调用各个控件的事件响应方法。此时,就和桌面应用非常接近了。我们可以给各个控件编写事件响应代码,并在事件响应代码中调用 M 处理数据。当所有的事件都处理完成后,页面控制器会载入模板,然后传入对象体系,并通过复杂的渲染机制,将 UI 内容渲染出来返回给浏览器。
这种模式的好处很明显,那就是应用程序逻辑都在 V 里面完成。而且控件封装为了对象,控件的逻辑可以自己负责。缺点就是每次提交都是提交整个表单,数据量较大时明显会增加通讯量和延迟。而且早期的 Web 应用事件驱动模型会导致每次提交都重新构造整个页面,用户体验和效率也不怎么样。好在后来都有所改进,使用 Ajax 来提交表单和更新用户界面。
在上述三种模式中,我们会发现一个共同点:绝不在浏览器端实现应用程序逻辑。
因为浏览器端是不可控的运行环境,如果应用程序逻辑在浏览器端实现,那么安全性就得不到保证了。比如批量删除操作这个应用程序逻辑如果在浏览器端实现,用户完全可以自己分析浏览器端的代码和逻辑,直接构造一个表单来提交,从而绕过各种验证。
但是不在浏览器端实现应用程序逻辑,不代表不可以在 V 里面实现应用程序逻辑。要记住在 Web 应用中,V 是可以分成多个部分的,并且分别运行在服务端和浏览器端的,而且在服务端实现 V 也有很多种模式。实际上第三种模式就是典型,V 运行在服务端,并且完成所有的应用程序逻辑。
[b]在服务端 V 中实现应用程序逻辑[/b]
比如我们可以在模板中写入应用程序逻辑,然后模板引擎解析模板时,通过嵌入模板的 tag 来调用 M 获得要呈现的数据。这种方式已经非常非常多的实现了,实际应用时对于显示各种数据是很方便的。但是这种模式没办法主动响应用户操作。所以后来出现的事件驱动才是最完整(但不是最完善)的解决方案。
可惜由于 Web 应用的天然特性,事件驱动不管怎么改善,都绕不开将整个表单提交到服务器的过程。所以事件驱动在 Web 应用上一直都没有成为主流。