从零开始学习ASP

时间:2024.4.20

从零开始学习ASP.NET MVC 1.0(收藏)

从零开始学习ASP.NET MVC 1.0

从零开始学习ASP.NET MVC 1.0 (一) 开天辟地入门篇

《从零开始学习ASP.NET MVC 1.0》 文章导航

(一) 开天辟地入门篇

(二) 识别URL的Routing组件

(三) Controller/Action 深入解析与应用实例

(四) View/Model 全解

(五) ViewEngine 深入解析与应用实例

一.摘要

随着ASP.NET MVC 1.0版本的正式发布, 我将本系列文章也更新到了1.0, 对于已经发表的文章我都会根据1.0版本重新编辑. 希望本系列文章能打给大家帮助.

二.前言

ASP.NET MVC是微软官方提供的开源MVC框架. 在经过了漫长的preview和2个RC版本后, 终于发布了1.0版本.其实从RC开始ASP.NET MVC的核心代码就已经趋于稳定.所以现在开始学习ASP.NET MVC是一个很好的时间点.学习的早,而且不用担心知识过时.

本系列教程是我在学习ASP.NET MVC时的总结, 比如虽然我学习老赵的视频教程, 但是因为版本较老, 一般我都要从最新版本上进行知识的校验与升华, 然后将最后的精华总结出来写成文字. 现在还在不断的学习, 不断地写作.希望大家喜欢本系列文章, 我将用精益求精的精神写本系列文章.

三.Hello ASP.NET MVC

现在开始跟着我一步一步学习ASP.NET MVC.在什么都不知道之前, 还是让我们先下载一个ASP.NET MVC程序. 当看到程序运行后, 我的心才会踏实很多.

首先去ASP.NET MVC的官网看看:

/mvc/

这里我们可以下载ASP.NET MVC的安装文件.目前最新版本是1.0,另外我们可以在CodePlex上获取源代码:

/aspnet

在官网上下载ASP.NET MVC安装文件, 一路回车完成安装. 启动VS2008 SP1(不是VS2008?不是SP1?作为一个专业的IT人士,水平可以菜,软件咱一定要用新的!去找个新版本吧!),点击新建,在Web中可以找到:

点击确定即创建了一个默认的MVC项目.同时还会创建一个测试项目. 这些我现在都不懂也不关注, 直接将Web项目中的default.aspx设置为启动页, 运行项目, 一个ASP.NET MVC的项目已经运行在我的电脑上了:

而且那个Home和About还能点击!虽然现在我什么都不懂, 不过看着能运行的实例, 心里就踏实多了!

四.学习MVC的概念

一个ASP.NET页面通常需要做这些事情:

获取一个页面需要的数据.

在页面的Page_Load(页面加载)方法中为我们的页面控件绑定数据

浏览器显示页面

MVC即Model, View, Controller

Model就是我们1中获取的网页需要的数据

Controller就是我们获取数据,然后将数据绑定到页面控件的这个业务流程.不十分正确但是可以先这样理解: Controller就是我们的Page_Load方法逻辑.

View就是我们的aspx页面,注意这是一个不包含后台代码文件的aspx页面.(其实带有.asp.cs文件也不会有编译错误,但是这样写代码就违背了MVC的设计初衷)

下面这张图很好的概括了MVC的作用:

一个URL请求, ASP.NET MVC引擎会分析这个URL要使用那个Controller, 这个Controller(实际上真正的方法是Controller的Action)从数据库或者其他数据源获取数据,通常这些数据是一个业务的模型类(即MVC中的Model). Controller将Model对象传递给页面(即MVC中的View), 页面显示在浏览器上.(这一步是ViewEngine的功能, 我们一般的ASPX页面使用的是WebForm的ViewEngine,当然也可以替换.)

五.学习实例首页

简单的概念也有了.实例也能运行了.现在就是看看这个实例是如何使用ASP.NET MVC的.从首页下手.

1.寻找入口方法

首页网站的地址应该是 http://localhost:1847/home/index (1847是端口号,随机生成,所以会不同), 为什么地址不是具体的页面但是最后却将请求提交给了view/home/Index.aspx 页面? 很明显是使用了URL重写. ASP.NET中叫做UrlRouting,对应的程序集是System.Web.Routing, 打开项目的Global.asax.cs文件, 会找到我们建立的页面重写规则:

public static void RegisterRoutes(RouteCollection routes)

{

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(

"Default", // Route name

"{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" } // Parameter defaults

);

}

protected void Application_Start()

{

RegisterRoutes(RouteTable.Routes);

}

关于Routing的深入了解将在以后介绍, 这里我们完全可以照葫芦画瓢, 按照实例添加新的重写规则.

最后, http://localhost:1847/home/index 就被解析为:

Controller为Home, Action为Index, 没有id参数.

2.寻找Controller

在Controllers文件夹下我们可以找到HomeController.cs, 这里使用了一个约定, 就是如果URL中获取到的

Controller名字是Home, 则他的Controller类名就是HomeController. 在URL中的名字后加上”Controller”.

实例中Controller都放在Controllers文件夹, 所以我们可以按照命名约定很容易就可以找到HomeController类

3.寻扎Action

打开HomeControllers.cs, 发现里面有两个方法:

public ActionResult Index()

{

ViewData["Message"] = "Welcome to ASP.NET MVC!";

return View();

}

public ActionResult About()

{

return View();

}

其中的Index和About都是Action.这个Action是个什么东东呢?目前我只知道一个Controller可以包含多个Action, MVC模式中Controller角色的具体实现逻辑都是在Action中的.

因为我们的Action是Index, 所以自然就要调用Index()方法.这里将"Webcome to ASP.NET MVC!", 这里的Model角色就是这句话, 也就是一个字符串对象.

Controller的一个重要作用就是将Model对象传递给View,也就是具体的页面. 传递的方法就是将这个字符串放到ViewData这个集合对象中, 然后在页面上也调用这个对象获取数据. 这个ViewData 对象一定是静态的, 或者至少是和HttpContext对象关联的, 否则到了View页面上是不能够调用的.但是也不知道ASP.NET MVC是在什么时候创建了这个对象.有心深入的一会去源代码里找找就能找到答案了.

4.寻找View

Model有了,数据有了, 接下来要跳转到View去显示数据了.第一个问题就是如何从Controller中跳到View上? return View() 这句话实现了此功能.其实如果这样写大家会更清楚:

public ActionResult Index()

{

ViewData["Message"] = "Welcome to ASP.NET MVC!";

return View("index");

}

View方法中可以带一个名字, 这个名字就是View的名字.如果把index改成about,那么访问/home/index就会跳转到about页!

虽然知道了我们要返回到名称是"index"的View上, 但是这个View的页面在哪里呢?在Web中有一个Views文件夹:

这里面存放的都是View对象, 也就是只有显示功能的aspx页面文件.但是aspx文件要遵循约定: Views下面要按照Controller创建文件夹, 比如HomeController就对应Home文件夹, 然后在里面添加view, 比如index.aspx, 那么在HomeController中返回到名为Index的View对象实际就会返回Views/Home/Index.aspx页面.

如果不写View的名字, 则认为Action的名称就是View的名称.

5.页面展示

最后的工作就是View页面使用Model数据完整页面显示工作, 在index.aspx这个没有后台代码的View对象中, 通过ViewData对象获取Model:

<%= Html.Encode(ViewData["Message"]) %>

接下来ViewEngine即页面引擎会将aspx中的HTML部分以及上面的数据部分和在一起返回给浏览器.

关于View对象我注意到此页面是继承自System.Web.Mvc.ViewPage而不是直接继承自System.Web.UI.Page, 而这个ViewData对象就是ViewPage中的一个属性. 这里的ViewData一定是页面级别的,当页面编译完毕这个对象就会被注销(HTTP是无状态的协议,每次请求其实都是生成一个新的ViewPage对象).

六.总结

虽然仅仅是第一篇入门文章, 但是是不是觉得已经会用ASP.NET MVC了? 虽然学习不可以骄傲, 但是可以自豪的是ASP.NET MVC我已经不再陌生, 即使不能驾轻就熟, 偶尔说出来几个概念,给你解释解释啥是MVC, 还是很能吓唬人的. 在后续文章中,我将对MVC的各种细节做具体的讲解.希望大家喜欢本系列文章!

从零开始学习 ASP.NET MVC 1.0 (二) 识别URL的Routing组件

《从零开始学习ASP.NET MVC 1.0》 文章导航

(一) 开天辟地入门篇

(二) 识别URL的Routing组件

(三) Controller/Action 深入解析与应用实例

(四) View/Model 全解

(五) ViewEngine 深入解析与应用实例

一.摘要

本篇文章从基础到深入的介绍ASP.NET MVC中的Routing组件. Routing翻译过来是"路由选择", 负责ASP.NET MVC的第一个工作:识别URL, 将一个Url请求"路由"给Controller.

二.承上启下

第一篇文章中我们已经学会了如何使用ASP.NET MVC, 虽然其中还有很多的细节没有深入了解, 但是对基本的处理流程已经有了认识:来了一个Url请求, 从中找到Controller和Action的值, 将请求传递给Controller处理. Controller获取Model数据对象, 并且将Model传递给View, 最后View负责呈现页面.

而Routing的作用就是负责分析Url, 从Url中识别参数, 如图:

这一讲就让我们细致的了解System.Web.Routing及其相关的扩展知识.

三.Routing的作用

第一讲中实例的首页地址是: localhost/home/index

我们发现访问上面的地址, 最后会传递给 HomeController中名为index的action(即HomeController类中的index方法).

