014_捆绑包与显示模式

捆绑包(Bundlejavascript

   可以组织和优化CSS以及JavaScript文件,是由视图和布局引起浏览器向服务器请求的文件。css

显示模式(Display Modehtml

   针对不一样的设备采用不一样的视图。java

理解默认脚本库

         在建立除Empty之外的任一MVC项目时,Visual Studio都会在Scripts文件夹中添加一组JavaScript库,最主要并经常使用的有:jquery

  • jquery-1.8.2.js:jQuery库,可以使得在浏览器中操做HTML元素变得简单而容易,与HTML标准部分的内建API相比,其优点尤为明显。
  • jquery-ui-1.8.24.js:jQuery UI库,经过HTML元素建立富用户控件,为Web应用程序建立美观的UI,该库创建在jQuery库之上。
  • jquery.mobile-1.1.0.js:jQuery Mobile库,为移动设备建立富用户控件。jQuery Mobile创建在jQuery之上,且只会添加到使用Mobile模板选项建立的项目中
  • jquery.validate.js:jQuery Validation库,执行HTML表单元素的输入验证。
  • knockout-2.2.0.js:Knockout库,将“模型-视图-视图模型”模式运用于Web程序的客户端部分,做用是将Web程序中客户端的数据从显示给用户的元素中分离出来。一般被称为MVC的浏览器。
  • modernizr-2.6.2.js:Modernizr库,用于检测浏览器对HTML5及CSS3的支持状况,可以在支持状况下使用最新功能,而在不支持时能够优雅降级。

如下是Visual Studio及MVC专用库:web

  • jquery-1.8.2.intellisense.js:在视图中编写jQuery代码时,为Visual Studio提供智能感应的功能。
  • jquery.unobtrusive-ajax.js:提供MVC框架渐近式Ajax特性,依赖于jQuery。
  • jquery.validate-vsdoc.js:在编写使用jQuery验证库的代码时,为Visual Studio提供智能感应的功能。
  • jquery.validate.unobtrusive.js:提供MVC框架渐近式验证特性,依赖于jQuery。

   对于Visual Studio及MVC专用的库,不须要咱们作任何事情,Visual Studio会自动使用它们。ajax

   这里列出的都是常规的版本,同时出现的还会有压缩版——通常在发布的时候使用,能够节省不少空间,并减小网络带宽,节约网络资源。数据库

准备示例

项目:ClientFeatures浏览器

项目模板:Basic(基本)缓存

模型类:Appointment.cs

using System;
using System.ComponentModel.DataAnnotations;

namespace ClientFeatures.Models
{
    public class Appointment
    {
        [Required]
        public string ClientName { get; set; }

        [DataType(DataType.Date)]
        public DateTime Date { get; set; }

        public bool TermsAccepted { get; set; }

    }
}

控制器:Home

using ClientFeatures.Models;
using System;
using System.Web.Mvc;

namespace ClientFeatures.Controllers
{
    public class HomeController : Controller
    {

        public ViewResult MakeBooking()
        {
            return View(new Appointment
            {
                ClientName = "Adam",
                Date = DateTime.Now.AddDays(2),
                TermsAccepted = true
            });
        }

        public JsonResult MakeBooking(Appointment appt)
        {
            // 在实际项目中,这里是存储新 Appointment 的语句
            return Json(appt, JsonRequestBehavior.AllowGet);
        }

    }
}

视图:MakeBooking.cshtml

@model ClientFeatures.Models.Appointment

@{
    ViewBag.Title = "Make A Booking";
    AjaxOptions ajaxOpts = new AjaxOptions
    {
        OnSuccess = "processResponse"
    };
}

<h2>Book an Appointment</h2>

<link rel="stylesheet" href="~/Content/CustomStyles.css" />
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>

<script type="text/javascript">
    function processResponse(appt) {
        $('#successClientName').text(app.ClientName);
        $('#successDate').text(processDate(app.Date));
        switchViews();
    }

    function processDate(dateString) {
        return new Date(parseInt(dateString.substr(6, dateString.length - 8))).toDateString();
    }

    function switchViews() {
        var hidden = $('.hidden');
        var visible = $('.visible');
        hidden.removeClass('.hidden').addClass('.visible');
        visible.removeClass('.visible').addClass('.hidden');
    }

    $(document).ready(function () {
        $('#backButton').click(function (e) {
            switchViews();
        })
    });

</script>

<div id="formDiv" class="visible">
    @using (Ajax.BeginForm(ajaxOpts))
    {
        @Html.ValidationSummary(true)
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p>
        <p>Your name: @Html.EditorFor(m => m.ClientName)</p>
        <p>@Html.ValidationMessageFor(m => m.Date)</p>
        <p>Appointment Date: @Html.EditorFor(m => m.Date)</p>
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p>
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms $ conditions</p>
        <input type="submit" value="Make booking" />
    }
</div>

<div id="successDiv" class="hidden">
    <h4>Your appointment is confirmed</h4>
    <p>Your name is: <b id="successClientName"></b></p>
    <p>The date of your appointment is: <b id="successDate"></b></p>
    <button id="backButton">Back</button>
</div>

         上面视图中,两个div元素,一个会在视图第一次渲染时显示给用户,它含有一个已启用Ajax的表单。当表单被递交并在接受到服务器对Ajax请求做出的相应时,应采起的应对方式是隐藏第一个div中的表单,同时显示另外一个div元素,显示预定所肯定的细节。

         视图中link元素中引用的/Content文件夹中的CustomStyles .css文件为自定义添加的CSS文件,内容以下:

div.hidden {
    display: none;
}

div.hidden {
    display: block;
}

         此处但愿建立一个用于复杂视图的典型场景,但又不须要建立一个复杂的应用程序,这就是为何添加的CSS文件只有两盒样式,也是为何对一个十分简单的视图使用一连串的jQuery库的缘由。其关键思想是有许多文件要进行管理。当在编写一个实际程序时,所受到的考验偏偏是须要在视图中处理许多脚本和样式文件。

         如今启动程序看就能够看到效果了:

                         

 

管理脚本与样式表

         上面示例中视图代码中混用了Scripts文件夹中的库、Content文件夹中的CSS样式表、本地的Script元素,还有HTML和Razor标记。可是,按照示例那样写仍是存在着一些隐含的问题,咱们能够将脚本和样式表进行管理以改善。

脚本及样式表加载的资料分析

         在对一个项目进行优化以前,最好是先作一些测量。对于本例使用的是IE11的“F12工具”进行测量。

         启动程序,导航至/Home/MakeBooking,而后按F12键。以后点击“网络”选项卡( ),点击“网络流量捕获”按钮( )。重载浏览器内容(刷新页面)将会获得以下图这样的结果:

 

         下面列出的是从上图看到的一些关键数据:

  • 浏览器对Home/MakeBooking造成了9个请求
  • 2个请求用于CSS文件
  • 6个请求用于JavaScript文件
  • 浏览器发送给服务的有22322字节
  • 服务器发送给浏览器的有642670字节

这些都是最坏状况下的数据,由于在加载以前已经清理了缓存。若是在现实中,浏览器的缓存未清理,则浏览器会经过以前的请求缓冲,对此会有所改善。

         若是不清理缓存,再次加载,则会是下面这种效果:

 

  • 浏览器对Home/MakeBooking造成了9个请求
  • 2个请求用于CSS文件
  • 6个请求用于JavaScript文件
  • 浏览器发送给服务的有4610字节
  • 服务器发送给浏览器的有158315字节

   若是注意观察,将会发现视图下载的JavaScript文件列表中已经从新建立了两个常见的问题。第一个是混用了最小化的和常规的JavaScript文件。这个问题不大,但对开发期间的调试会形成必定的影响,因此,最好不用混用。

   第二个是同时下载了jQuery库的最小化和常规版本。发送这种状况是由于布局也会加载一些JavaScript和CSS文件,而用户缺少相应的手段强制浏览器不用下载它已经拥有的代码。

         后面的内容,将介绍一下如何有控制地获取脚本好CSS文件。更普遍意义上,也会展现如何减小浏览器须要发送给服务器的请求数,以及须要下载的数据量。

使用脚本和样式捆绑包

         将JavaScript和CSS文件组织成捆绑包(Bundle),使其可以做为一个单一的单元进行处理。捆绑包是在/App_Start/BundleConfig.cs文件中定义的。下面是由Visual Studio默认建立的:

using System.Web;
using System.Web.Optimization;

namespace ClientFeatures
{
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                        "~/Scripts/jquery-ui-{version}.js"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*"));

            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                        "~/Scripts/modernizr-*"));

            bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));

            bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                        "~/Content/themes/base/jquery.ui.core.css",
                        "~/Content/themes/base/jquery.ui.resizable.css",
                        "~/Content/themes/base/jquery.ui.selectable.css",
                        "~/Content/themes/base/jquery.ui.accordion.css",
                        "~/Content/themes/base/jquery.ui.autocomplete.css",
                        "~/Content/themes/base/jquery.ui.button.css",
                        "~/Content/themes/base/jquery.ui.dialog.css",
                        "~/Content/themes/base/jquery.ui.slider.css",
                        "~/Content/themes/base/jquery.ui.tabs.css",
                        "~/Content/themes/base/jquery.ui.datepicker.css",
                        "~/Content/themes/base/jquery.ui.progressbar.css",
                        "~/Content/themes/base/jquery.ui.theme.css"));
        }
    }
}

         其中静态方法RegisterBundles会在MVC程序第一次启动时,经过Global.asax中的Application_Start方法调用。RegisterBundles方法以一个BundleCollection对象为参数,经过使用它的Add方法注册新的文件捆绑包。

         也能够分别建立用于脚本和样式表的捆绑包,重要的是将这些文件类型分开,由于MVC框架对这些文件的优化是不一样的。样式是由StyleBundle类表示,而脚本是由ScriptBundle类表示。

         建立一个新的捆绑包,实际上就是在建立StyleBundle或ScriptBundle类的一个实例。它们都有一个构造函数参数,即引用捆绑包的路径。其做用是做为浏览器请求捆绑包内容的一个URL,所以,重要的是要为这些路径使用一个与应用程序所支持的路径无冲突的URL方案。最安全的作法是以~/bundles或~/Content做为起始路径。

         一旦建立了上述的StyleBundle或ScriptBundle对象,就可使用Include方法添加捆绑包所包含的样式或脚本文件的细节。有一些较好的作法,能够参考下面对BundleConfig类进行的修改:

using System.Web;
using System.Web.Optimization;

namespace ClientFeatures
{
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/*.css"));

            bundles.Add(new ScriptBundle("~/bundles/clientfeaturesscripts").Include(
                        "~/Scripts/jquery-{version}.js",
                        "~/Scripts/jquery.validate.js",
                        "~/Scripts/jquery.validate.unobtrusive.js",
                        "~/Scripts/jquery.unobtrusive-ajax.js"));

        }
    }
}

         上述修改中,首先使用~/Content/css路径对StyleBundle进行了修改,并将Include方法的参数改成~/Content/*.css,以可以使该捆绑包包含程序中全部的CSS文件。文件后缀.css前面的星号(*)是一个通配符,表示Content文件夹中的全部CSS文件,但这里忽略了文件的顺序,固然这在此处并不重要。——实际上,在浏览器中,CSS文件的加载顺序是不重要的,所以使用通配符的方式是很好的选择。可是,若是要使用CSS的样式优先规则,则须要分别列出这些文件,以保证顺序的正确。

         ScriptBundle中的路径设置为~/bundles/clientfeaturesscripts,这个捆绑包中使用Include方法逐一指定了须要的脚本文件,并以逗号分隔,缘由是此处只须要部分脚本文件,并关注脚本的加载和执行的顺序。

         注意jQuery库文件的指定方式:~/Scripts/jquery-{version}.js,文件名中的{version}部分相对灵活,由于这样作,会匹配指定文件的任一版本,它会使用程序的配置,选择该文件的常规或最小化版本。

         提示:使用常规版或最小版是由Web.config文件中的compilation元素决定的。当其debug属性被设置为true时,使用常规版;而当debug为false时,则使用最小化版。

         这么写的好处是能够将所使用的库的更新为新版本,而没必要从新定义捆绑包。缺点是{version}标志没法区分同一个文件夹中同一个库的不一样版本。所以,必须确保Scripts文件夹中只有一个版本的库。

运用捆绑包

         使用捆绑包以前,首先要作的是建立视图。固然也能够没有这一步,但这可让MVC框架可以为应用程序执行最大限度的优化。

         建立一个新的文件夹,路径及名称为/Scripts/Home,在该文件夹中添加一个新的脚本MakeBooking.js。这是须要遵照的约定,以便按控制器来组织各个页面的JavaScript文件(即按控制器名(不含“Controller”部分)及动做方法名组织JavaScript脚本的约定):   

    function processResponse(appt) {
        $('#successClientName').text(appt.ClientName);
        $('#successDate').text(processDate(appt.Date));
        switchViews();
    }

    function processDate(dateString) {
        return new Date(parseInt(dateString.substr(6, dateString.length - 8))).toDateString();
    }

    function switchViews() {
        var hidden = $('.hidden');
        var visible = $('.visible');
        hidden.removeClass('hidden').addClass('visible');
        visible.removeClass('visible').addClass('hidden');
    }

    $(document).ready(function () {
        $('#backButton').click(function (e) {
            switchViews();
        })
    });

         上面代码只是将原来视图中的那部分脚本转移到了独立的js文件中。接着就是要修改视图文件了。修改原则是但愿浏览器只请求其所须要的文件,适当地保留须要负责副本的请求,因此能够删除已经建立捆绑包的那些link和script元素,保留惟一的一个指向新建的那个专门的js文件MakeBooking.js的那个script元素,具体以下:

@model ClientFeatures.Models.Appointment

@{
    ViewBag.Title = "Make A Booking";
    AjaxOptions ajaxOpts = new AjaxOptions
    {
        OnSuccess = "processResponse"
    };
}

<h4>Book an Appointment</h4>

<script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></script>

<div id="formDiv" class="visible">
    @using (Ajax.BeginForm(ajaxOpts))
    {
        @Html.ValidationSummary(true)
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p>
        <p>Your name: @Html.EditorFor(m => m.ClientName)</p>
        <p>@Html.ValidationMessageFor(m => m.Date)</p>
        <p>Appointment Date: @Html.EditorFor(m => m.Date)</p>
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p>
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms $ conditions</p>
        <input type="submit" value="Make booking" />
    }
</div>

<div id="successDiv" class="hidden">
    <h4>Your appointment is confirmed</h4>
    <p>Your name is: <b id="successClientName"></b></p>
    <p>The date of your appointment is: <b id="successDate"></b></p>
    <button id="backButton">Back</button>
</div>

         能够在视图文件中引用捆绑包,但最好是建立可以多个视图共享的捆绑包——即在布局中运用捆绑包:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head>
<body>
    @RenderBody()

    @Scripts.Render("~/bundles/jquery")     @RenderSection("scripts", required: false)
</body>
</html>

         捆绑包是分别使用@Styles.Render和@Scripts.Render辅助器方法添加的。为了达到预期目标,咱们须要编辑_Layout.cshtml文件,以使用新定义的捆绑包,删除不想使用的引用:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") </head>
<body>
    @RenderBody()

    @Scripts.Render("~/bundles/clientfeaturesscriptes") 
    @RenderSection("scripts", required: false)
</body>
</html>

         好了,如今能够看看最终生成的HTML,下面是由用于~/Content/css捆绑包的Styles.Render方法生成的结果:

<link href="/Content/CustomStyles.css" rel="stylesheet"></link>

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

         而这些是Scripts.Render方法生成的:

<script src="/Scripts/jquery-1.8.2.js"></script>

<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>

<script src="/Scripts/jquery.validate.js"></script>

<script src="/Scripts/jquery.validate.unobtrusive.js"></script>

 

使用Scripts小节

         到如今为止,该示例还不能很好的工做,缘由是包含在专业的js文件MakeBooking.js中的脚本代码须要依靠jQuery为按钮创建事件处理程序。也就是,jQuery文件要在MakeBooking.js以前加载,但在布局中可以看出实际的加载顺序正好相反,致使视图中的script元素出如今布局中的script元素以前,致使按钮不能正常工做,甚至有的浏览器还会报出JavaScript错误,好比我用的IE11就报出了以下图的错误:

 

         解决上面的问题,能够有两种方式:

一、  将Scripts.Render调用移入视图的head元素;

二、  利用_Layout.cshtml文件中定义的“可选脚本小节(Optional Scripts Section)”——使用这种方式其实就是定义了一个占位符,当视图中有可选脚本小节时,便将视图中的实际代码放在这儿。

下面使用的是第二种方式作的修改,请参考:

@model ClientFeatures.Models.Appointment

@{
    ViewBag.Title = "Make A Booking";
    AjaxOptions ajaxOpts = new AjaxOptions
    {
        OnSuccess = "processResponse"
    };
}

<h4>Book an Appointment</h4> @section scripts{ <script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></script> } <div id="formDiv" class="visible">
    @using (Ajax.BeginForm(ajaxOpts))
    {
        @Html.ValidationSummary(true)
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p>
        <p>Your name: @Html.EditorFor(m => m.ClientName)</p>
        <p>@Html.ValidationMessageFor(m => m.Date)</p>
        <p>Appointment Date: @Html.EditorFor(m => m.Date)</p>
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p>
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p>
        <input type="submit" value="Make booking" />
    }
</div>

<div id="successDiv" class="hidden">
    <h4>Your appointment is confirmed</h4>
    <p>Your name is: <b id="successClientName"></b></p>
    <p>The date of your appointment is: <b id="successDate"></b></p>
    <button id="backButton">Back</button>
</div>

         这样一来,script小节会出如今布局中对Scripts.Render的调用以后,视图专用的脚本也就会在jQuery以后才加载,按钮元素也就能正常工做,且不会再报相似以前那种错误了。

         这里仍是要提醒一下,在使用捆绑包时,这是常常会出现的错误,有必要对其加以重视,这也是为何专门把这一问题介绍一下的缘由。

注:关于可选脚本小节,可参考“视图”一章内关于“对Razor视图添加动态内容”中的“使用分段”小节的介绍,理解分段的概念及其工做原理。

修改后的资料分析

         如今清理一下缓存,并导航到/Home/MakeBooking,看看这一调整的效果:

 

         下面是主要信息的摘要:

  • 浏览器对/Home/MakeBooking造成了8个请求(主要请求项)
  • 2个用于CSS文件的请求
  • 5个用于JavaScript文件的请求
  • 从浏览器发送给服务的内容有23237字节
  • 从服务器发给浏览器的有496183字节

对比以前的642670字节,大约减小了1/3。

若是将程序从调试模式切换到部署配置时,将会更明显,具体作法是将Web.config文件中compilation元素上的debug属性设置为false便可:

  <system.web><compilation debug="false" targetFramework="4.5" /></system.web>

         此时再次重复上述步骤,将获得以下结果:

 

         如下是摘要:

  • 造成的请求为4个
  • 1个请求用于CSS
  • 2个请求用于JavaScript
  • 浏览器发送给服务的内容有1288字节
  • 服务器发送给浏览器的内容有126607字节

   此次对CSS和JavaScript文件的请求变少了,缘由是MVC框架在部署模式中,将一系列样式表和JavaScript文件联系在一块儿,并作了最小化,以使一个捆绑包中的内容可以经过一个单一的请求进行加载。若是查看程序渲染的HTML,就能明白是怎么实现的了:

   Styles.Render方法生成的结果:

<link href="/Content/css?v=6jdfBoUlZKSHjUZCe_rkkh4S8jotNCGFD09DYm7kBWE1" rel="stylesheet"/>

         Scripts方法生成的结果:

<script src="/bundles/clientfeaturesscripts?v=KyclumLmAXQGM1-wDTwVUS31lpYigmXXR8HfERBGk_I1"></script>

         这些长长的URL以一个单一的数据库的形式,请求了一个捆绑包的内容。MVC框架对CSS数据所采起的最小化与JavaScript文件是不一样的,这是为何将样式表和脚本分别放在不一样捆绑包中的缘由。

         虽然还能够继续优化,但对于咱们这样的一个简单的程序,这已经足够了,好比若是将MakeBooking.js文件也添加到捆绑包中,还能够再消除一个请求,但这已经意义不大了。一开始那种视图专用的内容与布局的代码混合在一块儿的作法是提倡的,若是在更复杂的系统中,也会会建立第二个捆绑包,让它包含更多的自定义代码,但这里只是一个简单的示例程序,优化的准则是要清楚到什么程度为止。——目前,对应这样的一个示例程序已经达到目标了。

面向移动设备

         移动设备和桌面设备有着很大的区别,最容易引发问题的是触摸交互和受限制的屏幕尺寸。

         更多的移动开发方面的知识不是咱们这里关注的,在此,只须要作些了解便可。

观察应用程序

         开发一个移动设备的程序最好是从零开始创建一个新项目。但这里采起的办法是在原示例项目(ClientFeatures)基础中添加支持。

         首先,须要一个可以模拟移动端展现的程序的工具,书中推荐的是Opera Mobile Emulator(Opera移动设备模拟器),该模拟器是免费的,能够从www.opera.com/developer/tools/mobile上下载。固然也可使用Windows Phone、Android以及Blackberry等,但这些每每都很慢,用起来也很痛苦,由于它们都不是仅模拟浏览器,而是模拟了整个移动操做系统。

         我在学习的时候下载的是“Opera Mobile Classic Emulator 12.1 for Windows”版本。安装完成后启动,并将模拟器的设置使用HTC Hera的配置。导航到/Home/MakeBooking将会看到结果以下:

 

使用移动专用的布局和视图

         因为程序如此之简单,以致于没有什么丑陋的地方须要修改,只是它并无在触摸屏上以易于操做的方式显示。后面,简单介绍如何建立移动专用版本的视图和布局,以方便对其内容的调整来适应目标设备。其实,这个过程很简单,须要作的只是建立其名称带有.Mobile的视图和布局便可:

         视图:MakeBooking.Mobile.cshtml(注意,在扩展名前面加了.Mobile)

@model ClientFeatures.Models.Appointment

@{
    ViewBag.Title = "Make A Booking";
    AjaxOptions ajaxOpts = new AjaxOptions
    {
        OnSuccess = "processResponse"
    };
}

<h4>This is an MOBILE View</h4>

@section scripts{
    <script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></script>
}

<div id="formDiv" class="visible">
    @using (Ajax.BeginForm(ajaxOpts))
    {
        @Html.ValidationSummary(true)
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p>
        <p>Name: </p><p>@Html.EditorFor(m => m.ClientName)</p>
        <p>@Html.ValidationMessageFor(m => m.Date)</p>
        <p>Date: </p><p>@Html.EditorFor(m => m.Date)</p>
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p>
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p>
        <input type="submit" value="Make booking" />
    }
</div>

<div id="successDiv" class="hidden">
    <h4>Your appointment is confirmed</h4>
    <p>Your name is: <b id="successClientName"></b></p>
    <p>The date of your appointment is: <b id="successDate"></b></p>
    <button id="backButton">Back</button>
</div>

         注意,为了可以使input元素的标签可以单独显示,上面对其作了一点改动。另外也对h4标题元素的内容作了调整,以使其更加醒目。

         MVC框架可以智能识别并运用移动视图。好比若是使用桌面浏览器,将使用/Views/Home/MakeBooking.cshtml视图;若是是移动浏览器模拟器,则将看到由/Views/Home/MakeBooking.Mobile.cshtml视图渲染的HTML,如图:

 

         MVC检测浏览器的方式是依赖于.NET框架包含的一组文件,这组文件是在相似C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Browsers目录的位置的,对于不一样的系统会有些不一样。该目录中有一系列文件,其中的每个都与一个移动浏览器相关联。当MVC框架接收到移动模拟器的请求时,会使用“用户代理”字符串,这种用户代理字符串是全部浏览器都会发生的,因而便能肯定该请求发自移动设备,也就是为何会自动使用MakeBooking.Mobile.cshtml视图了。

         上述目录中的文件大致参考下图:

 

建立自定义显示模式

         默认状况下,MVC框架只检测移动设备,并将其余方面的各项工做做为一个单一的类别加以处理。若是需对不一样种类的设备采起具体的响应,则须要建立本身的显示模式。

         这里选择使用Amazon的Kindle Fire配置资料,它是Opera Mobile Emulator中自带的一种配置。因为它是平板设备上的一款浏览器,所以,它发生的用户代理字符串与.NET Framwork指望的从Opera Mobile不一样,所以默认将会使用标准的MakeBooking.cshtml视图。

         下面经过对Global.asax文件进行修改,以使其可以为Opera Tablet(指平板电脑的Opera浏览器)建立一个新的显示模式:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Web.WebPages; namespace ClientFeatures
{

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("OperaTablet") { ContextCondition = (context => context.Request.UserAgent.IndexOf("Opera Tablet", StringComparison.OrdinalIgnoreCase) >= 0) }); 
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

         经过上面的修改,MVC框架将查找诸如/Views/Home/MakeBooking.OperaTablet.cshtml的视图。

提示:MVC框架会依次检查DisplayModeProvider.Instance.Modes属性所返回的集合中的每一种显示模式,并在发现其ContextCondition表达式返回true时,中止查找。有一个默认的备用显示模式可以匹配任何请求,并使用默认视图。须要确保显示模式可以在该备用模式以前获得查询,因此,上述代码中使用了Insert方法将对象放到了零索引的位置。

         为了利用这一显示模式,此处添加了一个新的视图文件:MakeBooking.OperaTablet.cshtml:

@model ClientFeatures.Models.Appointment

@{
    ViewBag.Title = "Make A Booking";
    AjaxOptions ajaxOpts = new AjaxOptions
    {
        OnSuccess = "processResponse"
    };
}

<h4>This is the OPERA TABLET View</h4>

@section scripts{
    <script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></script>
}

<div id="formDiv" class="visible">
    @using (Ajax.BeginForm(ajaxOpts))
    {
        @Html.ValidationSummary(true)
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p>
        <p>Name: </p><p>@Html.EditorFor(m => m.ClientName)</p>
        <p>@Html.ValidationMessageFor(m => m.Date)</p>
        <p>Date: </p><p>@Html.EditorFor(m => m.Date)</p>
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p>
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p>
        <input type="submit" value="Make booking" />
    }
</div>

<div id="successDiv" class="hidden">
    <h4>Your appointment is confirmed</h4>
    <p>Your name is: <b id="successClientName"></b></p>
    <p>The date of your appointment is: <b id="successDate"></b></p>
    <button id="backButton">Back</button>
</div>

         下面是修改后的效果:

 

         到这里,已经简单地介绍了如何建立自定义显示模式,虽然上图中的显示仍是不很近人意,但也能明确地说明了其原理。

注意:在建立自定义显示模式时要当心。它很容易捕获不正确的客户端请求,或漏掉在某个特定类型客户端上运行不一样版本浏览器之间的一些微妙的差别,建议用一系列设备进行完全的测试。

相关文章
相关标签/搜索