一直说C#是强类型语言通俗地讲便是指C#中的变量在开发时的类型便是明确的String便是StringInt就是Int毫无争议强类型的好处有很多张嘴便可随意举上几例
能够享受代码提示功能
能够获得重构工具的支持
能够在编译期发现更多错误
……
不过C#也不是绝对的强类型语言因为它也有弱类型那就是Object我们知道Object是所有类型的最终基类任何类型的对象都可以使用Object来引用可是一旦转化成Object的变量之后代码提示便消失了即使我们明确对象的确切类型也必须通过Cast才能使用——更何况它形成了一种被滥用或误用的机会例如一段错误代码可能会传入一个不符合约定类型的对象那么就会造成错误更严重的是这样的错误可能只要在运行时才能被发现编译器对此无能为力
类似的实践其实很多例如方法尽可能接受抽象的类型而返回具体的类型
ASPNET MVC中对于强类型一说最典型的方面便是视图在ASPNET MVC中每种视图(ViewPartialLayout)都可以选择弱类型和强类型两种基类两者的区别便是Model的类型强类型视图的唯一区别便是其Model属性为范型参数所指定的类型——而弱类型则自然就是Object了在这里我提出一个最佳实践总是使用强类型的视图并且所有数据都从Model中获取这么做可能会在一定程度上增加了代码量因为我们需要为每个视图建立一个Model不过我认为这是值得的在强类型的视图中VS中能够对Model的各种成员做出丰富的代码提示我们便可以快速地输入代码并确保不会出现拼写之类的低级错误如果使用了ViewData这个弱类型的字典那么每次我们获取某个值之后都必须将其Cast成具体的类型才能使用这无疑使视图模板变得复杂而难以维护
其实以上的准则还有另一个层面的内容使用项目模板安装了ASPNET MVC之后在~/Views/Shared/LogOnUserControlascx文件中可以发现对PageUser属性的直接访问这自然可以运行但是其带来的后果是弱化了aspx/ascx/master文件的模板概念而重新让其意识到我是一张页面在ASPNET MVC框架中我们要时刻记着项目中没有页面我们始终使用Controller中的Action方法来处理请求而每个请求需要对应一个磁盘上物理文件的思维方式一定需要改变(无论是在用WebForms模型还是MVC模型)直接访问PageUser属性也使我们很难为视图进行独立的单元测试因为视图与HttpContext直接耦合而HttpContext的Mock相当困难
在《最佳实践》的示例中我为每个视图都构建了一个Model它们都在MyMvcDemoWebUIModels项目中值得一提的是由于业务的需要视图之间很可能出现数据共享例如View和Layout(更ASPNET的说法是Page与Master Page)之间共享同一个Model更进一步两者之间其实是多对多的关系如果我们为每个ViewPage定义了强类型的Model又如何应对同一个ViewPage套用不同ViewMasterPage的情况呢?这个问题最自然的解决方式便是使用接口一个Model对象不可以有多个父类但是完全可以实现多个接口因此我们往往为强类型的ViewMasterPage指定一个接口作为其泛型参数从示例中您也可以发现SiteMaster的类型为ViewMasterPage<ISiteMasterModel>而每个ViewPage的Model类型都实现了ISiteMasterModel接口
使用ViewData的另一个坏处是必须使用字符串作为键进行访问字符串是什么?是常量分散在各处的常量是维护性的大敌而使用ViewData则几乎无可避免地将字符串常量分散在控制器和视图两个地方——您可能会觉得使用枚举不就解决这个问题了吗?如果这么做相当于为每个视图定义不同的枚举类型那么我们为什么不直接构造强类型的Model呢?使用字符串作为键的另一个坏处是无法在编译期发现问题如果您一不小心拼写错误了怎么办呢?您可能会觉得原本aspx就必须到运行时才会动态编译不过现在ASPNET MVC的模板已经增加了相关的MSBuild Task或者使用ASPNET的预编译功能都可以在编译时对视图进行检查了这也是我建议大家使用aspx而不是另一种DSL来构建视图的原因——aspx的周边支持实在是太丰富了
不过在视图中字符串常量并非只出现在ViewData的访问上例如使用ASPNET MVC框架模板创建项目之后SiteMaster中生成导航栏链接的代码是这样的
<ul id=menu>
<li><%= HtmlActionLink(Home Index Home)%></li>
<li><%= HtmlActionLink(About About Home)%></li></ul>
代码使用了HtmlActionLink这个辅助方法生成一个导向至某个Action的链接只可惜这里又出现了字符串参数表示的Controller名和Action名事实上如果您下载了ASPNET MVC RC的源代码之后能够发现其中的MvcFutures项目中包含了强类型的ActionLink辅助方法于是我们的代码就能修改为如下模样(参见《最佳实践》的示例)
<li><%= HtmlActionLink<HomeController>(c => cIndex() Home) %></li>
<li><%= HtmlActionLink<HomeController>(c => cAbout() About) %></li><li><%= HtmlActionLink<AccountController>(c => cRegister() Register) %></li><li><%= HtmlActionLink<AccountController>(c => cList() Admin) %></li>
这下我们便可以使用Lambda Expression这个强类型的表示方法来告诉ActionLink方法究竟该生成导向至哪个Action方法的链接甚至我们可以在调用Action方法的同时指定其参数——例如最后一行代码指定将查看第一页内容而这些数据在交给URL Routing的配置后便能得到我们想要的URLMvcFutures中还定义了其他类似的辅助方法例如UrlHelper中的辅助方法则会直接生成一个URL链接——而不是一个元素可惜目前MvcFutures中的这些辅助方法编写的并不完全正确因为它直接把方法名作为Action的名称来使用而ASPNET MVC从某个预览版开始引入了ActionNameAttribute可以为一个Action方法指定一个不同的Action名称因此如果您使用ActionNameAttribute来改变Action名则很可能您需要构建自己的辅助方法不过这条准则依旧成立使用强类型的表示法生成URL地址
如果您要使用MvcFutures中的内容则必须自行编译MvcFutures项目有一点值得注意如果您使用直接编译ASPNET MVC RC解决方案所得到的MicrosoftWebMvcdll就会发现它依赖的是SystemWebMvc Version= Culture=neutral PublicKeyToken=null而您开发时所使用的也就是安装在系统中的程序集是SystemWebMvc Version= Culture=neutral PublicKeyToken=BFADE两者不同自然难以兼容幸运的是您只要手动修改一下MvcFutures项目的程序集引用就可以了对您来说这一定不是问题
看到这里不知道您是否发现了一个比较严重的问题其严重程度大大降低了这些负责生成URL的辅助方法的可用性这是个什么问题呢?又该如何解决呢?我在这里先卖个关子下个星期我将公布这个问题以及它的解决方法——这部分内容并没有包含在上次《最佳实践》的讲座中算是一个保留节目吧