当然服务器端不会自己去实现这个功能, 关键点就是在Global.asax.cs文件中的下列代码:

public static void RegisterRoutes(RouteCollection routes)

{

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(

"Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" } // Parameter defaults

);

}

protected void Application_Start()

{

RegisterRoutes(RouteTable.Routes);

}

回来看我们的Url: localhost/home/index

localhost是域名, 所以首先要去掉域名部分: home/index

对应了上面代码中的这种URL结构: {controller}/{action}/{id}

因为我们建立了这种Url结构的识别规则, 所以能够识别出 Controller是home, action是index, id没有则为默认值"".

这就是Routing的第一个作用:

1.从Url中识别出数据.比如controller,action和各种参数.

如果跟踪程序, 接下来我们会跳转到HomeController中的Index()方法. 这是Routing内部为实现的第二个作用:

2.根据识别出来的数据, 将请求传递给Controller和Action.

但从实例中我们并不知道Routing如何做的这部份工作.第五部分我做了深入讲解.

四.Routing的使用

在分析Routing的实现原理前, 先学习如何使用Routing为ASP.NET MVC程序添加路由规则.

1. 使用MapRoute()方法.

这是最简单的为ASP.NET MVC添加识别规则的方法.此方法有如下重载:

MapRoute( string name, string url);

MapRoute( string name, string url, object defaults);

MapRoute( string name, string url, string[] namespaces);

MapRoute( string name, string url, object defaults, object constraints);

MapRoute( string name, string url, object defaults, string[] namespaces);

MapRoute( string name, string url, object defaults, object constraints, string[] namespaces);

name参数:

规则名称, 可以随意起名.当时不可以重名,否则会发生错误:

路由集合中已经存在名为“Default”的路由。路由名必须是唯一的。

url参数:

url获取数据的规则, 这里不是正则表达式, 将要识别的参数括起来即可, 比如: {controller}/{action}

最少只需要传递name和url参数就可以建立一条Routing(路由)规则.比如实例中的规则完全可以改为:

routes.MapRoute(

"Default",

"{controller}/{action}");

defaults参数:

url参数的默认值.如果一个url只有controller: localhost/home/

而且我们只建立了一条url获取数据规则: {controller}/{action}

那么这时就会为action参数设置defaults参数中规定的默认值. defaults参数是Object类型,所以可以传递一个匿名类型来初始化默认值:

new { controller = "Home", action = "Index" }

实例中使用的是三个参数的MapRoute方法:

routes.MapRoute(

"Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" } // Parameter defaults

);

constraints参数:

用来限定每个参数的规则或Http请求的类型.constraints属性是一个RouteValueDictionary对象,也就是一个字典表, 但是这个字典表的值可以有两种:

用于定义正则表达式的字符串。正则表达式不区分大小写。

一个用于实现 IRouteConstraint 接口且包含 Match 方法的对象。

通过使用正则表达式可以规定参数格式,比如controller参数只能为4位数字:

new { controller = @"\d{4}"}

通过第IRouteConstraint 接口目前可以限制请求的类型.因为System.Web.Routing中提供了HttpMethodConstraint类, 这个类实现了IRouteConstraint 接口. 我们可以通过为RouteValueDictionary字典对象添加键为"httpMethod", 值为一个HttpMethodConstraint对象来为路由规则添加HTTP 谓词的限制, 比如限制一条路由规则只能处理GET请求:

httpMethod = new HttpMethodConstraint( "GET", "POST" )

完整的代码如下:

routes.MapRoute(

"Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" }, // Parameter defaults

new { controller = @"\d{4}" , httpMethod = new HttpMethodConstraint( "GET", "POST" ) } );

当然我们也可以在外部先创建一个RouteValueDictionary对象在作为MapRoute的参数传入, 这只是语法问题.

namespaces参数:

此参数对应Route.DataTokens属性. 官方的解释是:

获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值。

我目前不知道如何使用. 请高手指点

2.MapRoute方法实例

下面通过实例来应用MapRoute方法. 对于一个网站,为了SEO友好,一个网址的URL层次不要超过三层:

localhost/{频道}/{具体网页}

其中域名第一层, 频道第二层, 那么最后的网页就只剩下最后一层了. 如果使用默认实例中的"{controller}/{action}/{其他参数}"的形式会影响网站的SEO.

假设我们的网站结构如下:

下面以酒店频道为例, 是我创建的Routing规则:

public static void RegisterRoutes(RouteCollection routes)

{

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

#region 酒店频道部分

// hotels/list-beijing-100,200-3

routes.MapRoute(

"酒店列表页",

"hotels/{action}-{city}-{price}-{star}",

new { controller = "Hotel", action = "list", city = "beijing", price="-1,-1", star="-1" }, new { city=@"[a-zA-Z]*",price=@"(\d)+\,(\d)+", star="[-1-5]"}

);

//hotels/所有匹配

routes.MapRoute(

"酒店首页",

"hotels/{*values}",

new { controller = "Hotel", action = "default", hotelid = "" }

);

#endregion

//网站首页.

routes.MapRoute(

"网站首页",

"{*values}",

new { controller = "Home", action = "index"}

);

}

实现的功能:

(1)访问 localhost/hotels/list-beijing-100,200-3 会访问酒店频道的列表页,并传入查询参数

(2)访问 localhost/hotels 下面的任何其他页面地址, 都会跳转到酒店首页.

(3)访问 localhost 下面的任何地址, 如果未匹配上面2条, 则跳转到首页.

简单总结:

(1)Routing规则有顺序(按照添加是的顺序), 如果一个url匹配了多个Routing规则, 则按照第一个匹配的Routing规则执行.

(2)由于上面的规则, 要将具体频道的具体页面放在最上方, 将频道首页 和 网站首页 放在最下方.

(3) {*values} 表示后面可以使任意的格式.

3.使用Route类

MapRoute方法虽然简单, 但是他是本质也是通过创建Route类的实例, 为RouteCollection集合添加成员.

下载最新版本的MSDN-Visual Studio 20008 SP1, 已经可以找到Route类的说明.

创建一个Route类实例,最关键的是为以下几个属性赋值:

属性名称 说明 举例

Constraints 获取或设置为 URL 参数指定有效值的表达式的词典。 {controller}/{action}/{id}

DataTokens 获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值。 new RouteValueDictionary { { "format", "short" } }

Defaults 获取或设置要在 URL 不包含所有参数时使用的值。 new { controller = "Home", action = "Index", id = "" }

RouteHandler 获取或设置处理路由请求的对象。 new MvcRouteHandler()

Url 获取或设置路由的 URL 模式。 new { controller = @"[^\.]*" }

这些属性除了RouteHandler以外, 其他的都对应MapRoute方法的参数.RouteHandler是实现了IRouteHandler接口的对象.关于此接口的作用在第五部分Routing深入解析中做讲解.

五.Routing深入解析

对于一个一般开发人员来说, 上面的知识已经完全足够你使用ASP.NET MVC时使用Routing了.

接下来的部分我将深入Routing的机制讲解Routing的高级应用.但是因为是"高级应用", 加上这篇文章已经太长了, 再加上马上今天就过去了, "每日一篇"的承诺一定要兑现的, 所以不会对所有细节进行讲解. 或者也可以略过此部分.

Routing如何将请求传递给Controller?上面讲解Routing作用的时候, 我们就分析出Routing会将请求传递给Controller, 但是Routing如何做的这部份工作我们却看不到.关键在于MapRoute()这个方法封装了具体的细节.

虽然MapRoute方法是RouteCollection对象的方法,但是却被放置在System.Web.Mvc程序集中, 如果你的程序只引用了System.Web.Routing, 那么RouteCollection对象是不会有MapRoute方法的. 但是如果你同又引用了System.Web.Mvc, 则在mvc的dll中为RouteCollection对象添加了扩展方法:

public static void IgnoreRoute(this RouteCollection routes, string url);

public static void IgnoreRoute(this RouteCollection routes, string url, object constraints);

public static Route MapRoute(this RouteCollection routes, string name, string url);

public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults);

public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces); public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints);

public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces);

public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);

RouteCollection是一个集合,他的每一项应该是一个Route对象. 但是我们使用MapRoute时并没有创建这个对象, 这是因为当我们将MapRoute方法需要的参数传入时, 在方法内部会根据参数创建一个Route对象:

public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {

if (routes == null) {

throw new ArgumentNullException("routes");

}

if (url == null) {

throw new ArgumentNullException("url");

}

Route route = new Route(url, new MvcRouteHandler()) {

Defaults = new RouteValueDictionary(defaults),

Constraints = new RouteValueDictionary(constraints)

};

if ((namespaces != null) && (namespaces.Length > 0)) {

route.DataTokens = new RouteValueDictionary();

route.DataTokens["Namespaces"] = namespaces;

}

routes.Add(name, route);

return route;

}

上面就是MapRoute方法的实现, 至于在创建Route对象时第二个参数是一个MvcRouteHandler, 它是一个实现了IRouteHandler接口的类. IRouteHandler十分简单只有一个方法:

IHttpHandler GetHttpHandler(RequestContext requestContext);

参数是一个RequestContext 类实例, 这个类的结构也很简单:

public class RequestContext

{

public RequestContext(HttpContextBase httpContext, RouteData routeData);

public HttpContextBase HttpContext { get; }

public RouteData RouteData { get; }

}

其中的一个属性RouteData就包含了Routing根据Url识别出来各种参数的值, 其中就有Controller和Action的值.

