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

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

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

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

首先会发现/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],被替换掉了。

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

二、修改标题栏图标

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

也可以通过在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的声明处。

可以看到,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>

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

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

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

但是很遗憾,在_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又是什么鬼?我们还是跳转到声明处看看吧

首先,它是一个抽象类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方法的声明,眼前一亮,原来都在这里:

大家可以展开类及方法上的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);

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

<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>

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

现在我们可以初步体会到,@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的基础的东西,不是本书的重点,不多介绍,看看效果。

目前,它可以很好的工作,但它是脆弱的。思考一个问题,如果,我们的应用,并没有部署在网站的根目录,或者修改了路由的定义,那么,锚点的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>

运行看看:

没有意外,一切按计划行事。但为了早日冲入第二境,我决定再用@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# 破境之道】
方便各位在有疑问的时候可以及时给我个反馈。同时,也算是给各位志同道合的朋友提供一个交流的平台。
需要源码的童鞋,也可以在群文件中获取最新源代码。

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

  1. 《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第二节:MVC5项目结构

    第一境 ASP.Net MVC5项目初探 — 第二节:MVC5项目结构 接下来,我们来看看,VS为我们自动创建的项目,是什么样子的? 可以通过菜单中[View]->[Solution Explo ...

  2. 《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第一节:运行第一个MVC5项目

    第一境 ASP.Net MVC5项目初探 — 第一节:运行第一个MVC5项目 创建一个MVC项目,是很容易的,大部分工作,VS都帮我们完成了.只需要按照如下步骤按部就班就可以了. 打开VS2017,选 ...

  3. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  4. 【无私分享:从入门到精通ASP.NET MVC】从0开始,一起搭框架、做项目 目录索引

    索引 [无私分享:从入门到精通ASP.NET MVC]从0开始,一起搭框架.做项目(1)搭建MVC环境 注册区域 [无私分享:从入门到精通ASP.NET MVC]从0开始,一起搭框架.做项目(2)创建 ...

  5. ASP.NET MVC深入浅出(被替换) 第一节: 结合EF的本地缓存属性来介绍【EF增删改操作】的几种形式 第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery ) 第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法 第六节: EF高级属性(二) 之延迟加载、立即加载、显示加载(含导航属性) 第十节: EF的三种追踪

    ASP.NET MVC深入浅出(被替换)   一. 谈情怀-ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态 ...

  6. 《ASP.NET MVC 5 破境之道》:概述

    第一节:写作本书的目的 关于笔者 张晓亭(Mike Cheers),1982年出生,内蒙古辽阔的大草原是我的故乡. 没有高学历,没有侃侃而谈的高谈阔论,拥有的就是那一份对技术的执著,对自我价值的追求. ...

  7. ASP.NET MVC使用Bootstrap系列(5)——创建ASP.NET MVC Bootstrap Helpers

    阅读目录 序言 内置的HTML Helpers 创建自定义的Helpers 使用静态方法创建Helpers 使用扩展方法创建Helpers 创建Fluent Helpers 创建自动闭合的Helper ...

  8. Asp.NET MVC 拍卖网站,拆解【2】 Asp.NET MVC章回,第(1)节

    时间和篇幅所限,MVC不会介绍基本的建站过程,请参照博客园技术专题文章传送门  英语足够好的请直接去微asp.net 官网 传送门(强烈推荐,尤其是想使用最新技术的时候更应该直接去官网),本文主要介绍 ...

  9. 解析ASP.NET Mvc开发之删除修改数据 分类: ASP.NET 2014-01-04 23:41 3203人阅读 评论(2) 收藏

    目录: 从明源动力到创新工场这一路走来 解析ASP.NET WebForm和Mvc开发的区别 解析ASP.NET 和Mvc开发之查询数据实例 解析ASP.NET Mvc开发之EF延迟加载 ------ ...

随机推荐

  1. 最高频的K个单词 · Top K Frequent Words

    [抄题]: 给一个单词列表,求出这个列表中出现频次最高的K个单词. [思维问题]: 以为已经放进pq里就不能改了.其实可以改,利用每次取出的都是顶上的最小值就行了.(性质) 不知道怎么处理k个之外的数 ...

  2. php中的declare

    <?php // 事件的回调函数 function func_tick() { echo "call...\r\n"; } // 注册事件的回调函数 register_tic ...

  3. php不重新编译,安装未安装过的扩展,如curl扩展

    假设我们的之前的php安装于/data/php下. 1.找到之前安装的PHP源码包,把它重新解压出来,进入到要安装的扩展目录. > cd /data/php-5.6.11/ext/curl (* ...

  4. spring框架的概述与入门

    1. Spring框架的概述 * Spring是一个开源框架 * Spring是于2003 年兴起的一个轻量级的Java开发框架,由Rod Johnson在其著作Expert One-On-One J ...

  5. SqlDataHelper

    using System;using System.Data;using System.Configuration;using System.Linq;using System.Web;using S ...

  6. .NET资源文件实现多语言切换

    1.创建对应的资源文件 lang.en.resx  英文 lang.resx   中文,默认 lang.zh-tw.resx  繁体 首先说明,这三个文件前面部分名称需要一样,只是 点 后面的语言代号 ...

  7. jQuery中animate()对Firefox无效的解决办法

    在使用 animate()做返回顶部的动画时,会出现对Firefox无效的情况,如: $('body').animate({scrollTop:'0'},500); 它对Chrome,IE,Opera ...

  8. 运行代码后出现Process finished with exit code 0是为什么?

    Process finished with exit code 0 意味着你的程序正常执行完毕并退出. 可以科普一下exit code,在大部分编程语言中都适用: exit code 0 表示程序执行 ...

  9. PS想象的力量无限大,设计师的脑洞无限大!

    我(nemanjasekulic)一直对魔法与科幻感兴趣,但是,现实中,它们并不存在.我所做的是尽量体现一切都是可能的,表达一种没有约束的理想概念. 编辑:千锋UI设计

  10. web札记

    url中不能是#号,struts不读取#之后的字符串.