ASP.NETMVC4INACTION学习笔记第二波-飞外

ASPNET MVC4 视图基础(Views fundamentals)

--忽然发现我的时间不够了,但是我还是完成了

原著:ASP.NET MVC 4 IN ACTION

本人能力有限,尽量将书中的知识浓缩去讲,仔细学过后,然后你再学习其他语言的MVC框架也就大同小异了

本次覆盖知识点:

1. 把数据传递给视图 (Providing data to the view )2. 使用强类型视图 (Using strongly typed views)3. 使用视图助手对象 (Understanding view helper objects)4. 用模版开发 (Developing with templates)

视图(Views)是ASP.NET MVC 应用程序起决定性的一部分--它提供了一个很简洁的方式,在你的应用程序中,逻辑中涉及到展现页面的部分分离出来了。在上一章中的我们的Guestbook应用程序中,我们用Razor模版引擎写了一些简单的视图,稍微了解了一点,最后,我们学习了layouts是如何让你的应用程序中的所有页面保持一致的风格,结束了上章的课程。

没有学过的,上章飞机票:点击开始学习

在本章中,我们将再稍微深入学习一下视图---我们将会研究一下ASP.NET MVC如何呈现视图,然后我们再看下把数据传给视图的其他不同的一些方法。最后,我们再讲一下原来在 ASP.NET MVC 2 中介绍过的一些模版(temlate)特征,为了分析讲解这些特征,我们一开始就会在我们的Guestbook应用程序中添加一个编辑页面。

3.1 视图介绍

视图的职责看似简单。它的目标是与相对应的模型(Model)相关联,并使用对应的模型来呈现内容。由于控制器(Controller)和相关联的服务已经处理了所有的业务逻辑,并且把处理后的结果封装成了模型对象,而视图只需要知道怎样把这个模型转换成HTML来呈现就行了。

尽管这种职责关系的分离可能,对传统的ASP.NET应用程序编程人员来说编写造成了麻烦和困扰,但是视图你还是要编写的。为了保证你的视图不会太复杂而很难去维护,所以对于视图,你还是要小心仔细的,要有意的去设计它.

在我们学习“如何把数据传递给视图”的方法之前,我们先研究一下MVC框架是怎样把视图给呈现的。

3.1.1 选择一个视图去展示

在上一章里面我们通过调用一个控制器里面的一个action中的一个View方法来让页面呈现了。下面这个是我们的GuestbookController控制器里的Create操作:

通过这些代码, Views/Guestbook/Create.csthml 这个视图文件就会被呈现。但是在这个应用程序中,MVC框架怎么知道去呈现这个指定的视图,而不是其他视图,比如说Index.cshtml的呢?

调用View方法将会返回一个 知道怎么去呈现指定的视图的ViewResult对象。当调用View的无参数方法时,MVC框架就会认为你要呈现的视图的名字跟这个方法所在的action(Create)名字一样。之后,MVC框架的ControllerActionInvoker类就会去调用ViewResult类,然后把呈现页面的责任转交给了它。在这个时候,这个框架也会命令ViewEngineCollection去查找要呈现的页面的位置。(回顾一下上一章的内容。这个视图引擎默认会在 Views/ Controller Name 目录和Views/Shared目录)

视图引擎

不同的视图引擎会有它不同的格式去呈现视图,默认 ASP.NET MVC有两个视图引擎-RazorViewEngine和WebFormViewEngine。Razor视图引擎的格式是cshtml文件或者vbhtml文件,然而WebForm视图引擎依旧支持老格式的Web Form视图(aspx页面和.ascx文件)。以前的ASP.NET MVC版本默认都只包括WebForm视图引擎。

为什么在ASP.NET MVC3中要引入新的视图引擎呢?随着ASP.NET 1.0的发行的开始,Web Form就允许把代码和标签写在一个aspx页面里面,但是把C#代码写在aspx页面里面,控件的逻辑写法在通常的开发中严重受到阻碍。取而代之的是,开发者们努力去把所有的逻辑放在了页面的后面(代码后置)。随着ASP.NET 的发布,在aspx文件中增加了数据绑定,其他更新的模块都去适应配合控件开发的模式。

在许多MVC的框架里,视图开发都鼓励和要求代码,直接用标签去写(也就是不用控件),由于ASPX视图引擎在设计时没有达到这个目标,所以ASP.NET团队决定去设计一个全新的视图引擎--把代码写在模版里的一种模式。这是一种更智能的引擎,引擎能够很容易地识别出标签的开始和结束,而且开发者也不用写的那么复杂。