归根结底, ASP.NET MVC最后还是使用HttpHandler处理请求. ASP.NET MVC定义了自己的实现了IHttpHandler接口的Handler:MvcHandler, 因为MvcRouteHandler的GetHttpHandler方法最后返回的就是MvcHandler.

MvcHandler的构造函数需要传入RequestContext 对象, 也就是传入了所有的所有需要的数据, 所以最后可以找到对应的Controller和Action, 已经各种参数.

六.测试Routing

因为一个Url会匹配多个routing规则, 最后常常会遇到规则写错或者顺序不对的问题.于是我们希望能够看到Url匹配Routing的结果.

其中最简单的办法就是使用RouteDebug辅助类. 这个类需要单独下载dll组件, 我将此组件的下载放在了博客园上:

/zhangziqiu/RouteDebug-Binary.zip

解压缩后是一个DLL文件, 将这个DLL文件添加到项目中并且添加引用.

使用方法很简单, 只需要在Application_Start方法中添加一句话:

RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

比如下面是我的示例中的代码:

protected void Application_Start()

{

RegisterRoutes(RouteTable.Routes);

RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

}

现在你访问任何URL, 都会出现RouteDebug页面, 如下:

其中不仅有你的所有Routing规则, 还显示了是否匹配.并且按照顺序列出. 还有识别的参数列表.

当你不想测试Routing规则的时候则注释掉这一段, 即可回复跳转到View对象上.

七.总结

本文讲解了ASP.NET MVC中一个关键的组件:Routing的使用. System.Web.Routing在Framework3.5 SP1中已经集成, 也就是说虽然我们还没有ASP.NET MVC的正式版, 但是Routing组件却已经提早发布了. 因为Routing是一个相对独立的组件, 不仅能和ASP.NET MVC配额使用, 也可以用于任何需要URL路由的项目. 另外Routing的作用和Url重写(Url Rewrite)是有区别的, 你会发现Routing和Url Rewrite相比其实很麻烦, 无论是添加规则还是传递参数.对UrlRewite感兴趣的可以去寻找UrlRewrite.dll这个组件, 很简单很强大, 有关两者的异同以及如何使用UrlRewrite这里不在多说了.

本文的示例下载地址:

/zhangziqiu/Demo-2.rar

从零开始学习 ASP.NET MVC 1.0 (三) Controller/Action 深入解析与应用实例

《从零开始学习ASP.NET MVC 1.0》 文章导航

(一) 开天辟地入门篇

(二) 识别URL的Routing组件

(三) Controller/Action 深入解析与应用实例

(四) View/Model 全解

(五) ViewEngine 深入解析与应用实例

一.摘要

一个Url请求经过了Routing处理后会调用Controller的Action方法. 中间的过程是怎样的? Action方法中返回ActionResult对象后,如何到达View的? 本文将讲解Controller的基本用法, 深入分析Controller的运行机制, 并且提供了创建所有类型Action的代码. 值得学习ASP.NET MVC时参考.

二.承上启下

在上一篇文章中, 我已经学会了如何使用Routing获取Controller和Action, 随后的程序会调用Controller中的Action方法.

每个Action方法都要返回一个ActionResult对象. 一个Action会将数据传递给View,如图:

三.Controller与Action的作用

1.职责

Controller负责将获取Model数据并将Model传递给View对象.通知View对象显示.

2.ASP.NET MVC中的Controller和Action

在ASP.NET MVC中, 一个Controller可以包含多个Action. 每一个Action都是一个方法, 返回一个ActionResult实例.

ActionResult类包括ExecuteResult方法, 当ActionResult对象返回后会执行此方法.

下面分层次的总结Controller 处理流程:

1. 页面处理流程

发送请求 –> UrlRoutingModule捕获请求 –> MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest()

2.MvcHandler.ProcessRequest() 处理流程:

使用工厂方法获取具体的Controller –> Controller.Execute() –> 释放Controller对象

3.Controller.Execute() 处理流程

获取Action –> 调用Action方法获取返回的ActionResult –> 调用ActionResult.ExecuteResult() 方法

4.ActionResult.ExecuteResult() 处理流程

获取IView对象-> 根据IView对象中的页面路径获取Page类-> 调用IView.RenderView() 方法(内部调用Page.RenderView方法)

通过对MVC源代码的分析,我们了解到Controller对象的职责是传递数据,获取View对象(实现了IView接口的类),通知View对象显示.

View对象的作用是显示.虽然显示的方法RenderView()是由Controller调用的,但是Controller仅仅是一个"指挥官"的作用, 具体的显示逻辑仍然在View对象中.

需要注意IView接口与具体的ViewPage之间的联系.在Controller和View之间还存在着IView对象.对于ASP.NET程序提供了WebFormView对象实现了IView接口.WebFormView负责根据虚拟目录获取具体的Page类,然后调用Page.RenderView().

四.ActionResult解析

通过上面的流程,我们知道了ActionResult对象在整个流程中的作用.ActionResult是一个抽象类, 在Action中返回的都是其派生类.下面是我整理的ASP.NET MVC 1.0 版本中提供的ActionResult派生类:

类名 抽象类 父类 功能

ContentResult 根据内容的类型和编码,数据内容.

EmptyResult 空方法.

FileResult abstract 写入文件内容,具体的写入方式在派生类中.

FileContentResult FileResult 通过 文件byte[] 写入文件.

FilePathResult FileResult 通过 文件路径 写入文件.

FileStreamResult FileResult 通过 文件Stream 写入文件.

HttpUnauthorizedResult 抛出401错误

JavaScriptResult 返回javascript文件

JsonResult 返回Json格式的数据

RedirectResult 使用Response.Redirect重定向页面

RedirectToRouteResult 根据Route规则重定向页面

ViewResultBase abstract 调用IView.Render()

PartialViewResult ViewResultBase 调用父类ViewResultBase 的ExecuteResult方法.

重写了父类的FindView方法.

寻找用户控件.ascx文件

ViewResult ViewResultBase 调用父类ViewResultBase 的ExecuteResult方法.

重写了父类的FindView方法.

寻找页面.aspx文件

目前ASP.NET MVC还没有提供官方的ActionResult列表.上面的列表是我在源代码中分析得出的.有些解释的可能不够清楚,请谅解.

下面我将列举各个ActionResult的实例.

五.实例应用

1.添加Controller

安装了ASP.NET MVC后, 在项目上点击右键会找到添加Controller项:

2.添加Action

下面这个类提供了返回各种类型的ActionResult的Action实例:

public class DemoController : Controller

{

/// <summary>

/// http://localhost:1847/Demo/ContentResultDemo

/// </summary>

/// <returns></returns>

public ActionResult ContentResultDemo()

{

string contentString = "ContextResultDemo!";

return Content(contentString);

}

/// <summary>

/// http://localhost:1847/Demo/EmptyResultDemo

/// </summary>

/// <returns></returns>

public ActionResult EmptyResultDemo()

{

return new EmptyResult();

}

/// <summary>

/// http://localhost:1847/Demo/FileContentResultDemo

/// </summary>

/// <returns></returns>

public ActionResult FileContentResultDemo()

{

FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[Convert.ToInt32(fs.Length)];

fs.Read(buffer, 0, Convert.ToInt32(fs.Length) );

return File(buffer, @"image/gif");

}

/// <summary>

/// http://localhost:1847/Demo/FilePathResultDemo

/// </summary>

/// <returns></returns>

public ActionResult FilePathResultDemo()

{

//可以将一个jpg格式的图像输出为gif格式

return File(Server.MapPath(@"/resource/Images/2.jpg"), @"image/gif");

}

/// <summary>

/// http://localhost:1847/Demo/FileStreamResultDemo

/// </summary>

/// <returns></returns>

public ActionResult FileStreamResultDemo()

{

FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);

return File(fs, @"image/gif");

}

/// <summary>

/// http://localhost:1847/Demo/HttpUnauthorizedResultDemo

/// </summary>

/// <returns></returns>

public ActionResult HttpUnauthorizedResultDemo()

{

return new HttpUnauthorizedResult();

}

/// <summary>

/// http://localhost:1847/Demo/JavaScriptResultDemo

/// </summary>

/// <returns></returns>

public ActionResult JavaScriptResultDemo()

{

return JavaScript(@"alert(""Test JavaScriptResultDemo!"")");

}

/// <summary>

/// http://localhost:1847/Demo/JsonResultDemo

/// </summary>

/// <returns></returns>

public ActionResult JsonResultDemo()

{

var tempObj = new { Controller = "DemoController", Action = "JsonResultDemo" };

return Json(tempObj);

}

/// <summary>

/// http://localhost:1847/Demo/RedirectResultDemo

/// </summary>

/// <returns></returns>

public ActionResult RedirectResultDemo()

{

return Redirect(@"http://localhost:1847/Demo/ContentResultDemo");

}

/// <summary>

/// http://localhost:1847/Demo/RedirectToRouteResultDemo

/// </summary>

/// <returns></returns>

public ActionResult RedirectToRouteResultDemo()

{

return RedirectToAction(@"FileStreamResultDemo");

}

/// <summary>

/// http://localhost:1847/Demo/PartialViewResultDemo

/// </summary>

/// <returns></returns>

public ActionResult PartialViewResultDemo()

{

return PartialView();

}

/// <summary>

/// http://localhost:1847/Demo/RedirectToRouteResultDemo

/// </summary>

/// <returns></returns>

public ActionResult ViewResultDemo()

{

//如果没有传入View名称, 默认寻找与Action名称相同的View页面.

return View();

}

}

在文章最后提供有完整实例代码下载.

六.Controller 深入分析

在研究Controller/Action的流程过程中, 发现了ASP.NET MVC一些问题.

