2.8 视图
2.8.1 认识视图
在ASP.NET MVC中,视图就是应用程序的业务层或模型的表示,视图通常就是HTML页面,但是也可以呈现某种能够在Internet上传输的格式,例如JSON、XML、二进制数据、RSS、ATOM,甚至可以是自定义协议。
由于ASP.NET MVC架构提供了一个称为视图引擎的提供程序引擎,因此视图可以采用不同类型的呈现方式,这些呈现方式都是由ASP.NET MVC架构支持的。视图引擎负责根据控制器和操作的名称选择合适的视图。
ASP.NET MVC5架构提供的默认视图引擎为Razor视图引擎,它在浏览器中呈现HTML页面时需要利用文件夹的目录结构,以及CSHTML文件,语法结构为Razor语法。
ASP.NET MVC框架在呈现视图的内容时,视图引擎会根据以下的顺序,从上到下查找需要呈现的视图:
(1)~/Views/{controller}/{action}.cshtml
(2)~/Views/Shared/{action}.cshtml
以上的查找顺序意味着:
首先查找控制器目录,然后再查找Shared目录。
2.8.2 视图的类型
ASP.NET MVC提供了多种视图的呈现方式,包括布局视图、普通视图和分部视图,每种不同的视图具有不同的功能。
1.布局视图
使用Razor视图引擎,我们会发现它不同于.ASPX视图,它没有母版页。但我们可以将项目中的公共视图作为Razor视图引擎的母版使用。布局视图可以被理解为母版页,被其他视图页引用。布局视图为可动态替换视图中部分内容的特殊视图页,布局视图为标准的HTML页面,里面包含特殊的占位标记@RenderBody()或@RenderSection()。
其中@RenderBody()用于呈现子页面的主体内容。
@RenderSection()用于呈现在子页面中的片段内容。
在MVC中,系统默认的布局视图_Layout.cshtml位于\Views\Shared目录,打开此文件,会发现里面有@RenderBody()和@RenderSection()标记,代码如下:
<! DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta charset="utf-8" /> <meta name="viewport"content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title -我的ASP.NET应用程序</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> <div class="container body-content"> @RenderBody() <hr /> <footer> <p>© @DateTime.Now.Year -我的ASP.NET应用程序</p> </footer> </div> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) </body> </html>
布局视图为公共视图,文件名通常以下划线开头。布局视图中可以选择性地使用@RenderBody()或@RenderSection()方法作为布局占位符,但一个布局视图只能有一个@RenderBody(),可以有多个@RenderSection()。
由于布局视图的特殊性,决定了布局视图不能直接被控制器的方法调用,需要被其他视图页引用后才能使用。
2.普通视图
视图页用于呈现业务层的HTML内容,它可以是标准的含有Razor语法的HTML内容,也可以是引用了布局视图的视图。两种呈现方式的参考代码如下。
(1)标准的HTML视图
@{ Layout =null; //指定不需要引用任何布局视图 } <! DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title></title> </head> <body> <div> This page Render at:@DateTime.Now </div> </body> </html>
此种视图不包括任何布局视图,需要在视图页顶部指定“Layout=null”。
(2)引用布局视图的视图
@{ ViewBag.Title ="About"; //指定布局视图 Layout ="~/Views/Shared/_Layout.cshtml"; } <h2>@ViewBag.Title.</h2> <h3>@ViewBag.Message</h3> <p>Use this area to provide additional information.</p>
以上代码展示通过指定"Layout="含虚拟路径的视图的名称"”的方式显示调用的布局视图。如果视图页中没有显示指定的布局视图,则系统会自动调用“~/Views/_ViewStart.cshtml”视图文件中设定的视图页作为布局视图。
3.分部视图
分部视图是一种特殊的公共视图,在设计上用于细分内容,提供模块化的设计,其功能可理解为ASP.NET WebPage中用户控件(UserControl)。分部视图为布局视图或普通视图中的HTML片段,没有HTML中的<head>、<body>等标记。分部视图的作用在于一个视图的创建可以被多个布局视图和普通视图多次调用。分部视图的名称通常以下划线开头,可以存放在“~/Views/”目录下的任何位置。
以下代码为MVC应用程序的登录模块的分部视图代码(文件路径为~/Views/Shared/_LoginPartial.cshtml):
@using Microsoft.AspNet.Identity @if (Request.IsAuthenticated) { using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id ="logoutForm", @class ="navbar-right" })) { @Html.AntiForgeryToken() <ul class="nav navbar-nav navbar-right"> <li> @Html.ActionLink("你好 " +User.Identity.GetUserName() +"! ", "Manage", "Account", routeValues: null, htmlAttributes: new { title ="管理" }) </li> <li><a href="javascript: document.getElementById('logoutForm').submit()">注销</a></li> </ul> } } else { <ul class="nav navbar-nav navbar-right"> <li>@Html.ActionLink("注册", "Register", "Account", routeValues:null, htmlAttributes:new{id="registerLink"})</li> <li>@Html.ActionLink("登录", "Login", "Account", routeValues: null, htmlAttributes: new{id="loginLink"})</li> </ul> }
在布局视图或普通视图中调用分部视图,可以使用如下的两种方法之一:
Html.RenderPartial(string partialViewName) Html.Partial(string partialViewName)
以下代码展示了视图中调用分部视图的例子:
{ ViewBag.Title ="About"; Layout ="~/Views/Shared/_Layout.cshtml"; } <h2>@ViewBag.Title.</h2> <h3>@ViewBag.Message</h3> <! --以HMTL的方式呈现分部视图--> @Html.Partial("_LoginPartial") <p>Use this area to provide additional information.</p>
2.8.3 视图的创建
创建视图的方法有多种,可以使用2.3节中的方法创建视图,也可以使用如下的方式创建视图。
在解决方案浏览器中的文件夹上右击,在弹出的快捷菜单中依次选择“添加(D)”→“MVC5视图页(Rzaor)”(或选其他视图选项),如图2-21所示,然后在弹出的对话框中修改视图的名称为Index,单击“确定”按钮后完成视图的创建。
图2-21 添加视图
2.8.4 强类型视图
ASP.NET MVC提供了一种利用强类型的方法来将数据或对象传递到视图模板中。这种强类型的方法为编码提供了很丰富的智能输入提示信息与非常好的编译时的检查。
如下的代码表示将一个User类从控制器传递到了视图中。
return View(new User(){ Name ="小明", Account ="xiaoming", Birthday ="1990-1-1" });
通过在视图模板文件的头部使用@model语句,视图模板可以识别传入的参数中的对象类型是否是该视图模板所需要的对象类型,我们把这种视图称为强类型视图或者叫基于模型的视图,如显示用户信息的部分视图代码为:
@model User <div>姓名:@Model.Name</div> <div>账号:@Model.Account</div> <div>出生日期:@Model.Birthday</div>
这个例子中的@model关键字告诉模型类型是User类,这让所有ViewData.Model模型的引用变成强类型,而且可以直接访问。Model.Name中的大写字母M开头的Model为传入对象的对象名。若要获得对象的属性值,可以直接使用如下格式提供的方法:
Model.属性
例如,要获取User类的Account属性值,只需调用Model.Account即可。
2.8.5 资源的引用
1.JavaScript及CSS的打包压缩
打包(Bundling)及压缩(Minification)指的是将多个JavaScript文件或CSS文件打包成单一文件并压缩的做法,如此可减少浏览器需下载多个文件才能完成网页显示的延迟感,同时通过移除JavaScript/CSS文件中空白、批注及修改JavaScript内部函数、变量名称的压缩手法,能有效缩小文件的体积,提高传输效率,为使用者提供更流畅的浏览体验。
在ASP.NET MVC5中可以使用BundleTable打包多个CSS文件和JavaScript文件,以提高网络加载速度和页面解析速度。更重要的是通过打包可以解决IE浏览器的31个CSS文件连接的限制。在做ASP.NET项目时很多时候会使用一些开源的JavaScript控件,无形中增加了对CSS和JavaScript文件的引用。如果手工将这些CSS文件合并,会给将来版本升级造成很大的麻烦。于是,我们只好小心翼翼地处理这些CSS文件在页面中的引用。ASP.NET打包是ASP.NET4.5的新功能,在System.Web.Optimization命名空间下,它提供了一些ASP.NET运行性能方面的优化,比如,一个页面可能有很多CSS、JavaScript文件和图片,通过灵活地应用BundleTable类,可以帮助用户将压缩代码优化成一个最理想的文件,然后输出到客户端,从而提高了浏览器下载的速度。
在项目的App_Start文件夹中有一个BundleConfig.cs文件,如图2-22所示。
图2-22 BundleConfig.cs文件的位置
打开此文件,里面自动生成了几个绑定JavaScript脚本和CSS样式的示例,代码如下:
using System.Web; using System.Web.Optimization; namespace LMS { public class BundleConfig { //有关绑定的详细信息,请访问http://go.microsoft.com/fwlink/? LinkId=301862 public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js")); bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( "~/Scripts/jquery.validate*")); //使用适于开发和学习的Modernizr开发版本。然后,当作好生产准备时, //请使用http://modernizr.com上的生成工具来选择所需的测试 bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( "~/Scripts/modernizr-*")); bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( "~/Scripts/bootstrap.js", "~/Scripts/respond.js")); bundles.Add(new StyleBundle("~/Content/css").Include( "~/Content/bootstrap.css", "~/Content/site.css")); } } }
RegisterBundles()方法中的bundles.Add()语句是在向网站的绑定表(BundleTable)中添加Bundle项,包括ScriptBundle和StyleBundle,分别用来压缩脚本和样式表。用一个虚拟路径来初始化Bundle的实例,如“~/bundles/jquery”,这个路径并不真实存在,然后在新Bundle的基础上用Include()方法将要打包和压缩的文件或目录包含进去。
也可以根据需要分类打包脚本和样式表,以此方式创建多个压缩后的虚拟路径,如“~/bundles/jquery”“~/bundles/jqueryval”“~/bundles/modernizr”“~/Content/css”等。
要使用以上的打包脚本,还需要在项目根下的Global.asax文件中注册,即在这个文件的Application_Start()事件中添加代码BundleConfig.RegisterBundles(BundleTable. Bundles)。完整的Application_Start()事件的代码如下:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); //启用脚本及样式表的打包和压缩功能 BundleConfig.RegisterBundles(BundleTable.Bundles); } }
默认情况下,Bundle是会对JavaScript和CSS代码进行压缩打包。系统提供了如下的属性,可以显式地说明是否需要打包压缩:
BundleTable.EnableOptimizations=true;
如果将其设为true,则会进行打包压缩,否则不会进行打包压缩。
需要注意的是,在Web.config中,当compilation配置节点的debug属性设为true时(见图2-23),表示项目处于调试模式,这时Bundle不会将文件进行打包压缩,相当于BundleTable.EnableOptimizations=false,页面中引用的JavaScript和CSS代码会分散在HTML代码中,这样做是为了调试时方便查找问题。
图2-23 调试模式设置
2.对JavaScript及CSS的引用
在视图文件的相应位置调用ScriptRender类和StyleRender类的Render()方法可以输出打包的资源。
(1)输出Javascript脚本方法的原型为:
@Script.Render(params string[] paths)
Script.Render()方法调用的路径为打包后的虚拟路径,如要调用打包后的JQuery脚本,则可以使用如下语句:
@Scripts.Render("~/bundles/jquery")
(2)输出CSS样式表的方法的原型为:
@Styles.Render(params string[] paths)
Style.Render()方法调用的路径为打包后的虚拟路径,如要调用打包后的CSS样式表,则可以使用如下语句:
@Styles.Render("~/Content/css")
在_Layout.cshtml文件中引用JavaScript及CSS的代码如下:
<! DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta charset="utf-8" /> <meta name="viewport"content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title -我的ASP.NET应用程序</title> <! --引用打包样式--> @Styles.Render("~/Content/css") <! --引用打包脚本--> @Scripts.Render("~/bundles/modernizr") </head> <body> <div class="container body-content"> @RenderBody() <hr /> <footer> <p>© @DateTime.Now.Year --我的ASP.NET应用程序</p> </footer> </div> <! --引用打包脚本--> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") <! --定义布局页中脚本所呈现的区域块--> @RenderSection("scripts", required: false) </body> </html>