当然你也可以用别的面---所有人都可以查看guestbook,但是只有当前登录的用户可以编辑guestbook entries(guestbook中的一个实体对象,也就是一条记录)。为了可以达到这个目的,我们可以通过GuestbookEntry对象,代码如下:

尽管,这个GuestbookEntry类已经包括了所有我们要在使用GuestbookEntry的页面上展示的数据了,但是它不包括当前登录的用户的信息,或者不能决定视图是否显示Edit超链接。我们需要给视图更多的信息,不能仅仅靠GuestbookEntry来做这个决定。我们可以使用ViewDataDictionary来提供更多的信息,代码如下

public ActionResult Show(int id) {
 var entry = _db.Entries.Find(id); //找到该Id的Entries
 bool hasPermission = User.Identity.Name == entry.name; //如果登陆人的姓名等于entry录入人的姓名,就显示Edit按钮
 ViewData["hasPermission"] = hasPermission;
 return View(entry);
 }

添加到Controllers/GuestbookController.cs这个文件里面

在Controller里面我们可以直接使用ViewData,因为Controller继承ControllerBase类,ControllerBase下面有ViewData属性。我们通过比较GuestbookEntry的name和当前登录用户的name是否一样,然后比较结果放在hasPermission里面,然后视图那边查看hasPermission是true还是false来决定是否显示Edit超链接。

添加Show视图,右键action名称,添加视图,然后直接点添加

视图里面代码如下:

@{

现在我们来运行我们的项目:如果是从我的csdn上的,默认里面有3条数据了。

运行的时候,我们手动在地址栏的后面输入Guestbook/Show/1,其中1是entry的id号,在controller里面我们不是留了一个参数吗?在Asp.NET MVC中,你可以这样传递参数,完整模拟路径如下

http://localhost:端口号/Guestbook/Show/1

跳转后,页面默认如下:因为你还没有登录,所以不会显示Edit超链接

数据库中的表数据一览,目前id只有1,2,3,所以测试的时候,不要输入其他的

①输入地址后,显示效果如下

测试我们先注册一个用户,名字叫 茗洋芳竹,密码叫123456

注册好了,会自动登陆

输入Show/2,因为id=2的信息是 name等于茗洋芳竹,是本人,所以显示了Edit超链接

我猜想,如果你以前做过WebForm的话,聪明的你肯定已经领悟出来什么了.

3.2.2 ViewBag

跟 ViewDataDictionary一样,ViewBag也是把数据传递给视图使用的一种手段.ViewBag使用了C#4.0中的 的dynamic语法.

它不是使用key字符串在集合里存储对象了,你可以在你的controller中简单的设置一下dynamic类型的ViewBag的一个属性.

hasPermission是ViewBag的一个根本不存在的属性,这里名字随便取,因为ViewBag是个dynamic类型的.不懂dynamic的可以查看一下C# dynamic资料

如果采用ViewBag的方式

那么Show页面稍微改下就行了

原本代码是这样的

bool那里面的代码可以不要了,if后面的那个条件,直接改成

就行了.

估计ViewBag你已经会用了,Model从哪里来的,你先不用管,估计你也猜到了,我们在Controller中调用View()方法的时候,加了一个实体对象作为参数.那么这里你就可以用Model来等效于你后台传来的实体对象那样使用就行了.

尽管ViewData和ViewBag的都为解决数据传递视图的问题而提供了很多的灵活性,但是也因此付出了很大的代价.如果你偶尔把一个dynamic属性名称敲写错了,编译的时候是找不到错误的,这些技术所以也有不友好的地方.除此之外,在Visual Studio中 dynamic的属性,编写代码时候是没有提示的,ViewData也没有,你要记得ViewData中的键名称。(尽管有面上,在View方法中我们传递了一个实体.换句话说,也就是换个形式封装设置了ViewData.Model属性了,所以你在视图页面就可以直接使用Model属性了.

我们修改GuestbookController中的Index操作和相对应的视图页面(Guestbook/Index.cshtml)

在Index视图上就是用这个Action的.即使是一个弱类型的WebViewPage类,但是也可以使用ViewData.Model属性的.但是这个属性是一个类型对象,我们需要转换一下结果才能有效的使用.取而代之,我们可以使用@model关键字来使我们的父类WebViewPage T 指定model类型


通过使用@model关键字我们指定了model的类型,现在我们的视图已经继承了于WebViewPage T 而不是WebViewPage了.现在我们已经有了一个强类型的视图了.我们也可以使用@using关键字导入命名空间.在下一节里,我们将要看一下,在一个视图里我们怎样使用model对象来展示信息.

3.2.4 在一个视图里面展示视图model的数据

在一个视图里面展示信息,通常的,你可能使用HtmlHelper对象去辅助让这个model生成html,思考一下下面一个展示了一个完整的Guestbook entry信息的代码,这个代码写在Views/Guestbook/Show.cshtml

 h2 Guestbook Entry /h2 
 dl 
 dt 姓名: /dt 
 dd @Model.Name /dd 
 dt 日期: /dt 
 dd @Model.DateAdded /dd 
 dt 消息: /dt 
 dd @Model.Message /dd 
 /dl 
 p 
 @{
 bool hasPermission = (bool)ViewData["hasPermission"];
 }
 @if (ViewBag.hasPermission) // ViewBag.hasPermission
 { 
 @Html.ActionLink("Edit", "Edit", new { id = Model.Id });
 }
 @Html.ActionLink("返回 Entries", "Index")
 /p 

上面这段代码我相信你们应该能看懂了.看不懂的,在评价里面说一下

当我们在屏幕上展示未编码的用户输入的信息的时候,我们宁可不留出存在多种脚本攻击的可能性.还好这个数据在呈现于屏幕之前已经自动编码了.如果你想展示一个没有编码过的文本,你可以使用Html.Raw方法来使那些信息以纯文本的形式展现.


在login页面里面,我们使用一个视图model对象来表达了一个表单的全部的数据,代码如下

这个登陆页面也非常简单,如下

由于在Log on屏幕里,我们选择一个强类型视图,我们使用了内置的助手去帮助为每一个节点生成html.为了替换掉使用弱绑定来呈现action的参数,我们可以使用基于表达式的HtmlHelper拓展来创建不同类型的input节点,代码如下(Views/Accout/register.cshtml)

@model GuestInfo.Models.RegisterModel
@{
 ViewBag.Title = "注册";
}
 hgroup 
 h1 @ViewBag.Title. /h1 
 h2 创建新帐户。 /h2 
 /hgroup 
@using (Html.BeginForm()) {
 @Html.AntiForgeryToken()
 @Html.ValidationSummary()
 fieldset 
 legend 注册表单 /legend 
 ol 
 li 
 @Html.LabelFor(m = m.UserName)
 @Html.TextBoxFor(m = m.UserName)
 /li 
 li 
 @Html.LabelFor(m = m.Password)
 @Html.PasswordFor(m = m.Password)
 /li 
 li 
 @Html.LabelFor(m = m.ConfirmPassword)
 @Html.PasswordFor(m = m.ConfirmPassword)
 /li 
 /ol 
 input   / 
 /fieldset 
}
@section Scripts {
 @Scripts.Render("~/bundles/jqueryval")
}

其中这段代码

功能类似于

label for=”UserName” 姓名 /label

input id=”UserName” name=”UserName” type=”text” value=”” /

由于这个label和input都可以用表达式(expression)来生成,所以我们不用再记复杂的label和input名称了

HtmlHelper被设计时就支持强类型视图

由于我们的表单都是使用强类型视图生成的,所以我们在controller中可以好好设计action,让这个表单可以post提交上去.

而不是列举每一个input字段作为一个action方法的参数,我们可以绑定所有的参数给同一个view model(我们曾经想要展现的视图)

在这里可以有两个参数,当然你也可以有多个.这里你也看到了,这里有个returnUrl,而不是表单中input节点.面上布局比较简单,我们可以使用EditorForModel方法来生成一个用于编辑信息的表单

①我们在Models/AccountModels.cs里面我们先添加一个类

这个代码和LocalPasswordModel代码一样的,我们复制一下代码改一下名字。

虽然已经有了更改密码的页面了,但是这里我们只是练习一下EditorForModel的用法,还希望不用嫌麻烦

接下来我们在Controller/AccountController.cs用法

添加一个Action

 public ActionResult ChangePasswordPage() {

EditorForModel方法会在所在的view上循环对应model中所有的member(成员),会为每个成员生成editor模版,我自己觉得这个也不怎么常用吧。文本框上面的文字,来自ChangePasswordModel类

但是有时,我们不可能只要这么简单的输出html,为了更灵活,比如说我们调整布局,或者添加更多的html标签等等

当然,你把EditorFor改成Editor也行,只不过有的名称你可能记不住,所以说For系列的模版,例如EditorFor而不是Editor,所以正确性更高

除了Collection和Object模版,其他的模版都呈现的是唯一的值,Object模版循环访问ModelMetadata.Properties集合中的每一项,每一项展现的时候都调用对应的display模版.Collection模版循环访问model对象中的每一项,并为列表中的每一项调用对应的display模版显示。

正如你预料到的,展示模版在浏览器中呈现为一个节点,例如纯文本或者一个锚标签,相反地,editor(编辑)模版展现的是表单中的节点,默认的编辑模版在表3.3都列出来了

3.3.3 查找模版

editor模版和display模版助手方法(helper methods)都是通过名字查找对应的模版.模版的名字的值也是有来源的.模版助手方法根据这个名字,然后用明确的算法找到对应的模版并呈现。一旦匹配找到了,就会立即去呈现内容。模版助手方法在根据下一个模版名字查找之前,会根据明确的路径查找模版。查找的路径是EditorTemplates和DisplayTemplates文件夹。跟控制器找视图有点像.如果一个助手方法在一个明确域(area)的视图里面被使用了,被查找的文件夹可能是:
 Area / ControllerName /EditorTemplates/ TemplateName .cshtml(或者.vbhtml) Area /Shared/EditorTemplates/ TemplateName .cshtml(或者.vbhtml)
如果模版不在这些文件夹里,或者视图不在域里,这个默认的视图查询地址就会被使用
 ControllerName /EditorTemplates/ TemplateName .cshtml(或者.vbhtml)Shared/EditorTemplates/ TemplateName .cshtml(或者.vbhtml)
举个例子:

我们现在要在更改密码页面上展现一个自定义的ChangePasswordModel模版。model我们已经有了,现在我们只需要定义一个更model类型名字(ChangePasswordModel)一样的模版就行了。文件放的路径如下图

左边一个,只给AccountController使用,右边一个,所有的控制器都可以使用,EditorTemplates文件夹是自己建立的

3.3.4 自定义模版(难)
总而言之,有两种理由去创建一个自定义模版

■ 创建一个自定义模版 ■ 重写现有的模版

首先我们看一下在控制器明确的视图文件夹下的模版解决方案规则,因此我们能够很明确的在Shared文件夹下重写一个内置的模版,然后我们在重写控制器明确的视图文件夹里。
解释一下“控制器明确的视图文件夹”:例如GuestController这个就是控制器明确了,名字是Guest,所以我们能找到对应的视图文件夹,例如 Views/Guest/… 这个最终的文件夹路径就是 控制器明确的视图文件夹
打个比方,你可能有一个应用广泛的模板,用于显示的电子邮件地址.然后在一个域或者控制器模版的文件里提供一个模版。
在大多数情况下,模版跟创建一个类型是一样的。ChangePasswordModel模版标签如下
创建一个新的Object.cshtml.里面用了EditorFor模版,每一个被P标签包裹。局部模版有什么好处?
首先,局部模版在视图里面都是通过名称找到的。避开在视图中明确指定哪一个模版被调用的需要,模版可以从model元数据信息中找到。此外模版能够在ViewDataDirectory中获得额外的信息,然而局部模版和其他的页面是获得不到的。这些信息在View.Data.ModelMetadata属性中.只有在ASP.NET MVC中模版才有ModelMetadata属性.在局部模版和视图,这些属性都为null
使用ModelMetadata属性时候,你能够获得从model元数据提供器生成的所有元数据信息。这些包括关于model的model类型信息,属性,和元数据。
Model类型信息包括的属性都在表3.4列出来了
除了一些model类型信息之外,ModelMetadata也包含了其他的metadata,默认的都是来自特性.如图表3.5
在我们自定义模版中,我们研究一下这些model元数据,然后去自定义HTML去呈现。除了在表3.4和3.5列出来的属性之外,ModelMetadata对象暴露了一个IDictory string,object 类型的AdditionValues属性。这个属性包含了一些来自自定义model元数据提供者的额外的元数据信息.举个例子:如果你想展示一个必填的(required)字段,我们只需要在我们的自定义模版中检测这个IsRequired属性。比如说我们要装饰一下我们的实体中的一个DataType.DateTime数据类型的特性,我们可以用一个日期捕获插件来自定义个模版来呈现时间。
实践一下,我们可能要重写一下现有的模版,因为这个现有的Object模版可能适合,可能不适合我们的需求。这个model的元数据不能包括任何样式信息,所以自定义的样式和其他的标签都是通过重写内置的模版完成的。但是很多网站都趋向于标准的通用的用户接口布局,比如说我们通常把一个label放在一个input上面用作标记和提醒用户,或者用一个*来标记这个input是必须输入内容的。我们只需要重写模版,就可以潜移默化地影响整个站点。

比如说,我们可能希望把label放在跟input放在同一行,而不是在一列后右对齐,为了达到这个目的,我们需要重写现有的Object模版

①我们首先在 Views/Shared/下面建立一个EditorTemplates文件夹,添加一个Object.cshtml,代码如下
@foreach (var prop in ViewData.ModelMetadata.Properties
 .Where(pm = pm.ShowForEdit
 !ViewData.TemplateInfo.Visited(pm)))
{ 
 div 
 @if (!String.IsNullOrEmpty(
 Html.Label(prop.PropertyName).ToHtmlString()))
 { 
 div 
 @Html.Label(prop.PropertyName) 
 /div 
 }
 div 
 @Html.Editor(prop.PropertyName)
 @Html.ValidationMessage(prop.PropertyName, "*")
 /div 
 div /div 
 /div 
 
}

②接下来修改Views/Account/Login.cshtml代码

@model GuestInfo.Models.LoginModel
@{
 ViewBag.Title = "登录";
}
 hgroup 
 h1 @ViewBag.Title. /h1 
 /hgroup 
 section  
 h2 使用本地帐户登录。 /h2 
@using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
 @Html.AntiForgeryToken()
 @Html.ValidationSummary(true)
 fieldset 
 legend “登录”表单 /legend 
 @Html.EditorForModel()
 input   / 
 /fieldset 
 p 
 @Html.ActionLink("Register", "Register") (如果你没有帐户)。
 /p 
}
 /section 
 section  
 h2 使用其他服务登录。 /h2 
 @Html.Action("ExternalLoginsList", new { ReturnUrl = ViewBag.ReturnUrl })
 /section 
@section Scripts {
 @Scripts.Render("~/bundles/jqueryval")
}

按F5运行效果,登陆页面如下:

我们创建了一个foreach循环,一层一层地遍历了在编辑时以前应该显示的或者不应该显示的ModelMetadata.Properties属性.展示一个label,editor模版,还有验证信息,把每一个属性放在一个div标签里面.最后我们包括了一个cleaner div重置了浮动样式应用于实现列布局.这个最终效果在图3.5
在一个全局的模版中通过放一个普通的呈现逻辑,我们可以很容易的跨站点为我们的视图,提供标准化display和editor布局.为了达需要自定义化的效果的域,我们要有选择性的重写或者提供一个新模版,通过在一个地方标准化和封装我们的呈现内容的逻辑,我们在一个地方编写了很少的代码就影响了整个站点(site),如果我们想要改变我们的时间插件,我们可以简单的写一个data-time模版,就可以很容易改变我们的站点,让它呈现不同的内容。

3.4 总结

MVC框架可以在视图(页面)里减少很多商业逻辑,不幸运的是,视图给我们带来了很多必须掌握的复杂的东西,为了应付这个复杂度和难点,为了提高视图和其他模块的结合性,我们研究了怎样使用强类型视图和隔离开的视图model,随着分离的视图model的普遍增加,使用模版从这些视图model中获得元数据来呈现内容的概念已经成为可能。在分隔开的视图model中,在你的应用程序中,我们还是要保持视图和model是分离开的理念。
 现在我们理解了视图是怎么工作的了,下一章我们将探讨一下控制器基础

第二章代码:http://download.csdn.net/download/yangyanghaoran/5207734

额外赠送:

临时去除迅雷看看广告的做法【原创】
①打开资源管理器


②打开迅雷看看,播放任意电影,紧接着会有广告

 ③查看资源管理器,多了一个这个进程 XLUEOPS.exe,我们只要在广告播放的30秒内手动 关闭此进程,就可以立即 去掉广告,每次放广告,迅雷看看都会加载此进程

④右键单击它,结束进程,就可以临时去掉广告

其他方法:

如果你装了360安全卫士,直接点一键加速,就可以临时去掉广告


个人觉得3.3.4 自定义模版有点难,看不懂的也不用纠结我。这里只是视图基础,具体的以后再讲

关于ASP.NET MVC4 IN ACTION系列目录地址已经生成:点击查看目录