1.Routing组件与MVC框架的结合

Routing组件和ASP.NET MVC并不是一个项目, 在ASP.NET MVC中仅仅是使用了Routing组件, 在源代码中是通过dll的方式引用的.Routing组件已经包含在.net framework 3.5 sp1中了.

那么ASP.NET MVC是如何应用Routing组件的呢?

Routing组件获取了Url中的数据后, 会将数据保存在一个 RouteData 对象中.并将请求传递给一个实现了IRouteHandler接口的对象. 在Asp.net MVC中提供的MvcRouteHandler类实现了此接口, Routing 将请求传递给MvcRouteHandler的GetHttpHandler方法.下面是源代码:

IRouteHandler接口:

public interface IRouteHandler

{

IHttpHandler GetHttpHandler(RequestContext requestContext);

}

MvcRouteHandler类:

public class MvcRouteHandler : IRouteHandler {

protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {

return new MvcHandler(requestContext);

}

#region IRouteHandler Members

IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {

return GetHttpHandler(requestContext);

}

#endregion

}

曾经我认为IRouteHandler是多余的, 用IHttpHandler就够了. 现在知道了为何要定义这个接口. 主要是为了传递RouteData对象.GetHttpHandler方法需要一个RequestContext 对象.RequestContext 是 System.Web.Routing程序集中的类, 里面除了处理请求需要的HttpContextBase对象,还包括了一个RouteData对象.

RequestContext类:

public class RequestContext

{

public RequestContext(HttpContextBase httpContext, RouteData routeData);

public HttpContextBase HttpContext { get; }

public RouteData RouteData { get; }

}

Routing组件在Web.Config中注册了一个HttpModule: System.Web.Routing.UrlRoutingModule, 而不是HttpHandler:

<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

可惜看不到这个类的源代码. 所有请求最后都是要传递给IHttpHandler对象处理, 主要的工作是编译页面, 所以我猜测这个Module将请求截获后通过IRouteHandler接口对象获取一个HttpHandler, 然后将处理移交给获取到的HttpHandler.

ASP.NET MVC 中实现了IHttpHandler接口的类是MvcHandler, MvcRouteHandler.GetHttpHandler方法就是返回一个MvcHandler对象. MvcHandler类的构造函数需要传入一个RequestContext对象. 实现的IHttpHandler接口方法处理过程中都需要依赖这个对象.

但是微软在这里的处理有一些不足. MvcHandler虽然实现了IHttpHandler接口但是不能被当作IHttpHandler接口使用. 因为IHttpHandler中没有定义RequestContext属性, 如果一个MvcHandler对象此属性没有赋值则会出错, 也没有将默认的无参数构造函数设置为private, 所以理论上可以很随意的实例化一个MvcHandler而不为其RequestContext属性赋值.

IRouteHandler想实现的语意是: 返回一个具有RequestContext属性的IHttpHandler对象.

但是最后的实现结果是: 提供"返回IHttpHandler对象"的方法, 此方法接收RequestContext对象参数.

还需要注意ControllerContext类. 在Controller的处理过程中使用此对象作为保存上下文数据的容器.下面是这几个类的包含关系:

可以看到在ControllerContext中包含了RequestContext对象,但是又将RequestContext对象中的两个属性提取到自己的类中.如果仅仅是为了使用方便而这么做, 个人认为不是一个好的设计.数据对象的存储职责也应该明确,使用ControllerContext.RequestContext.RouteData 的方式更容易被人理解.

PS:这种方式类似于方法内联.对于属性JIT为了效率会帮助我们做内联.而仅仅是为了使用方便.

2.IView 与 View对象的关系

所以从系统的角度上看, 实现了IView接口的对象才是View.

但是从实现效果上看, 具体的aspx或者ascx页面才是View.

当第一次看到IView接口时我认为它应该是"View角色"需要实现的接口. 但是结果并不是这样.

在我们的系统中View对象应该是aspx或者ascx文件. 而且并不是所有的ActionResult都需要找到aspx或者ascx文件, 事实上只有PartialViewResult 和 ViewResult 才会去寻找View对象.其他的ActionResult要么是返回文件, 要么是跳转等等.

那么两者的关系到底是怎样的? 其实其中的过程需要牵扯到这几个接口和类:

IViewEngine, ViewEngineResult, ViewEngineCollection

ViewEngine是View引擎, ViewEngineCollection是一个引擎集合,里面保存了各种寻找View的引擎.但是在目前的源代码中只有WebFormViewEngine : VirtualPathProviderViewEngine : IViewEngine

这一系列WebForm使用的引擎.引擎的作用有两个:

1.寻找Page/用户控件的路径

2.根据路径创建IView对象.也就是根据页面的物理文件创建IView接口对象.

而且目前实现了IView接口的对象也只有一个:

WebFormView

WebFormViewEngine 根据页面路径, 将一个页面地址转化为一个WebFormView对象,也就是一个IView接口对象.

至此IView接口和Page页面类仍然没有任何关系, IView对象只是保存了页面的物理路径.

接着在IView的Render事件中,根据物理路径创建了一个页面的object实例,注意看这一段代码:

object viewInstance = BuildManager.CreateInstanceFromVirtualPath(ViewPath, typeof(object)); if (viewInstance == null) {

throw new InvalidOperationException(

String.Format(

CultureInfo.CurrentUICulture,

MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,

ViewPath));

}

ViewPage viewPage = viewInstance as ViewPage;

if (viewPage != null) {

RenderViewPage(viewContext, viewPage);

return;

}

ViewUserControl viewUserControl = viewInstance as ViewUserControl;

if (viewUserControl != null) {

RenderViewUserControl(viewContext, viewUserControl);

return;

}

viewInstance 就是通过物理路径创建的页面对象.但是他的类型是object, 而且程序尝试将其分别转化为ViewPage对象和ViewUserControl对象.

我想很多人都看到了这里的设计不足.现在我们只能"约定": 所有的MVC中的页面对象都必须继承自ViewPage或者ViewUserControl类, 否则程序就会出错.产生这种不足的原因就是IView接口和ViewPage没有任何的耦合性, 完全是硬编码进去的.

为什么不让页面直接实现IView接口? 然后尝试将页面转化为IView接口对象, 而不是ViewPage, 这样才是好的设计. 其实微软知道什么是好的设计, 我猜测他们遇到的困难是Page对象和IView接口的冲突. 因为两者都需要Render. 如果在IView中定义自己的Render名称, 那就意味着ASP.NET MVC开发小组要自己处理页面的显示逻辑, 而现在ASP.NET WebForm模式下面的页面显示引擎又不能复用, 重新开发自己的一套显示引擎成本又太大, 才出此下策.

以上只是猜测.这种设计的缺陷虽然可以接受, 但是真的是让我好几天陷入了看不懂代码的痛苦之中.还好, 现在可以解脱了.

七.如何在MVC项目中使用MVC源代码项目

另外在为了跟踪实现过程, 我将ASP.NET MVC的源代码项目添加到了实例项目中, 其中有一些需要注意的地方:

1. 将实例项目中的System.Web.Mvc引用删除, 改成项目引用.

2. 需要在Web.Config中注释掉程序集引用:

<compilation debug="true">

<assemblies>

<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

<add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

<add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

<!-- <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>-->

<add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

<add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

<add assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

</assemblies>

</compilation>

注释掉的程序集存在于GAC中, 但是我们现在不希望使用GAC中的程序集, 而是引用项目.

3. 将View目录下的Web.Config中的所有System.Web.Mvc相关的 PublicKeyToken 都修改为 null:

<pages

validateRequest="false"

pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">

<controls>

<add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" namespace="System.Web.Mvc" tagPrefix="mvc" />

</controls>

</pages>

八.总结

首先很抱歉在本系列文章开篇时承诺的每日一篇仅仅坚持了2天.具体原因就不解释了.这篇文章的出炉历时半个月, 并且经历了ASP.NET MVC从RC,RC2直到最近的1.0版本的演变. 在查看MVC源代码上花费了大量的时间, 希望付出的努力能够为大家研究学习ASP.NET MVC带来帮助.

实例源代码下载地址:

/zhangziqiu/Asp.net-MVC-3-Demo.rar

从零开始学习 ASP.NET MVC 1.0 (四) View/Model 全解

《从零开始学习ASP.NET MVC 1.0》 文章导航

(一) 开天辟地入门篇

(二) 识别URL的Routing组件

(三) Controller/Action 深入解析与应用实例

(四) View/Model 全解

(五) ViewEngine 深入解析与应用实例

一.摘要

本文讲解在Action中向View传递Model的几种方式.以及View获取Model以后如何编写显示逻辑.还详细的介绍了ASP.NET MVC框架提供的Html Helper类的使用及如何为Html Helper类添加自定义扩展方法.

二.承上启下

上一篇文章中我们学习了Controller处理一次请求的全过程.在Controller的Action中, 会传递数据给View,还会通知View对象开始显示.所以Model是在Action中获取的, 并由Action传递给View. View对象接到Action通知后会使用自己的显示逻辑展示页面.

下面首先让我们学习如何将Model传递给View对象.

三.传递数据给View

在MVC中,Model对象是指包含了数据的模型. Controller将Model传递给View以后, View对象中不应该做任何的业务逻辑处理, 仅仅根据Model对象做一些显示逻辑的处理.

传递Model对象时, 我们有两种选择:

1.传递一个弱类型的集合, 即成员为object类型的集合, 在View中需要将每个成员转换成我们需要的类型,比如int, string,自定义类型等.

