《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

MVC默认模板的视觉设计从MVC1到MVC3都没有改变,比较陈旧了;在MVC4中做了升级,好看些,在不同的分辨率下,也能工作得很好,但是HTML和CSS都是自定义的,改造起来,成本很高,也不够理想;

在MVC5中,采用了比较流行的Bootstrap框架,有很高的接受度,改造起来,成本也降低很多。当然,本书不会着重讲解Bootstrap框架。

模板页,是MVC视图的重要组成部分,MVC中,约定视图都存放在/Views目录中,我们来看一下它的结构:

《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

首先会发现/Views/目录中,又有一个Web.config文件。

    1. 根目录下的web.config文件的作用域是整个项目,例如项目需要加载哪些程序集,是否使用Debug模式,配置数据库连接字符串,绑定webservice,指定错误页面,配置密钥信息,等等;
    2. /Views目录下的web.config文件的作用域是视图,文件的内容是对视图引擎的配置。如果在项目中引入了area的概念,那么,每个area都会包含一个特别的web.config,这样,就可以对每一个area作一些特别的控制;

    其次是_ViewStart.cshtml文件。

      1. 这个文件的作用域是它所在目录及其子目录中的视图,它的代码先于同目录下任何视图代码的执行,也可以递归地应用到子目录下的任何视图;
      2. 同样,可以在子目录中建立新的_ViewStart.cshtml,来覆盖上级目录中_ViewStart.cshtml的代码;
      3. 这个文件指定了默认的Layout属性,这个属性默认应用到其作用域下的任何视图,从而避免了冗余的配置,提高可维护性;
      4. 由于它先于任何视图执行,所以可以通过修改视图的Layout属性来覆盖默认配置;

      接下来是Shared目录,它用来存放可重复使用的视图以及局部视图。

        1. 存放布局视图模板,_Layout.cshtml是_ViewStart.cshtml文件中默认指定的布局文件;
        2. 存放错误显示模板,Error.cshtml是默认的错误显示视图;
        3. 可以添加自定义的404页面;
        4. 可以添加局部视图文件;

        最后是Home目录,它是根据MVC的约定创建的HomeController控制器的视图模板目录。

          1. Index.cshtml,About.cshtml,Contact.cshtml也分别对应了HomeController控制器中的Index,About和Contact操作;
          2. 可以在控制器视图的代码窗口中任意位置点击鼠标右键,选择[Go To Controller]项或使用快捷键[Ctrl + M, Ctrl + G]快速跳转到对应控制器的代码文件,这也是MVC约定的利好之一;至于为何不直接跳转到相应的操作上,这个就相对复杂一些,因为控制器的操作,是可以有很多重载的;

          说到View层的改造,我们就不得不说说Razor,Razor是从MVC3.0RTM版本开始引入的,Razor一经推出就深受所有ASP.Net开发者的喜爱:

          首先,它是一个视图引擎。它能够理解如何去渲染一个路由作为Url,允许向网页中嵌入基于服务器的代码(Visual Basic 和 C#)的标记语法,并渲染基于服务器的代码能够创建动态内容,可以根据不同的模型属性类型来渲染不同的控件标签。在网页加载时,服务器在向浏览器返回页面之前,会执行页面内的基于服务器代码。由于是在服务器上运行,这种代码能执行复杂的任务,比如访问数据库;

          其次,它是一个模板视图引擎。它支持在一个模板中渲染另外的局部模板,比如使用频率很高的页面头部和脚部,可以抽出来作为局部模板,增强可重用性;

          最后,它是轻量级的模板视图引擎。Razor在减少代码冗余、增强代码可读性和VS智能感知方面,都有着突出的优势。相比Web Pages漫长的生命周期路径而言,Razor简直就是轻装上阵的少年;

          想要了解更多关于Razor的语法信息,请访问:

          http://www.w3school.com.cn/aspnet/razor_intro.asp

          以及两个短小的视频帮助快速上手:

          https://docs.microsoft.com/zh-cn/aspnet/mvc/videos/mvc-3/mvc-3-razor-view-engine

          https://docs.microsoft.com/zh-cn/aspnet/mvc/videos/mvc-3/mvc-3-razor-helpers

          看到这里,您一定已经迫不及待的想要动手了。下面,我们通过一些对视图的修改,来深入了解Razor能给我们带来什么惊喜。

          一、修改页面标题

          有过HTML开发经验的读者,都知道页面标题,是在HTML页面的<title>...</title>标签中指定的。

          默认情况下,MVC视图的标题为[xxx] - My ASP.NET Application,我们想把“ - My ASP.NET Application”替换为“ - Honor Shop”。

          查找顺序是由内而外,因为内层配置会覆盖外层,所以我们先看Home目录中的视图文件,有没有指定标题。默认是没有的。

          接下来看Home目录中,有没有建立新的_ViewStart.cshtml,因为它也可以覆盖全局配置。默认也是没有的。

          接下来再找上级目录,也就是/Views目录中的_ViewStart.cshtml,默认是可以找到的,这个文件里默认指定的Layout是"~/Views/Shared/_Layout.cshtml",其中“~/”代表虚拟根目录,是相对路径的表示方法,它指向了/Views/Shared/_Layout.cshtml文件。

          打开_Layout.cshtml文件,我们可以看到<title>@ViewBag.Title - My ASP.NET Application</title>,可能现在还不太明白@ViewBag.Title是什么意思,不过先不用管它,总之,格式看起来很像“[xxx] - My ASP.NET Application”。

          我们把<title>@ViewBag.Title - My ASP.NET Application</title>修改为<title>@ViewBag.Title - Honor Shop</title>。

          运行一下,可以看到标题栏已经变成“[xxx] - Honor Shop”的格式了。

          接下来,我们来看看[xxx]是从哪儿来的。还是按照刚才的顺序,这次比较幸运,在控制器视图文件里就发现了一些端倪。每个视图文件的开头都有如此一段代码:

          @{ ViewBag.Title = "xxx"; }

          有的同学会疑惑,这是个什么鬼,HTML里面,没见过啊。这里,结合刚才我们用到的<title>@ViewBag.Title - My ASP.NET Application</title>一起来说明一下,首先,我们使用的是Razor视图引擎,'@'符号,在Razor中有特殊的含义,它标明一段服务器端代码的开始。

          C# 的主要 Razor 语法规则

          Razor 代码封装于 @{ ... } 中

          行内表达式(变量和函数)以 @ 开头

          代码语句以分号结尾

          字符串由引号包围

          C# 代码对大小写敏感

          C# 文件的扩展名是 .cshtml

          C# 实例

          <!-- 单行代码块 -->
          @{ var myMessage = "Hello World"; }

          <!-- 行内表达式或变量 -->
          <p>The value of myMessage is: @myMessage</p>

          <!-- 多行语句代码块 -->
          @{
          var greeting = "Welcome to our site!";
          var weekDay = DateTime.Now.DayOfWeek;
          var greetingMessage = greeting + " Here in Huston it is: " + weekDay;
          }
          <p>The greeting is: @greetingMessage</p>

          至于ViewBag.Title = "xxx";,代码中并没有ViewBag的定义,首先想到它会不会是Razor内置的东东,要么,就是MVC提供的东东,但要解释清楚它,目前还有点复杂,这里讲解会牵扯的东西比较多,为了保持对视图的改造的流畅性,我们后面再介绍它。但现在可以猜测的出它是一个服务器端对象,并且它有一个Title属性。虽然这个猜测有点偏激,姑且先这么理解它吧。

          那么,书归正传,就尝试替换一下吧,看看效果,正如所料,[xxx],被替换掉了。

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          也有细心的同学,会发现我的标题栏里的图标怎么于自己的不一样,下面我们就来更改标题栏的图标吧。

          二、修改标题栏图标

          学习过HTML的读者,都知道这是一个比较基础知识,我们不难发现,在项目的根目录下,“躺着”一个名叫favicon.ico的文件。对了,就是它,图标文件的扩展名为.ico,读者可以从网上随意下载一个ico文件,来替换它。我为Honor Shop精心设计了一个,简洁又富有科技感的图标:)自我陶醉一下。

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          也可以通过在html的<head>...</head>标签中加入如下代码来改变favicon的路径及名称。

          <head>
          <title>@ViewBag.Title - Honor Shop</title>
          <link rel="icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />
          <link rel="shortcut icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />
          </head>

          三、修改页脚

          先修改页脚,不为别的,代码少,改起来简单:P,在页面底部,很轻松就找到了:

          <footer>
          <p>© @DateTime.Now.Year - My ASP.NET Application</p>
          </footer>

          简单修改为:

          <footer>
          <p>© @DateTime.Now.AddYears(-3).Year - Honor Shop</p>
          </footer>

          运行看看,效果实现,也简单使用了一下在Razor中进行服务端编码的快感。

           

          不过这时发现,好像很多地方都用到了“Honor Shop”这段字符串,这是我的网站的名字,可以预料,之后,还会有很多地方会用到。当然,方法有很多了,比如写入配置文件,比如写入数据库,等等。这里为了折腾,暂时不考虑这些方法,既然我们现在是对视图的修改,本着学习的目的,先在视图层面想办法,比如在布局视图中加一个变量,然后,视图里就可以引用这个变量了,也比四处硬编码来得爽快。

          我们现在_Layout.cshtml的顶端加入如下代码:

          @{ var SiteName = "My Honor Shop"; }

          接着,我们对页面标题进行更新:

          <title>@ViewBag.Title - @SiteName</title>

          再更新页脚

          <p>© @DateTime.Now.AddYears(-3).Year - @SiteName</p>

          运行一下看看,哎哟,不错哟。

          不过,这里有个问题,如果我们的项目中,使用了多个布局文件,那不是要在每个布局文件中都定义一遍SiteName?而且,经过试验,在控制器视图中,也没有办法直接使用@SiteName,因为控制器视图与_Layout视图并没有继承关系。

          这里提出第一个方案,还记得_ViewStart先于其他视图运行,它的代码里只是指定了一个Layout属性,别的什么都没做。但是这个Layout却可以在控制器视图中进行重写覆盖,由此灵感而发,这个Layout属性到底是谁的属性?在Layout上点击鼠标右键选择[Go To Definition]项或者使用快捷键[F12]跳转到Layout的声明处。

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          可以看到,Layout是一个抽象类StartPage的属性,既然这个类是一个抽象类,那么它就不能被实例化,但不管是哪个类继承自它,也就同样继承Layout属性,既然Layout能够在其他视图中使用,那么与Layout平起平坐的其他属性,肯定也可以。所以,第一眼就瞄到了PageData这个属性,它是一个字典,Key是object类型,Value是dynamic类型,看起来,都挺合适的。于是乎,在_ViewStart.cshtml中做些手脚:

          @{
          /* 在PageData中添加站点名称键值对 */
          PageData.Add("SiteName", "My First Solution 4 Page Title - Honor Shop");

          Layout = "~/Views/Shared/_Layout.cshtml";
          }

          以页面标题为例,对视图中所有使用@SiteName的地方进行更新:

          <title>@ViewBag.Title - @PageData["SiteName"]</title>

          运行一下,效果如同期望一样,在布局视图和控制器视图中,都可以使用:

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          第二个方案就是我们之前还很朦胧的ViewBag,与查看Layout属性的方法一样,故技重施,跳转到ViewBag的声明处。

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          可以看到,ViewBag是一个抽象类WebViewPage的属性,而且是动态属性,那么如法炮制,再对_ViewStart.cshtml中做些手脚:

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          但是很遗憾,在_ViewStart中不能操作ViewBag属性。这是为什么呢?再仔细看看上面两个抽象类,原来他们分别在不同的命名空间,也就是说,_ViewStart和控制器视图,处于不同命名空间。第二个方案宣告失败,放弃,就是这么随性:D

          不过于此同时,我们还是有所收获的,在返回头来看看第二个抽象类WebViewPage,尤其是它内部声明的一系列属性,我可以很负责任的告诉你,你的整个.NET MVC生涯都是在与它们打交道,不信?你等着瞧……

          关于页脚的修改,暂时告一段落,下面来修改导航栏吧。

          四、修改导航栏

          知己知彼方能百战不殆,我们先看看导航栏原来长得什么样子。

          <div class="navbar navbar-inverse navbar-fixed-top">
          <div class="container">
          <div class="navbar-header">
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          </button>
          @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
          </div>
          <div class="navbar-collapse collapse">
          <ul class="nav navbar-nav">
          <li>@Html.ActionLink("Home", "Index", "Home")</li>
          <li>@Html.ActionLink("About", "About", "Home")</li>
          <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
          </ul>
          </div>
          </div>
          </div>

          忽略掉繁杂的HTML标签和各种样式,标重点,第9、13、14、15行。第13~15行很相似,只是参数的内容不同,第9行与其它几行,都是以@Html.ActionLink开头,参数个数不同。看来都是使用了Html的ActionLink的重载方法。

          等等,'@'符我们说过了,表明后面要接服务端代码了,那么,Html又是什么鬼?我们还是跳转到声明处看看吧

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          首先,它是一个抽象类WebViewPage<TModel>的属性,WebViewPage<TModel>类又继承自WebViewPage类,WebViewPage类就是刚才我们看到的声明了ViewBag的类,兜兜转转啊,不过,WebViewPage类中也声明了一个Html,只不过是HtmlHelper<object>类型,这里的Html是HtmlHelper<TModel>类型,其实它们两个都是同根同源的,都是HtmlHelper<TModel>类的实例。同时,也可以看出,两个WebViewPage,一个是强类型的,一个是弱类型的。继续追踪HtmlHelper<TModel>类,它继承自HtmlHelper类,具有两个构造函数,还有两个只读属性,没有方法声明,那么,方法应该是都在HtmlHelper基类中声明的了,跟进去一看,大跌眼镜,虽然有声明了一堆方法,但是居然没有我们期待已久的ActionLink方法,此刻,必须要想到扩展方法,如果没有想到,那么请恶补C#的相关细节。返回到_Layout.cshtml,直接跳转到ActionLink方法的声明,眼前一亮,原来都在这里:

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          大家可以展开类及方法上的Summary信息来了解一些信息。从类名上可以看出,这是一个针对链接的扩展类。从方法名可以看出,链接可以分为两种,一种是ActionLink,另一种是RouteLink。RouteLink可以简单理解为通过路由(不是通常所说的路由器阿)跳转,用到时再详细介绍。我们先来看ActionLink。

          ActionLink有10个重载,每个都讲,也是比较辛苦的,毕竟我也很懒……我们就拿导航栏中第9行代码使用的重载来做一个说明吧。

          //
          // 根据指定的链接文字,操作名称,控制器名称,路由参数对象和html属性对象,
          // 返回一个锚点元素(也就是html中的a标签),
          // MvcHtmlString是一个特殊的字符串,是经过Html-encoding的html字符串,这点很重要。
          //
          public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper
          // 锚点标签中显示的文字
          , string linkText
          // 控制器中操作的名称
          , string actionName
          // 控制器名称
          , string controllerName
          // 包含路由参数的对象,通过反射检测routeValues对象的属性获取路由参数。
          // 通常使用对象初始化器语法创建routeValues对象。
          , object routeValues
          // 一个包含html属性列表的对象
          , object htmlAttributes);

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          目前,我们也只能是修改第一个参数,动手:

          <div class="navbar navbar-inverse navbar-fixed-top">
          <div class="container">
          <div class="navbar-header">
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          </button>
          @Html.ActionLink(@PageData["SiteName"] as string // @PageData["SiteName"]的值是dynamic类型
          , "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
          </div>
          <div class="navbar-collapse collapse">
          <ul class="nav navbar-nav">
          <li>@Html.ActionLink("首页", "Index", "Home")</li>
          <li>@Html.ActionLink("关于我们", "About", "Home")</li>
          <li>@Html.ActionLink("联系我们", "Contact", "Home")</li>
          </ul>
          </div>
          </div>
          </div>

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          这样就完成了对导航信息的基本修改。

          现在我们可以初步体会到,@Html.ActionLink可以帮助我们生成一个锚点元素,它是.NET MVC视图引擎提供的一个辅助方法。

          Html就是对HtmlHelper的封装,它能帮助我们生成页面上所需要的各种元素。

          但我现在又想把我辛辛苦苦设计的Logo放上去,替换干巴巴的“Honor Shop"文字。

          五、添加带链接的图片

          首先,将制作好的Logo文件(logo.png)拷贝到/Content/Images/下。将原来的锚点代码注释掉。

          @*@Html.ActionLink(@PageData["SiteName"] as string // @PageData["SiteName"]的值是dynamic类型
          , "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })*@

          Razor中使用@*....*@来注释,看起来,还挺有喜感的。

          接下来,我们先立个目标,最终要生成一个什么样的html,能够满足我们添加的Logo的需求。

          <a href="/Home/Index" class="navbar-brand" style="padding-top: 0px; padding-bottom:0px;">
          <img src="/Content/Images/logo.png" alt="Honor Shop" title="Honor Shop" style="height: 100%; background-color:white;">
          </a>

          这里没有什么特别的,都是一些Html和CSS的基础的东西,不是本书的重点,不多介绍,看看效果。

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          目前,它可以很好的工作,但它是脆弱的。思考一个问题,如果,我们的应用,并没有部署在网站的根目录,或者修改了路由的定义,那么,锚点的href和图片的src的值,都有可能把浏览器导航到一个网站上并不存在的资源处。再做一个假设,我们的应用里有很多这样的锚点加图片的元素,他们在编译时,并不会报告错误,应用将变得非常难以维护。

          更好的办法就是可以通过路由来计算路径,这样就可以有效的解决上面提出的部署位置和修改路由的问题了。

          .NET Web应用为我们提供了一套路由机制,可以为我们计算路由路径,其核心就是RouteTable,我们可以通过

          RouteTable.Routes.GetVirtualPath(RequestContext requestContext, RouteValueDictionary values).VirtualPath;
          RouteTable.Routes.GetVirtualPath(RequestContext requestContext, string routeName, RouteValueDictionary values).VirtualPath;

          这两个重载方法来计算路径,是不是很开心,那么来看看参数列表,我们是不是都具备:

          第一个参数,requestContext,不用操心,视图引擎已经为我们提供了,可以通过this.ViewContext.RequestContext来获取;

          第二个参数,values,他的类型是RouteValueDictionary,是一个字典,我们可以通过这个字典,提交路由所需的参数;

          第三个参数,routeName,可以用来指定我们需要使用哪条路由;

          路由是MVC的一个重要机制,更是ASP.NET核心框架的一部分,还记得我们在本境第二节中介绍App_Start目录时,提及到RouteConfig,这个类就是用来管理配置路由的,并且在应用启动时,Global中的Application_Start方法中会调用它的RegisterRoutes方法来注册路由。

          ASP.NET MVC框架中的路由主要有两个用途:

          1. 匹配传入的请求(该请求不匹配服务器文件系统中的文件或资源),并把这些请求映射到控制器操作。

          2. 构造传出的URL,用来响应控制器操作。

          现在我们打开RouteConfig,看看默认提供的路由是什么样的。

          using System.Web.Mvc;
          using System.Web.Routing;

          namespace HonorShop.Web
          {
          public class RouteConfig
          {
          public static void RegisterRoutes(RouteCollection routes)
          {
          routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

          // 注册一条路由配置
          routes.MapRoute(
          // 定义路由的名称,名称可以自定义,但在路由表中不可重复。
          name: "Default",
          // 定义一条url访问的模式
          url: "{controller}/{action}/{id}",
          // 指定路由的默认值
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
          );
          }
          }
          }

          通过上面的代码,可以看出,注册一条路由所需要的参数,以及他们的含义。

          对于构造首页的url,我们知道controller是Home,action是Index,id是可选的,可以不提供。

          但是对于图片的url,就没有那么幸运了,因为图片是静态资源,并没有明确的controller和action。很显然,默认提供的路由,不适合我们。不如,我们动手来添加一条路由吧:

          // 注册一条路由指向图片资源
          routes.MapRoute(
          // 定义本条路由的名称为Images
          name: "Images",
          // 定义url匹配的模式
          url: "Content/Images/{imgName}",
          // 定义本条路由的默认参数值,这里配置为可选;
          // 其实更好的建议是配置为一张“图片未找到”“图片已损坏”等含义的图片名称;
          defaults: new { imgName = UrlParameter.Optional }
          );

          好了,准备工作都已经就绪了,让我们返回_Layout.cshtml,在它顶部开始撸代码:

          @{
          var context = this.ViewContext.RequestContext;
          var href_values = new RouteValueDictionary { { "controller", "home" }, { "action", "index" } };
          var href = RouteTable.Routes.GetVirtualPath(context, href_values).VirtualPath;
          var src_values = new RouteValueDictionary { { "id", "logo.png" } };
          var src = RouteTable.Routes.GetVirtualPath(context, "Images", src_values).VirtualPath;
          }

          方法都很简单,前面也对细节都解释过了,下面接着修改我们的锚点和图片元素:

          <a href="@href" class="navbar-brand" style="padding-top: 0px; padding-bottom:0px;">
          <img src="@src" alt="Honor Shop" title="Honor Shop" style="height: 100%; background-color:white;" />
          </a>

          运行效果与图14一样,完美。完美是完美,不过实现的过程,未免复杂了些,而且这里只是解决了路径计算的问题,如果有很多路径需要计算,参数也是各种各样,可以想象计算路径给我们带来的如此的繁重的代码量,对于偷奸耍滑成性的MVC团队来说,这也太不能忍了。

          值得庆幸的是,MVC给我们提供了很多与链接相关的辅助方法:

            • Html.ActionLink:我们前面刚介绍过;
            • Url.Action:根据给定的Controller,Action 生成链接,但是Html.ActionLink返回的是MvcHtmlString的一个带<a>标签的超链接,而Url.Action返回的是string,一个根据Controller,Action生成的URL地址,比Html.ActionLink少了<a>标签;
            • Html.RouteLink 与 Url.RouteUrl:两者都是可以指定由哪一个路由来生成Url,其它与上面的ActionLInk,Action一样;
            • Url.Content:将虚拟(相对)路径转换为应用程序绝对路径。

          这次,我们来使用两个Url属性提供的方法,毕竟我们是用来计算路径,Url看起来比Html更贴切一些。将之前直接使用路由所做的更改,全都删除或者注释掉,对的,就是这么随性:

          <a href="@Url.Action("Index", "Home")" class="navbar-brand" style="padding-top: 0px; padding-bottom:0px;">
          <img src="@Url.Content("~/Content/Images/logo.png")"
          alt="Honor Shop" title="Honor Shop" style="height: 100%; background-color:white;" />
          </a>

          运行看看:

          《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

          没有意外,一切按计划行事。但为了早日冲入第二境,我决定再用@Html提供的辅助方法折腾一遍,但是悲剧的是在@Html中并没有找到与图片相关的扩展方法。这就有点悲剧了。

          如果只用@Html.ActionLink能不能实现呢,能,可以把Logo图片加到样式表的背景图里,ActionLink的htmlAttributes应用样式,也是可以的。也比较简单,但对SEO不够友好,特殊场景还是可以使用的,这里就不实操了。

          不过这时,想起了之前提到的@Html.ActionLink是一系列扩展的重载方法,这些方法都可以辅助生成锚点元素。那么,我们是不是也可以通过扩展方法,来生成符合我们要求的自定义图片链接元素呢?说干就干。

          在项目中创建一个Html目录,用来放置对HtmlHelper扩展方法的类文件;

          在Html目录中新建类LinkExtensions,修改为静态类(必须,可以参考C#扩展方法的实现);

          using System.Collections.Generic;
          using System.Web.Mvc;
          using System.Web.Routing;

          namespace HonorShop.Web.Html
          {
          public static class LinkExtensions
          {
          /// <summary>
          /// Summary:
          /// 扩展ActionLink方法,用来创建带锚点的图片元素;
          /// </summary>
          /// <param name="htmlHelper">扩展类</param>
          /// <param name="actionName">操作名</param>
          /// <param name="controllerName">控制器名</param>
          /// <param name="routeValues">路由参数列表</param>
          /// <param name="linkAttributes">锚点htmlAttributes</param>
          /// <param name="imagePath">图片路径</param>
          /// <param name="imageAttributes">图片htmlAttributes</param>
          /// <returns>An image element (img element) within an anchor element (a element).</returns>
          public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper
          , string actionName
          , string controllerName
          , RouteValueDictionary routeValues
          , IDictionary<string, string> linkAttributes
          , string imagePath
          , IDictionary<string, string> imageAttributes)
          {
          var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);

          string imgUrl = urlHelper.Content(imagePath);
          TagBuilder imgTagBuilder = new TagBuilder("img");
          imgTagBuilder.MergeAttribute("src", imgUrl);
          if (null != imageAttributes && < imageAttributes.Count)
          imgTagBuilder.MergeAttributes(imageAttributes, true);
          string img = imgTagBuilder.ToString(TagRenderMode.SelfClosing);

          string url = urlHelper.Action(actionName, controllerName, routeValues);

          TagBuilder tagBuilder = new TagBuilder("a")
          {
          InnerHtml = img
          };
          tagBuilder.MergeAttribute("href", url);
          if (null != linkAttributes && < linkAttributes.Count)
          tagBuilder.MergeAttributes(linkAttributes, true);

          return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
          }
          }
          }

          在_Layout.cshtml的顶部添加引用

          @using HonorShop.Web.Html;

          修改导航栏Logo位置代码

          @Html.ActionLink("Index",
          "Home",
          new RouteValueDictionary { { "area", string.Empty } },
          new Dictionary<string, string> {
          { "alt", "Honor Shop" },
          { "title", "Honor Shop" },
          { "class", "navbar-brand" },
          { "style", "padding-top: 0px; padding-bottom:0px;" } },
          "~/Content/Images/logo.png",
          new Dictionary<string, string> {
          { "alt", "Honor Shop" },
          { "title", "Honor Shop" },
          { "style", "height: 100%; background-color:white;" } })

          两个字典拼装的有点多,代码显得长了点,不过,一个方法搞定,还是简洁了很多,而且使用了扩展方法,在重用和灵活性上都得到了大幅度的提升。

          回想一下,我们在第二部分说过如何修改标题栏图标时,使用了如下代码:

          <link rel="icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />
          <link rel="shortcut icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />

          其中,也涉及到了路径问题,那么,我们应用学到的知识,来更新一下它们吧:

          <link rel="icon" href="@Url.Content("~/Content/Images/honorshop.ico")" type="image/x-icon" />
          <link rel="shortcut icon" href="@Url.Content("~/Content/Images/honorshop.ico")" type="image/x-icon" />

          运行一下,效果如图15所示一样。大功告成。

          到这里,我们也基本了解了HtmlHelper和UrlHelper两个辅助类的使用方法以及如何为其添加扩展方法。这将在我们后面的开发过程中,打下良好的基础,读者朋友需要细细品味,最好能够跟着动手实际操作一番。

          下一节开始,我们就要使用这些知识,动手打造我们的第一个页面了。

          喜欢本系列丛书的朋友,可以点击链接加入QQ交流群(994761602)【C# 破境之道】
          方便各位在有疑问的时候可以及时给我个反馈。同时,也算是给各位志同道合的朋友提供一个交流的平台。
          需要源码的童鞋,也可以在群文件中获取最新源代码。

          上一篇:栈stack的C实现


          下一篇:ASP.NET MVC3中的路由系统 Routes