阅读说明书 1、本文将要说到的MVC和Web Form如果没有特殊说明,则表示为ASP.NET 框架下的ASP.NET MVC CTP和 Web Forms 的最基本形态和特征,不考虑特殊应用。 2、本文重点在介绍MVC,以及与MVC对应(非对立)的Web Form的一些情况,绝没有“贬低”任何一种技术的用意,哪怕是ASP.NET以外的。我题目写的是“正名”,希望能客观分析各自有缺点。而并不是“取舍”。 3、本文按照一些朋友的建议,将会罗列一些技术介绍,但是请不要认为介绍了MVC的特点就一定是针对Web Form的不足。 4、这篇文章有很多个人的经验和感受,但是不会有太多个人感情色彩的技术偏向,所以请不要把这篇文章的内容定位在单纯拥护或者反对哪一方面。并且对于这两种架构的不足,希望大家能有清醒的认识和客观的评价。 5、本文作者对于MVC和Web Form(尤其是前者)都是处于不断学习探索阶段,即使指出它们客观存在的不足,也不代表“指责”任何一个。 6、我十分希望我们可以在轻松的氛围中展开深入的交流,所以请大家如果要发言,务必做到以下三点:一、通篇阅读并理解,不要断章取义;二、由于主题是技术讨论,所以无关此主题技术的闲杂话题请不要在这里发表,以免误导别的读者或干扰讨论氛围;三、大家各抒己见不是什么坏事,但请不要随意对别人发表的回复“捣浆糊”(也称“和稀泥”),以免走题。 7、总之,为了技术而讨论,我尊重每一位朋友的题内观点和建议。也欢迎大家拍砖。 8、理解并同意以上说明条款之后,您可以继续往下阅读,否则请多留给别人一些讨论的空间,谢谢。 |
一、对于了解、学习MVC的一些建议
如果大家想大致了解MVC的现状和为什么在Web Form之后还要推出MVC等等一些问题,可以参考以下文章:
WHY?
为什么会出现ASP.NET平台下的MVC框架?(一看还是老赵翻译的,放第一个^_^)
个人的一点思考:
通过这篇文章,我们可以大致了解MVC的作用,以及一小部分表面的工作原理。老赵做了比较详细的总结,如果你觉得还不够,建议再看一下英文原文中老赵省略的内容。
其中讲到:
The cryptic control ids and sensitivities of ViewState become a serious problem for so-called Web 2.0 sites. The only framework that does not have serious ViewState corruption issues is ASP.NET AJAX. But it is an immature library.
老赵对于这段文字有这么一段评价:
(译者注:事实上,如果您使用了ASP.NET AJAX中的UpdatePanel控件,在客户端与服务器端交互的过程中依旧需要传递页面上所有的ViewState。另外,2006年最佳个人定制页面网站www.pageflakes.com,也是基于当时的ASP.NET AJAX框架开发的,并且集成到.NET Framework 3.5中的ASP.NET AJAX又在各方面有了更进一步的改进,如果武断地称之为一个不成熟的类库未免有失偏颇)。
老赵提到了集成到.NET Framework 3.5中的ASP.NET AJAX,以及作者提到的ViewState,这里先打个伏笔,下文我会继续说到。ASP.NET AJAX作为Web Form发展过程中AJAX技术应用的产物,凭借其与Web From的“巧妙配合”(即使在一些时候用UpdatePanel去装载服务器控件反而有些拆东墙补西墙的感觉),如今终于被.NET3.5“名门正娶”,且不论集成的是不是时候,至少对于AJAX 在Web Form上面的应用算是一个里程碑。并且,对于MVC的AJAX的开发也提供不少的便利(包括如果以后还有别的ASP.NET架构推出)。
HOW?
因为这里只讲ASP.NET MVC(CTP),所以要知道MVC如何工作,这里最好就是引用Scott关于MVC的一些操作实例和解释:
ASP.NET MVC Framework (Part 0): What is it?
ASP.NET MVC Framework (Part 1)
ASP.NET MVC Framework (Part 2): URL Routing
ASP.NET MVC Framework (Part 3): Passing ViewData from Controllers to Views
ASP.NET MVC Framework (Part 4): Handling Form Edit and Post Scenarios
Scott Hanselman's ASP.NET MVC First Look Screencast
TDD and Dependency Injection with the ASP.NET MVC Framework
Writing Unit Tests for Controller Actions
如果大家喜欢看中文的话这儿有Scott博客的翻译:http://blog.joycode.com/scottgu/
由于上面的文章篇幅比较长,具体的内容我在这里就不重复。关于使用过程中,上面文章中没有周全的、需要特别注意的要点、其他文章谈到的一些问题以及我前一个多星期总结出有一些问题,拿出来与大家共同探讨。(有些MVC目前的缺点我在之前的文章中也总结过,大家可以作为参考:使用微软ASP.NET MVC Framework的一些感受 + 收集园子朋友发现的bug反馈以及使用微软ASP.NET MVC Framework的一些感受 + 收集园子朋友发现的bug反馈【补充】以及MVC Toolkit 部分已发现bug的根治方案 Part(1))。
如果有疏漏或者错谬的地方,欢迎大家指出。
首先来说一下MVC的特点(或者说不同之处)到底在哪?是在开发(维护)还是在应用?如果两者都有,最主要的是哪一方面。
关于开发。
ASP.NET MVC(CTP)结构图:
很多人“吹捧”MVC能如何提高开发效率,我觉得是曲解了”MVC”架构的本质并且对Web Form的认识有点不足。事实上MVC的开发远没有很多人想象的那么“轻松”,他确实是”M-V-C”的“简单”组合,但是在开发的时候,你会遇到很多你在Web Form中不太容易“享受”到的“苦恼”(当然这些苦恼多半出于思维、对开发对象的认识以及编程习惯)。我先从一个浏览者操作的角度,简要说明一下MVC的C-V是怎么工作的,然后谈对应的开发的“痛并快乐着”(M层涉及的内容较多,Scott也已经做了比较多的说明):
最普通的Web Form情况下当你打开浏览器的时候,你会通常进入的是一个类似这样的地址:http://www.cnblogs.com/,这时候,IIS的默认首页设置会把你带到比如这么一个页面:/Default.aspx,这个/Default.aspx是现实存在的,并且你是可以通过修改文件名直接访问对应存在的网页的。而在MVC模式下,你同样进入这样一个地址:http://MVC.cnblogs.com/,在新建好的ASP.NET MVC Application默认状态下,服务器首先找到的是这个路径:/下面的这个文件文件:/Default.aspx,但是你看到的内容却不是/Default.aspx,而是/Views/Home/Index.aspx。默认情况下/Default.aspx内容为空,只是起到URL识别作用,并且这个文件的内容不被实际访问,在Default.aspx中默认有这么一行说明:
这个文件的存在只是为了让IIS访问网站根目录/时,自动转向指定网页(我这里先说是网页,过会会说到,其实并非直接转向“网页”)。
让我们来看一下是怎么从/Default.aspx到/Views/Home/Index.aspx的。这个工作由C-V层协作完成,我们还要看一个十分重要的文件:/Global.asax.cs。打开之后我们可以看到这样一段代码:
{
Url = "Default.aspx",
Defaults = new { controller = "Home", action = "Index", id = (string)null },
RouteHandler = typeof(MvcRouteHandler)
});
Url = "Default.aspx",表示了一个URL传入条件(格式),即当传入文件名为/Default.aspx时。
Defaults = new { controller = "Home", action = "Index", id = (string)null }中,Defaults表示了当URL符合条件时,采取对应的操作,controller是C层的一个控制模块,“Home”对应了C层的HomeController类,其对应到的文件夹是V层中/Views/Home文件夹,这些对应都是自动完成的,你可以把这个”Home”间接理解为映射到/Views/Home/文件夹下面,但并不是直接访问,而是通过了C层的HomeController。Action字段负责传入需要在controller对应模块下,执行的操作命令,我们打开/Controller/HomeController.cs可以看到这一段:
{
[ControllerAction]
public void Index()
{
RenderView("Index");
}
[ControllerAction]
public void About()
{
RenderView("About");
}
}
这里我们看到了public void Index ()这段代码,而Index就是上面/Global.asax.cs 中Index对应的“Index”。也是自动对应的。我们继续看这个Index()做了些什么:
RenderView的第一个参数表示需要指向的页面,”Index”自动对应于Index.aspx文件,而我们刚才说了,这个HomeController是自动对应到/Views/Home文件夹的,那么整个Defaults = new { controller = "Home", action = "Index", id = (string)null },我们就可以理解为:在HomeController内,运行Index(),而RenderView("Index")又将返回的网页指向Index.aspx,由于HomeController对应了/Views/Home文件夹,所以整个返回的结果就是:/Views/Home/Index.aspx。其中的id = (string)null一般都要涉及到数据操作(和M层有关),我们这里先不谈。
现在V和C之间如何对应连接起来,的我们应该已经了解了大致方法了,并且所有它们之间的数据传递等操作,都是以这个为基础的。
其中有一点一定要注意:不能把controller = "Home", action = "Index"直接理解为指向/Views/Home文件夹下面的Index.aspx文件,Index.aspx文件是由HomeController下面的Index()指定的,如果Index()指定的是RenderView("others.aspx”),那么controller = "Home", action = "Index"就会返回/Views/Home下面的others.aspx。
马上来总结一下这个过程相对于Web Form开发来说复杂在了什么地方:
首先直观的我们就能看到他会增加许多原本Web Form不会产生的代码文件(我们刚才还没涉及到Model层),可以说Controller中,几乎每一个Views中的文件夹,都需要我们去建立对应的xxxController.cs(/Views/Shares文件夹一般不需要)。并且这些工作相对来说是“额外”的——Web Form中你什么都不用做,就能直接访问/Views/Home/Index.aspx。(默认MVC方案建好之后我列举的这些代码会是为你自动生成的,但是你要继续开发,就必须自己增加了)。当然,这样做也是极具意义的,它让View层和Controller层分离开来,当你访问一个URL的时候,你其实不是在直接访问文件,而是在访问一个方法,由这个方法去确定到底返回哪个页面(也就是说在用一个不变的URL下面,根据不同的条件,可以返回不同的页面,这和Response.Redirect()有本质区别,光就表面效果看起来有点像整页的Excute())。可能你还发现了,所有的调用过程中,几乎都是用了字符串形式,这不论对于面向对象的开发,还是日后维护,都是很不利的(这不是MVC构架本身的原因,而是我觉得MVC CTP有待改进的一个地方)。我认为完全可以把aspx页面的类名比如Views.Home.Index作为一个对象传入,哪怕用一个类似.ToString()的功能自动获取(只是类似返回一个string的效果,让系统可以在编译时检查就行),这样开发效率和维护效率都会提高很多。不知以后的MVC在这方面是否会有改进。
很多人把Web Form的一些codebehind放到.aspx中,或者想办法避免PostBack之类的操作,并且在.aspx中使用服务器控件一同去完成,这样的方法能不能算MVC的思路呢?我觉得有点像,但我觉得并不理想(哪怕目前的MVC所用的方法我也这么认为)。原因很简单,因为这并没有完全使V和C更多地分离开来(至少还有更好的方法),换句话说,这种情况下,V并不是一个纯粹的“模板”(即便MVC本身并没有要求非要这样)。目前MVC使用这样的形势来创建一个TextBox(input:text):
我们且不论"PlateName"这样的指定形势是否妥当(实际上在这里我们也不是要苛求,我只是举例,因为目前看来总有一个地方需要这么使用)。后面的ViewData.PlateOne.PlateName我来解释一下:
ViewData是通过C层实例化M层中对应数据模板,并通过上面提到的RenderView()传入页面(ViewPage)的一个<TViewData>(具体内容在M层中指定),我们看到的PlateOne就是在PlateViewDatan内的一个实体(M层中用ORM自动创建),PlateName是该实体下的一个“属性”,可以间接理解为对应数据库中的一个字段(但现在已经面向对象,所以不能完全等同起来,取值相等而已)。
为什么我说这不是纯粹的“模板”呢?因为他至少还需要在执行美工的时候去关注这些实体的属性,这样使得原本分离开来的V-C似乎又“藕断丝连”。更“模板”化的做法,当然是用一个内部标准的字符串替代,比如”{$PlateName}”,然后在最后对应M层的模板,使用Replace等等手段,把这些标记替换成实体中的属性值。当然这样的方法可能需要使用到“庞大”的字典,但对于追求MVC本身价值的角度来说,这是不过分的,况且这样的做法在Web Form 就早已经被使用。
关于维护
如同MVC的开发一样,MVC的维护是必要使程序员面对更多的代码文件。不对,纠正一下,这里应该说“代码段”更好,而不一定是“代码文件”,甚至是比之Web Form可能是更少的“代码文件”。
因为MVC中,程序的维护主要集中在C和M两层上,前面已经介绍,C层文件我们可以理解为对/Views/中指定文件夹的操作(并不绝对),一个网站V层(即可通过Controller访问)文件夹的个数一般都比.aspx.cs文件少是毋庸置疑的。而M层的代码文件,又与数据源有关,通常平均下来,一个SQL数据库表,对应一到两个.cs文件(另外也会涉及到一些你自己编写的实体。或者当然你也可以分离出很多,而一般不需要。)。
从这点上来说,分工更加明确了:需要执行逻辑的,找C层对应文件(或者说类/板块),需要对模板和一些数据处理的具体事务进行维护的,找M层具体文件,如上面提到的PlateViewData以及对应实体。如此可以看出MVC结构在开发“繁琐”的表面上,带来的是更加清晰的思路和逻辑处理板块。
如果能够达到我刚才说的“V层理想的模板”状态,那么这个维护的便捷性就更不止体现在单个程序逻辑上面,并且M/V/C各板块的同时工作将更小限度地受到相互间的干扰。
与之对应,还是有一些需要克服的困难,比如“代码文件”的减少,并不能掩盖大量代码段的出现,一个Controller.cs中可能会出现若干个类似Index()的方法,这也确实是一个让人头疼的问题,所以如何协调整理和高效管理这些方法,可能将成为提高对应模块维护效率的一个重点。
关于应用
这里所说的应用不是单指终端客户的使用,也包括我们在开发过程中的一些体验和解决问题的思路。
由于MVC(CTP)的应用似乎还没有走上正轨,我本人也还没有完成系统的开发(目前我对MVC也是观望和期待更多一点,所以也不敢“豁出去”),我们就先找一些在Web From下面的例子,谈谈是不是在某些方面,MVC可以起到更好的效果。
前一篇文章中,我提到了作为大学毕业设计而开发的一个ACDS系统(只是毕业设计中的一个组成部分,大约占1/4),似乎引来了许多疑问,我也觉得这样模糊的解释不太具有说服力(引发了一些人的胡乱猜测,在此赔罪),现在我把它其中的一个最基层的功能——“建表”给大家演示一下,然后从中讨论一下如果使用MVC,会给这个系统带来些什么影响(这个系统原本使用的是ASP.NET2.0,Web Form开发)。
当然这里还有一点要强调一下,这个系统的开发(整个物流〈信息〉系统,不光指ACDS),是我对MVC更加期待的一个催化剂,我本人并没有舍弃Web Form的意思,并且认为Web Form是完全可以优秀完成的,毕竟这个系统也是用Web Form开发的,并且我比较满意。我只是在寻求一种针对类似开发需求,更加高效的处理放案(微观上,非直观上)。并且必须说明,这个系统的开发不能完全代表所有的企业级应用开发,只是展示冰山一角的应用,希望可以抛砖引玉,而不要引起大家的误解。
我录了一段演示录像,大家可以下载看一下:
ACDS系统中"建表"环节演示+粗略分析 [下载]
(我所要演示的重点不是在功能或者实现方法上,而是数据流程以及一些事务处理上)
另外录像里面有两句(至少我自己感觉到的)口误,在此纠正:
1、一个地方我说了PostBack返回的是“对象,或者说对象的值”,这里的“值”说成“属性”更恰当。
2、有个地方我说成了TextBox.MVC,应该是TextBox.Text。
由于时间仓促,没有太多准备,也来不及做更多论述。如果大家对视频里面我说的有疑问,可以单独列出,我们展开探讨。
视频中的一张对于ACDS(建表环节/PostBack环节)的简易图
先写到这里吧,希望大家在一个融洽的氛围中进行交流,也期待各位拍砖。
PS:算一下貌似这几天没太多时间继续写了一_一!先列一下吧:
二、MVC开发模式=ASP开发模式?
三、更期待MVC哪些方面可以有更多改进
祝大家圣诞节愉快!^_^