2.传递强类型对象, 这些类型是我们自定义的. 在View中直接使用我们传递的强类型对象, 不需要再转换类型.

如果让我们自己设计一个MVC框架, 我们也会想到上面两种实现方式,接下来看看在ASP.NET MVC中的实现.

1.传递弱类型的集合

(1) 如何传递

ASP.NET MVC框架定义了ViewContext类, 直译后是"View上下文", 其中保存和View有关的所有数据, 其中Model对象也封装在了此类型中.

ViewContext对象包含三个属性:

IView View

ViewDataDictionary ViewData

TempDataDictionary TempData

其中ViewData集合和TempData集合都是用来保存Model对象的.在一个Controller的Action中, 我们可以用如下方式为这两个集合赋值:

/// <summary>

/// 传递弱类型Model的Action示例

/// </summary>

/// <returns>ViewResult</returns>

public ActionResult WeakTypedDemo()

{

ViewData["model"] = "Weak Type Data in ViewData";

TempData["model"] = "Weak Type Data in TempData";

return View("WeakTypedDemo");

}

在页面中, 是用如下方式使用这两个集合:

<div>

<% = ViewData["model"] %><br />

<% = TempData["model"] %><br />

</div>

(2) 传递过程

请注意Action中的赋值语句实际上操作的是Controller类的ViewData和TempData属性, 此时并没有任何的数据传递.上一篇文章中我们已经学到, return View语句会返回一个ViewResult对象, 并且接下来要执行ViewResult的Executeresult方法. Controller的View方法会将Controller类的ViewData和TempData属性值传递给ViewResult,代码如下:

protected internal virtual ViewResult View(IView view, object model) {

if (model != null) {

ViewData.Model = model;

}

return new ViewResult {

View = view,

ViewData = ViewData,

TempData = TempData

};

}

然后在ViewResult中根据ViewData和TempData构建ViewContext对象:

public override void ExecuteResult(ControllerContext context) {

//...

ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);

View.Render(viewContext, context.HttpContext.Response.Output);

//...

}

ViewContext对象最终会传递给ViewPage, 也就是说ViewData和TempData集合传递到了ViewPage. 我这里简化了最后的传递流程, 实际上ViewData对象并不是通过ViewContext传递到ViewPage中的, ViewPage上的ViewData是一个单独的属性, 并没有像TempData一样其实访问的是ViewContext.TempData. 这么做容易产生奇异, 本类ViewContext是一个很好理解职责十分清晰的类. 作为使用者的我们暂时可以忽略这点不足, 因为如此实现ViewData完全是为了下面使用强类型对象.

(3)ViewData和TempData的区别

虽然ViewData和TempData都可以传递弱类型数据,但是两者的使用是有区别的:

ViewData的生命周期和View相同, 只对当前View有效.

TempData保存在Session中, Controller每次执行请求的时候会从Session中获取TempData并删除Session, 获取完TempData数据后虽然保存在内部的字典对象中,但是TempData集合的每个条目访问一次后就从字典表中删除. 也就是说TempData的数据至多只能经过一次Controller传递, 并且每个元素至多只能访问一次.

2.传递强类型对象

我在系统中建立了一个模型类:StrongTypedDemoDTO

从名字可以看出, 这个模型类是数据传输时使用的(Data Transfer Object).而且是我为一个View单独创建的.

添加一个传递强类型Model的Action,使用如下代码:

public ActionResult StrongTypedDemo()

{

StrongTypedDemoDTO model = new StrongTypedDemoDTO() { UserName="ziqiu.zhang", UserPassword="123456" };

return View(model);

}

使用了Controller.View()方法的一个重载, 将model对象传递给View对象.下面省略此对象的传输过程, 先让我们来看看如何在View中使用此对象.

在创建一个View时, 会弹出下面的弹出框:

勾选"Create a strongly-typed view"即表示要创建一个强类型的View, 在"View data class"中选择我们的数据模型类.

在"view content"中如下选项:

这里是选择我们的View的"模板", 不同的模板会生成不同的View页面代码. 虽然这些代码不一定满足我们需要, 但是有时候的确能节省一些时间,尤其是在写文章做Demo的时候. 比如我们的View是添加数据使用的,那就选择"Create".如果是显示一条数据用的, 就选择"Detail".

以选择Detail为例, 自动生成了下列代码:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<DemoRC.Models.DTO.TransferModelController.StrongTypedDemoDTO>" %>

...

<body>

<fieldset>

<legend>Fields</legend>

<p>

UserName:

<%= Html.Encode(Model.UserName) %>

</p>

<p>

UserPassword:

<%= Html.Encode(Model.UserPassword) %>

</p>

</fieldset>

<p>

<%=Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |

<%=Html.ActionLink("Back to List", "Index") %>

</p>

</body>

...

页面的Model属性就是一个强类型对象, 在这里就是StrongTypedDemoDTO类实例.page页面指令可以看出, 这里的页面继承自ViewPage<T>类, 而不是ViewPage, 用T已经确定为StrongTypedDemoDTO类型, 所以Model的类型就是StrongTypedDemoDTO.

3.传递数据的思考

使用强类型数据要优于弱类型数据, 老赵也曾提出过. 强类型有太多的好处, 智能提示, 语意明确, 提高性能,编译时发现错误等等.

所以在实际应用中, 我们应该传递强类型的数据.

目前ASP.NET MVC还存在一些不足. 将页面类型化, 导致了只能传递一种数据类型实例到页面上. 而且内部代码的实现上并不十分完美.尤其是虽然我们已经知道传递的是StrongTypedDemoDTO类型, 页面的Model属性也是StrongTypedDemoDTO类型, 但是仍然需要进行强制类型转换, 原因是Controller的View(object model)方法重载接收的是一个object类型.

还有, 为每个View建立一个模型类, 也是一件繁琐的工作. 也许我们的业务模型中的两个类组合在一起就是View页面需要的数据, 但是却不得不建立一个类将其封装起来.模型类的膨胀也是需要控制一个事情. 尤其是对于互谅网应用而非企业内部的系统, 页面往往会有成百上千个.而且复用较少.

当然目前来说既然要使用强类型的Model, 我提出一些组织Model类型的实践方法.下面是我项目中的Model类型组织结构:

这里Model是一个文件夹, 稍大一些的系统可以建立一个Model项目. 另外我们需要建立一个DTO文件夹, 用来区分Model的类型. MVC中的Model应该对应DTO文件夹中的类.在DTO中按照Controller再建立文件夹, 因为Action和View大部分都是按照Controller组织的, 所以Model也应该按照Controller组织.

在Controller文件夹里放置具体的Model类. 其实两个Controller文件夹中可以同相同的类名称, 我们通过命名空间区分同名的Model类:

namespace DemoRC.Models.DTO.TransferModelController

{

/// <summary>

/// Action为StrongTypedDemo的数据传输模型

/// </summary>

public class StrongTypedDemoDTO

{

/// <summary>

/// 用户名

/// </summary>

public string UserName

{

get;

set;

}

/// <summary>

/// 用户密码

/// </summary>

public string UserPassword

{

get;

set;

}

}

}

使用时也要通过带有Controller的命名空间使用比如DTO.TransferModelController.StrongTypedDemoDTO, 或者建立一些自己的约定.

四.使用Model输出页面

View对象获取了Model以后, 我们可以通过两种方式使用数据:

1.使用内嵌代码

熟悉ASP,PHP等页面语言的人都会很熟悉这种直接在页面上书写代码的方式.但是在View中应该只书写和显示逻辑有关的代码,而不要增加任何的业务逻辑代码.

假设我们创建了下面这个Action,为ViewData添加了三条记录:

/// <summary>

/// Action示例:使用内嵌代码输出ViewData

/// </summary>

/// <returns></returns>

public ActionResult ShowModelWithInsideCodeDemo()

{

ViewData["k1"] = @"<script type=""text/javascript"">";

ViewData["k2"] = @"alert(""Hello ASP.NET MVC !"");";

ViewData["k3"] = @"</script>";

return View("ShowModelWithInsideCode");

}

在ShowModelWithInsideCode中, 我们可以通过内嵌代码的方式, 遍历ViewData集合:

<html xmlns="/1999/xhtml" >

<head runat="server">

<title>使用内嵌代码输出ViewData</title>

<% foreach(KeyValuePair<string, object> item in ViewData )

{%>

<% = item.Value %>

<% } %>

</head>

<body>

<div>

<div>此页面运行的脚本代码为:</div>

<fieldset>

<% foreach(KeyValuePair<string, object> item in ViewData )

{%>

<% = Html.Encode(item.Value) %> <br />

<% } %>

</fieldset>

</div>

</body>

</html>

页面上遍历了两遍ViewData,第一次是作为脚本输出的, 第二次由于进行了HTML编码,所以将作为文字显示在页面上.

使用这种方式, 我们可以美工做好的HTML页面的动态部分, 使用<% %>的形式转化为编码区域, 通过程序控制输出.由于只剩下显示逻辑要处理,所以这种开发方式往往要比CodeBehind的编码方式更有效率, 维护起来一目了然.

最让我高兴是使用这种方式后,我们终于可以只使用HTML控件了.虽然ASP.NET WebFrom编程模型中自带了很多服务器控件, 功能很好很强大, 但是那终究是别人开发的控件, 这些控件是可以随意变化的, 而且实现原理也对使用者封闭. 使用原始的页面模型和HTML控件将使我们真正的做程序的主人.而且不会因为明天服务器控件换了个用法就要更新知识, 要知道几乎所有的HTML控件几乎是被所有浏览器支持且不会轻易改变的.

2.使用服务器控件

注意虽然我们同样可以在ASP.NET MVC中使用服务器端控件, 但是在MVC中这并不是一个好的使用方式.建议不要使用.

要使用服务器端控件, 我们就需要在后台代码中为控件绑定数据. ASP.NET MVC框架提供的添加一个View对象的方法已经不能创建后台代码, 也就是说已经摒弃了这种方式.但是我们仍然可以自己添加.

首先创建一个带有后台代码的(.cs文件),一般的Web Form页面(aspx页面),然后修改页面的继承关系, 改为继承自ViewPage:

public partial class ShowModelWithControl : System.Web.Mvc.ViewPage

在页面上放置一个Repeater控件用来显示数据:

<body>

<form id="form1" runat="server">

<div>

<asp:Repeater ID="rptView" runat="server">

<ItemTemplate>

<%# ((KeyValuePair<string, object>)Container.DataItem).Value %><br />

</ItemTemplate>

</asp:Repeater>

</div>

</form>

</body>

在Page_Load方法中, 为Repeater绑定数据:

public partial class ShowModelWithControl : System.Web.Mvc.ViewPage

{

protected void Page_Load(object sender, EventArgs e)

{

rptView.DataSource = ViewData;

rptView.DataBind();

}

}

在Controller中创建Action, 为ViewData赋值:

/// <summary>

/// Action示例:使用服务器控件输出ViewData

/// </summary>

/// <returns></returns>

public ActionResult ShowModelWithControlDemo()

{

ViewData["k1"] = @"This";

ViewData["k2"] = @"is";

ViewData["k3"] = @"a";

ViewData["k4"] = @"page";

return View("ShowModelWithControl");

}

运行结果:

再次强调, 在ASP.NET MVC中我们应该尽量避免使用这种方式.

3.使用 HTML Helper 类生成HTML控件

HTML Helper类是ASP.NET MVC框架中提供的生成HTML控件代码的类. 本质上与第一种方式一样, 只是我们可以不必手工书写HTML代码,而是借助HTML Helper类中的方法帮助我们生成HTML控件代码.

同时,我们也可以使用扩展方法为HTML Helper类添加自定义的生成控件的方法.

HTML Helper类的大部分方法都是返回一个HTML控件的完整字符串, 所以可以直接在需要调用的地方使用<% =Html.ActionLink() %>的形式输出字符串.

(1)ASP.NET MVC中的HtmlHelper类

在ViewPage中提供了Html属性, 这就是一个HtmlHelper类的实例. ASP.NET MVC框架自带了下面这些方法:

Html.ActionLink()

Html.BeginForm()

Html.CheckBox()

Html.DropDownList()

Html.EndForm()

Html.Hidden()

Html.ListBox()

Html.Password()

Html.RadioButton()

Html.TextArea()

Html.TextBox()

上面列举的常用的HtmlHelper类的方法,并不是完整列表.

下面的例子演示如何使用HtmlHelper类:

<div>

<% using (Html.BeginForm())

{ %>

<label style="width:60px;display:inline-block;">用户名:</label>

<% =Html.TextBox("UserName", "ziqiu.zhang", new { style="width:200px;" })%>

<br /><br />

<label style="width:60px;display:inline-block;">密码:</label>

<% =Html.Password("Psssword", "123456", new { style = "width:200px;" })%>

<% }%>

</div>

上面的代码使用Html.BeginForm输出一个表单对象, 并在表单对象中添加了两个Input, 一个使用Html.TextBox输出, 另一个使用Html.Password输出,区别是Html.Password输出的input控件的type类型为password.效果如图:

(2)扩展Html Helper类

我们可以自己扩展HtmlHelper类, 为HtmlHelper类添加新的扩展方法, 从而实现更多的功能.

在项目中建立Extensions文件夹, 在其中创建SpanExtensions.cs文件.源代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

namespace System.Web.Mvc

{

public static class SpanExtensions

{

public static string Span(this HtmlHelper helper,string id, string text)

{

return String.Format(@"<span id=""{0}"">{1}</span>", id, text);

}

}

}

上面的代码我们为HtmlHelper类添加了一个Span()方法, 能够返回一个Span的完整HTML字符串.

因为命名空间是System.Web.Mvc,所以页面使用的时候不需要再做修改,Visual Studio会自动识别出来:

请大家一定要注意命名空间, 如果不使用System.Web.Mvc命名空间, 那么一定要在页面上引用你的扩展方法所在的命名空间, 否则我们的扩展方法将不会被识别.

接下来在页面上可以使用我们的扩展方法:

<div>

<!-- 使用自定义的Span方法扩展HTML Helper -->

<% =Html.Span("textSpan", "使用自定义的Span方法扩展HtmlHelper类生成的Span") %> </div>

(3) 使用TagBuilder类创建扩展方法

上面自定义的Span()方法十分简单, 但是有时候我们要构造具有复杂结构的Html元素, 如果用字符串拼接的方法就有些笨拙.

ASP.NET MVC框架提供了一个帮助我们构造Html元素的类:TagBuilder

TagBuilder类有如下方法帮助我们构建Html控件字符串:

方法名称 用途

AddCssClass() 添加class=””属性

GenerateId() 添加Id, 会将Id名称中的"."替换为IdAttributeDotReplacement 属性值的字符.默认替换成"_" MergeAttribute() 添加一个属性,有多种重载方法.

SetInnerText() 设置标签内容, 如果标签中没有再嵌套标签,则与设置InnerHTML 属性获得的效果相同. ToString() 输出Html标签的字符串, 带有一个参数的重载可以设置标签的输出形式为以下枚举值: TagRenderMode.Normal -- 有开始和结束标签

TagRenderMode.StartTag -- 只有开始标签

TagRenderMode.EndTag -- 只有结尾标签

TagRenderMode.SelfClosing -- 单标签形式,如<br/>

同时一个TagBuilder还有下列关键属性:

属性名称 用途

Attributes Tag的所有属性

IdAttributeDotReplacement 添加Id时替换"."的目标字符

InnerHTML Tag的内部HTML内容

TagName Html标签名, TagBuilder只有带一个参数-TagName的构造函数.所以TagName是必填属性

下面在添加一个自定义的HtmlHelper类扩展方法,同样是输出一个<Span>标签:

public static string Span(this HtmlHelper helper, string id, string text, string css, object htmlAttributes) {

//创意某一个Tag的TagBuilder

var builder = new TagBuilder("span");

//创建Id,注意要先设置IdAttributeDotReplacement属性后再执行GenerateId方法.

builder.IdAttributeDotReplacement = "-";

builder.GenerateId(id);

//添加属性

builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));

//添加样式

builder.AddCssClass(css);

//或者用下面这句的形式也可以: builder.MergeAttribute("class", css);

//添加内容,以下两种方式均可

//builder.InnerHtml = text;

builder.SetInnerText(text);

//输出控件

return builder.ToString(TagRenderMode.Normal);

}

在页面上,调用这个方法:

<% =Html.Span("span.test", "使用TagBuilder帮助构建扩展方法", "ColorRed", new { style="font-size:15px;" })%>

生成的Html代码为:

<span id="span-test" class="ColorRed" style="font-size: 15px;">使用TagBuilder帮助构建扩展方法</span>

注意已经将id中的"."替换为了"-"字符.

五.总结

本来打算在本文中直接将ViewEngine的使用也加进来, 但是感觉本文已经写了很长的内容, (虽然不多,但是很长......)所以将ViewEngine作为下一章单独介绍.

前些天 Scott Guthrie's的博客上放出了"ASP.NET MVC免费教程", 里面介绍了创建一名为"NerdDinner"项目的全过程, 使用LINQ+ASP.NET MVC, 但是其中对于技术细节没有详细介绍(和本系列文章比较一下就能明显感觉出来), 下面提供本书的pdf文件下载地址以及源代码下载地址:

免费下载PDF版本

下载应用源码 + 单元测试

源代码是英文版本, 其实我最近有做一个中文的"Nerd Dinner"的想法, 但是因为要写文章而且还要工作已经让我焦头烂额, 写文章真的是一件体力活.如果有人同样有兴趣翻译代码, 我可以提供域名和服务器空间.

差点提供忘记本篇文章的示例源代码下载:

/zhangziqiu/AspNet-Mvc-4-Demo.rar

从零开始学习 ASP.NET MVC 1.0 (五) ViewEngine 深入解析与应用实例

《从零开始学习ASP.NET MVC 1.0》 文章导航

(一) 开天辟地入门篇

(二) 识别URL的Routing组件

(三) Controller/Action 深入解析与应用实例

(四) View/Model 全解

(五) ViewEngine 深入解析与应用实例

一.摘要

本文讲解ViewEngine的作用, 并且深入解析了实现ViewEngine相关的所有接口和类, 最后演示了如何开发一个自定义的ViewEngine. 本系列文章已经全部更新为ASP.NET MVC 1.0版本.希望大家多多支持!

二.承上启下

首先注意: 我会将大家在MVC之前一直使用的ASP.NET页面编程模型称作ASP.NET WebForm编程模型.

上一讲中我们已经学习了如何向View传递Model, 以及如何在View中使用Model对象. 目前为止我们使用的都还是ASP.NET WebForm的页面模型,比如aspx页面,用户控件,母版页等. 最后这些页面中都要转换为HTML代码. 比如页面中的内嵌代码:

<% = ViewData["model"] %>

你是否思考过, 为何页面会支持<% %>这种语法? 为何最后一个aspx页面会在浏览器中以HTML代码的形式展现?

有人会回答这是ASP.NET自带的语法和功能. 没有错, ASP.NET帮我们做了编译页面, 输出HTML, 返回HTML给客户端浏览器等一系列工作.但是这些工作在MVC框架中有很多是属于View角色的职责. 为了继续使用原有的ASP.NET WebForm页面引擎, ASP.NET MVC抽象出来了ViewEngine这个角色. 顾名思义ViewEngine即视图引擎, 其主要作用就是找到View对象, 编译View对象中的语言代码(执行语言逻辑), 并且输出HTML. 下面讲解的WebFormViewEngine就是使用ASP.NET WebForm的页面编译/呈现功能实现的.

三.ViewEngine解析

下面将讲解和ViewEngine有关的各个接口和类.

IView接口

IView接口是对MVC结构中View对象的抽象, 此接口只有一个方法:

void Render(ViewContext viewContext, TextWriter writer);

Render方法的作用就是展示View对象, 通常是将页面HTML写入到Writer中供浏览器展示.

在本系列第三篇文章中我曾经分析过, 虽然IView对象是MVC中View角色的抽象, 并且提供了Render方法, 但是实际上真正的View角色的显示逻辑在ViewPage/ViewUserControl类中. 这是由于ASP.NET MVC提供的WebFormViewEngine视图引擎是使用原有的ASP.NET Web From的页面显示机制, 我们无法直接将WebForm模型中的页面转化为IView对象.

于是最后使用了一个折中的办法:

在IView对象的Render方法中调用WebForm页面的Render方法. WebFormView是目前ASP.NET MVC中唯一实现了IView接口的类

所以如果我们使用自定义的ViewEngine引擎, 就可以直接创建一个实现了IView接口的类实现Render方法.

IViewEngine接口

ViewEngine即视图引擎, 在ASP.NET MVC中将ViewEngine的作用抽象成了 IViewEngine 接口.

虽然IViewEngine的职责是寻找View对象, 但是其定义的两个方法:

FindPartialView

FindView

返回的结果是ViewEngineResult对象, 并不是View对象. 我们可以将ViewEngineResult理解为一次查询的结果, 在ViewEngineResult对象中包含有本次找到的IView对象.

ASP.NET MVC 提供了下面两个实现了IViewEngine接口的类:

VirtualPathProviderViewEngine

WebFormViewEngine

WebFormViewEngine是VirtualPathProviderViewEngine的派生类.

VirtualPathProviderViewEngine类实现了FindPartialView/FindView方法, 更够根据指定的路径格式搜索页面文件, 并且使用了提供了Cache机制缓存数据. 注意因为使用的是ASP.NET Cache,依赖HttpContext对象, 这就导致Cache无法在WebService或者WCf等项目中使用. VirtualPathProviderViewEngine寻找页面的方法依赖下面三个属性:

MasterLocationFormats

ViewLocationFormats

PartialViewLocationFormats

在VirtualPathProviderViewEngine中只定义了这三个属性, 具体的值在派生类WebFormViewEngine中指定:

public WebFormViewEngine() {

MasterLocationFormats = new[] {

"~/Views/{1}/{0}.master",

"~/Views/Shared/{0}.master"

};

ViewLocationFormats = new[] {

"~/Views/{1}/{0}.aspx",

"~/Views/{1}/{0}.ascx",

"~/Views/Shared/{0}.aspx",

"~/Views/Shared/{0}.ascx"

};

PartialViewLocationFormats = ViewLocationFormats;

}

上面的代码中我们可以一步了然ViewEngine都搜索哪些路径.甚至还可以添加我们自己的路径和文件类型.

因为有了VirtualPathProviderViewEngine类, 在开发自定义的ViewEngine时不需要再编写搜索View文件的逻辑了.只需要定义搜索路径即可. 如果不使用ASP.NET WebForm的页面显示方式, 就需要自己定义的View对象如何显最后转化为HTML代码.

在后面的实例中会演示创建一个我们自定义的ViewEngine.

ViewEngineResult

ViewEngineResult是ViewEngine寻找View的查询结果.ViewEngineResult类没有派生类, 也就是说不同的ViewEngine返回的结果都是ViewEngineResult对象.

ViewEngineResult类有一个很重要的构造函数:

public ViewEngineResult(IView view, IViewEngine viewEngine)

以WebFormViewEngine为例, 在WebFormViewEngine类中定义了 MasterLocationFormats/ViewLocationFormats /PartialViewLocationFormats , 在调用FindPartialView/FindView方法时, 首先找到View对象的磁盘路径, 然后使用CreatePartialView/CreateView方法将磁盘路径转化实现了IView接口的WebFormView对象.

WebFormView中依然保存这页面对象的磁盘路径, 在调用Render时会根据磁盘路径创建ViewPage对象, 调用页面的Render方法.ASP.NET MVC编译页面时, 使用了.NET Framework 2.0以上的版本中提供的根据虚拟路径编译页面的函数:

BuildManager.CreateInstanceFromVirtualPath(string virtualPath, Type requiredBaseType)

命名空间为System.Web.Compilation.

ViewEngineCollection

ViewEngineCollection是IViewEngine对象的集合类. 在我们的系统中可以使用多个ViewEngine, 在寻找时会返回第一个匹配的ViewEngineResult, 下面是ViewEngineCollection类的Find方法代码:

private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator) {

ViewEngineResult result;

foreach (IViewEngine engine in Items) {

if (engine != null) {

result = cacheLocator(engine);

if (result.View != null) {

return result;

}

}

}

List<string> searched = new List<string>();

foreach (IViewEngine engine in Items) {

if (engine != null) {

result = locator(engine);

if (result.View != null) {

return result;

}

searched.AddRange(result.SearchedLocations);

}

}

return new ViewEngineResult(searched);

}

通过上面的代码我们了解到, ViewEngineCollection会首先从Cache中搜索, 如果没有搜索到结果,则根据路径格式搜索. 如果最后还是没有搜索到View对象则抛出找不到View的异常.所以虽然我们可以添加多个ViewEngine, 但是永远不要为两个ViewEngine指定同样的搜索格式(路径+文件类型), 因为如果出现一个页面对象符合两个ViewEngine的搜索格式的情况, 将无法控制使用哪一个ViewEngine输出页面.

在ViewBaseResult.ExecuteResult() 方法中, 调用了ViewEngineCollection.Find方法获取ViewEngineResult对象,并调用其中的IView.Render()方法完成View对象的显示.

四.开发自定义ViewEngine

下面通过示例演示如何开发自己的ViewEngine.其中要用到StringTemplate这个模板引擎, 在老赵的的MVC视频教程中也使用的此引擎演示ViewEngine. StringTemplate负责翻译一个模板页上面的占位符(aspx页面中的内嵌代码), 输出HTML.目前在StringTemplate的官方网站上已经提供了针对Asp.Net Mvc的ViewEngine.但是官方的ViewEngine模板没有使用VirtualPathProviderViewEngine基类.下面我将提供一种不能说更好但至少是另一种实现的StringTemplateViewEngine.其中需要使用StringTemplate的模版功能.

1. 实现IView接口

要开发一个自己的ViewEngine, 首先要创建实现了IView接口的类, 在此我们创建了名为StringTemplateView的类

public class StringTemplateView : IView

{

#region 属性 Properties

/// <summary>

/// StringTemplate 对象, 在构造函数中创建

/// </summary>

private StringTemplate StringTemplate

{

get; set;

}

#endregion

#region 构造函数 Constructed Function

private StringTemplateView()

{

//不用于使用不带参数的构造函数

this.StringTemplate = new StringTemplate();

}

public StringTemplateView(StringTemplate template)

{

//null check

if (template == null) throw new ArgumentNullException("template");

//set template

this.StringTemplate = template;

}

#endregion

#region IView 成员

void IView.Render(ViewContext viewContext, System.IO.TextWriter writer)

{

foreach(var item in viewContext.ViewData)

{

this.StringTemplate.SetAttribute(item.Key.ToString(), item.Value.ToString());

}

//为StringTemplate设置HttpContext

this.StringTemplate.SetAttribute("context", viewContext.HttpContext);

//输出模板

NoIndentWriter noIndentWriter = new NoIndentWriter(writer);

this.StringTemplate.Write(noIndentWriter);

}

#endregion

}

StringTemplateView是在StringTemplate视图引擎中View角色的抽象, 所以功能是实现呈现页面的Render方法. StringTemplate的核心功能就是一套自己定义的模板输出引擎, 所以在构造StringTemplateView对象是必须传入一个StringTemplate实例,在Render时只是调用StringTemplate对象的模板输出方法.

2. 实现IViewEngine接口

有了IView对象. 接下来就要实现最核心的IViewEngine接口. 在具体的StringTemplateViewEngine类中, 要返回一个带有StringTemplateView对象的ViewEngineResult.

在我的实现方法中,使用了ASP.NET MVC已经提供的VirtualPathProviderViewEngine类作为我们的基类. VirtualPathProviderViewEngine类实现了IViewEngine接口的方法, 提供了在程序中寻找View物理文件路径的机制, 搜索时要使用在派生类中赋值的搜索路径.

下面是我们的StringTemplateViewEngine类实现:

public class StringTemplateViewEngine : VirtualPathProviderViewEngine

{

private string _AppPath = string.Empty;

#region 属性 Properties

public static FileSystemTemplateLoader Loader { get; private set; }

public static StringTemplateGroup Group { get; private set; }

#endregion

public StringTemplateViewEngine(string appPath)

{

_AppPath = appPath;

Loader = new FileSystemTemplateLoader(appPath);

Group = new StringTemplateGroup("views", Loader);

MasterLocationFormats = new[] {

"/Views/{1}/{0}.st",

"/Views//Shared/{0}.st"

};

ViewLocationFormats = MasterLocationFormats;

PartialViewLocationFormats = MasterLocationFormats;

}

protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) {

return this.CreateView(controllerContext, partialPath, String.Empty);

}

protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)

{

StringTemplate stringTemplate = Group.GetInstanceOf(viewPath.Replace(".st", ""));

StringTemplateView result = new StringTemplateView(stringTemplate);

return result;

}

}

注意首先在我们的StringTemplateViewEngine中,提供了搜索模板文件的路径,即先从View/{controller}中搜索,再从View/Share中搜索. 这样VirtualPathProviderViewEngine基类的方法就可以找到我们.st模板文件的具体路径, 然后使用StringTemplateViewEngine中提供的创建StringTemplateView的方法, 根据具体路径创建StringTemplateView对象.

在一些开源的ViewEngine中,尤其是MvcContrib项目中的ViewEngine都将创建View对象的功能放在一个ViewFactory类中, 个人认为这个更好的设计, 但是由于我们的StringTemplateViewEngine要继承VirtualPathProviderViewEngine, 所以没办法拆分创建View的方法.

至此我们已经完成了StringTemplateViewEngine的全部工作.

3.使用StringTemplateViewEngine

(1)为 .st 模板页增加智能感知

首先做一些准备工作. 因为我们的StringTemplate模板文件后缀是".st", 里面写的大部分都是HTML代码. 默认情况下Visual Studio是不会在编辑.st功能的时候提供智能感知支持的. 但是可以通过如下设置实现:

单击菜单中的"工具"->"选项":

在"文本编辑器"的文件扩展名中, 如图所示的为.st扩展名增加"HTML编辑器".

接下来在.st文件中就可以识别HTML代码了:

(2) 创建公用的菜单模板

StringTemplate引擎支持模板的嵌套, 所以可以讲两个页面公用的菜单栏放在menu.st文件中. 而且我们将此文件放在share文件夹中以便供所有模板页调用. menu.st文件代码如下:

<ul id="menu">

<li><a href="/StringTemplate/HelloST">HelloST</a></li>

<li><a href="/StringTemplate/SharedST">SharedST</a></li>

</ul>

(3) 创建页面模板和Controller

在Controller文件夹中, 创建StringTemplateController用于跳转到我们的模板页:

public class StringTemplateController : Controller

{

public ActionResult HelloST()

{

ViewData["msg"] = "Hello String Template ! ";

return View("HelloST");

}

}

在View文件夹中创建StringTemplate文件夹, 添加一个HelloST.st文件:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="/1999/xhtml">

<head>

<title>在Shared文件夹中的st页面</title>

<link href="../Content/Site.css" rel="stylesheet" type="text/css" /></head>

<body>

<div class="page">

<h1>StringTemplateViewEngine示例程序</h1>

<div id="menucontainer">

$Views/Shared/menu()$

</div>

<div id="main">

$msg$

</div>

</div>

</body>

</html>

示例中的代码十分简单, "$msg$"是StringTemplate的模板语言, 可以识别名称为"msg"的变量. "$Views/Shared/menu()$"也是StringTemplate中的语法, 作用是加载名为menu的模板.

(4) 加载StringTemplateViewEngine 模板引擎

虽然Controller和View文件都建立好了, 但是因为ASP.NET MVC默认的视图引擎是WebFormViewEngine, 但是可以同时使用多个视图引擎, 比如可以为所有".st"后缀名的文件使用StringTemplateViewEngine视图引擎.在Global.asax文件中, 在程序启动时注册我们的ViewEngine:

protected void Application_Start()

{

RegisterRoutes(RouteTable.Routes);

//添加StringTemplate视图引擎

StringTemplateViewEngine engine = new StringTemplateViewEngine(Server.MapPath("/")); ViewEngines.Engines.Add(engine);

}

现在, 访问"localhost/StringTemplate/HelloST" ,就可以看到我们的自定义的模板引擎的输出结果了:

在文章最后会提供本实例附带StringTemplateViewEngine的完整源代码.

五.其他ViewEngine简介

除了自己开发, 目前已经有了很多为ASP.NET MVC提供的ViewEngine:

MVCContrib项目中的ViewEngine:

SparkViewEngine(不推荐)

BrailViewEngine

XsltViewEngine

StringTemplateViewEngine

这是StringTemplate项目为ASP.NET MVC开发的ViewEngine, 官方以及下载网址是

/

另外在著名的MonoRail项目中, 还有一些类似于StringTemplate的页面显示引擎, 虽然都没有为ASP.NET MVC开发专门的ViewEngine, 但是还是很有参考价值的.我们可以用上面介绍的方法, 在页面显示引擎的基础上自己开发ASP.NET MVC的ViewEngine:

MonoRail项目中的三个ViewEngine:

AspNetViewEngine:用传统的.aspx文件做模板, 可以照常使用aspx语法和服务器控件, 但是由于Webform的生命周期和MonoRail完全不同, 有时候会让人觉得别扭, 有部分特性也受到了限制.

NVelocityViewEngine: 用NVelocity做模板引擎, 需要学习VTL语法, 但是使用很简单, 特别是很多java程序员已经熟悉velocity. 简单的语法也强迫程序员把逻辑和界面很好的分离开来, 方便跟美工配合.

BrailViewEngine:基于Boo的模板引擎, Boo是一种语法类似python的.NET语言, 据MonoRail的参考说, Brail引擎是功能最强, 性能最好的选择, 但Boo是一种陌生的语言, 这成了Brail引擎应用的最大障碍.

六.总结

本篇文章详细介绍了ViewEngine相关类, 已经如何开发自己的ViewEngine. 花了2周时间创作完成, 让大家久等了. 说道最近博客园首页的文章问题, 我觉得一篇文章除了要有知识点, 还有能够很好的讲解, 让大家明白比让自己明白更重要.我没有为了速度草草发表文章,就是希望写出来的东西能够有资格发表到博客园首页.

我希望大家都通过自律来建设博客园, 明白分享知识是一件光荣而且快乐的事情!

文章示例代码下载:

/zhangziqiu/AspNetMvc-5-Demo.rar

更多相关推荐:
新学期学习计划

新学期学习计划一新的学期开始了我已经是新学期三年级的学生了我深知我的学习任务又加重了在这一学期中我要更加努力的学习了不能落在其他同学的后面更要处处严格要求自己在学习劳动体育各个方面争取成为其他同学的榜样我为自己...

开学学习计划

开学学习计划学习计划1每天早上620起床用10分钟将头天要背的课文温习12遍640从家出发2上课认真听讲积极发言做好笔记3认真仔细写作业不对答案认真对待每一门课4写完作业后复习当天的内容并预习第二天上课的内容5...

新学期学习计划表

新学期学习计划表

小学生开学学习计划

小学生开学学习计划计划篇在课余背诵46则温习全年古诗31首每天写100字300字的读书重读福尔摩斯这次读书重点是读福尔摩斯对案件的推理过程培养自己的逻辑推理能力其次可以利用课余时间参加班提高英语口语能力篇amp...

开学学习计划

大学一年级学习规划束缚的高中生活结束了迎来了期盼已久的大学大学在多数人脑海里就是自由舒适的代名词它没有老师的严厉管教没有做不完的作业没有一天到晚的课总之感觉是那么地轻松愉快但是我不这么认为父母辛辛苦苦地为我挣取...

六年级开学学习计划

六年级开学学习计划一学习的目标明确实现目标也有保证学习计划就是规定在什么时候采取什么方法步骤达到什么学习目标短时间内达到一个小目标长时间达到一个大目标恰当安排各项学习任务如1静下心来认真仔细的完成老师布置的各项...

开学学习计划

开学学习计划学习计划1每天早上620起床用10分钟将头天要背的课文温习12遍640从家出发2上课认真听讲积极发言做好笔记3认真仔细写作业不对答案认真对待每一门课4写完作业后复习当天的内容并预习第二天上课的内容5...

我的新学期学习计划时间表

我的新学期学习计划时间表随着四十多天的寒假的过去新的一学期已经到来为了更好地利用时间更好地学习还是跟往常一样在周记的第一篇拟一个学习计划时间表吧早上0500起床06000630读语文06300650读英语065...

新学期学习计划范文

新学期学习计划范文计划一说来惭愧上课好几周了居然连一个学习计划都还没有实在惭愧要不是三叶草工作组催着要交学习计划我至今都还在迷迷糊糊中过日子呢忙那只是一个借口而已下面是我这学期的学习计划1把专业课程学好包括操作...

新学期学习计划

新学期学习计划新的学期就要到来了为了使下个学期的学习成绩进步各科成绩优异不偏科我新学期的打算如下一做好预习预习是学好各科的第一个环节所以预习应做到1粗读教材找出这节与哪些旧知识有联系并复习这些知识2列写出这节的...

小学六年级新学期学习计划

六年级新学期学习计划开学第一天一进学校看到洁净如新的校园看到崭新的桌椅和蔼可亲的老师我暗暗下定决心要发奋学习考上理想的中学俗话说逆水行舟不进则退这句话说得不错今后我一定要做到以下几点一做好三定一定学习目标语文9...

关于开学的作文:开学计划(2)

关于开学的作文开学计划2寒假生活眨眼结束了我带着对寒假生活的美好记忆怀着对新学期的向往开始了六年级下半学期的学习生活俗话说一年之计在于春新学期是学习的春天春天是耕耘的季节春天更是播种的季节耕耘和播种需要付出艰辛...

开学学习计划(47篇)