标记帮助程序(TagHelper)是ASP.NET Core中的新增功能。
首先通过一个例子来理解TagHelper及其用途。
TagHelper是服务器端组件。它们在服务器上运行,并在Razor文件中创建和渲染HTML元素。如果对以前版本的ASP.NET Core MVC有一定了解,那么读者可能也知道HTML TagHelper。TagHelper类似于HTML TagHelper,ASP.NET Core有许多内置的TagHelper用于常见任务,比如生成链接、创建表单和加载数据等。TagHelper的出现可帮助提高生产效率,并生成更稳定的 、可靠和可维护的代码。
要在整个应用程序中的所有视图使用内置TagHelper,需要在_ViewImports.cshtml
文件导入TagHelper。要导入TagHelper,我们使用@addTagHelper
指令。
@addTagHelper *.Microsoft.AspNetCore.Mvc.TagHelpers
通配符*
表示我们要导入MVC中所有的TagHelper,而Microsoft.AspNetCore.Mvc.TagHelpers则是内置TagHelper的组件位置。
假设要查看指定学生的详细信息,则要生成以下超链接。数字5是我们要查看其详细信息的学生的ID。
/home/details/5
我们可以通过多种方式在Razor视图中实现该效果。
@foreach (var student in Model)
{
<a href="/home/details/@student.ID">查看</a>
}
@Html.ActionLink("查看","details",new {ID = student.ID})
生成结果Link元素<a href="/home/details/5">查看</a>或者@Url.Action("details","home",new{ID=student.Id})
生成的结果:/home/details/5。
<a asp-controller="home" asp-action="details" asp-route-id="@student.ID" >查看a>,
生成结果<a href="/Home/details/5">查看a>
TagHelper中的Link标签通过添加新属性来增强标准的HTML标签,以下TagHelper均可以增强Link标签的href属性值。
asp-controller
asp-action
asp-route-{value}
正如名称所表示的那样,asp-controller
指定控制器名称,而asp-action
指定要包含在生成的href属性值中的操作方法名称。asp-route-{value}
属性用于在生成的href中包含路由数据属性值。{value}
可以替换为路由参数,比如id。参考以下代码:
<a asp-controller="home" asp-action="details" asp-route-id="@student.ID">查看a>
生成结果: 查看 。
从下面的代码中可以看出,手动生成的链接比使用HTMLHelper或TagHelper要容易得多。
<a href="/home/details/@student.ID">查看a>
读者可能产生一个疑问:为什么我们要使用TagHelper或HTML Helper来生成这些链接,而不是手动生成?我们将在下文回答这个问题。
让我们通过一个简单的例子来理解TagHelper的优势。
假设我们想要查看特定的学生的详细信息,则需要生成超链接,比如学生ID为5的详细信息。
/home/details/5
我们可以通过手动编写,如下:
<a href="/home/details/@student.ID">查看a>
也可以使用Link链接的TagHelper,代码如下:
<a asp-controller="home" asp-action="details" asp-route-id="@student.ID">查看a>
TagHelper是根据应用程序的路由模板生成的链接,这意味着如果我们更改路由模板,则TagHelper生成的链接会针对路由模板所做的更改自动修改和适配,让生成的链接正常工作。
而如果我们手动硬编码了URL,则当应用程序路由模块发生变化时,就必须在很多地方更改代码,这样效率并不高。
可以通过一个例子来理解这一点。
app.UseEndpoints(endpoints=>
{
endpoints.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}"
);
});
以下代码没有用TagHelper,而是对URL路径进行硬编码。
<a href="/home/details/@student.ID">查看a>
以下代码是使用元素的TagHelper完成的。
<a asp-controller="home" asp-action="details" asp-route-id="@student.ID">查看a>
请注意,我们没有针对URL路径进行硬编码,只指定控制器和操作方法的名称,以及路由参数及其值。在服务器上执行TagHelper时,它们会查看路由模板并自动生成正确的URL。
上述两种方法都会生成正确的URL路径(/home/details/5),它适用于当前路由模板({controller=Home}/{action=Index}/{id?})。
现在让我们改变路由模板,代码如下。请注意,在URL中有字符串pragim
。
app.UseEndpoints(endpoints=>
{
endpoint.MapControllerRoute(
name:"deafault",
pattern:"pragim/{controller=Home}/{action=Index}/{id?}"
);
});
请注意,不要忘记删除HomeController
中属性路由的属性,否则项目还是会正常进入Home/Index
的路径中。当读者运行项目发生404错误的时候,记得添加pragim
域信息和正确的URL访问路径http://localhost:2051/pragim。编译并运行项目,使用TagHelper生成的代码是正确的链接。
<a href="/pragim/home/details/1">查看a>。
//其中未使用TagHelper的代码则没有变化,缺少URL路径“/pragim”
<a href="/home/details/1">查看a>
我们还有其他TagHelper,它可以生成表单。将此表单发回服务器时,将自动处理发布的值并显示相关的验证信息。如果没有这些TagHelper,我们将不得不编写大量自定义代码来实现相同的功能。
如果此时读者觉得没有多大意义,请耐下心来。我们会在后面创建学生信息的时候讨论表单的TagHelper。
在开始说Image标记帮助程序的好处之前,我们先验证一下普通元素的问题。
准备好两个图片文件,一个名为noimage.png
,另一个名为noimage1.png
,把它们存储在wwwroot/images
文件夹中。现在正常运行项目,如图所示。
接下来回到项目中,将noimage.png
重命名为noimage2.png
。然后将noimage1.png
重命名为noimage.png
。再次刷新页面会发现没有变化,打开开发者工具可以发现,返回的HTTP Code:200 OK(from memmory cache)
是从缓存中得到的,如图所示。
这样带来的问题是用户会认为我们的程序出问题了,那么怎么解决?
当访问网页时,大多数浏览器会缓存该网页的图片,这样再次访问该页面时,浏览器就不用从Web服务器下载相同的图片,而是从缓存中提取。在大多数情况下,这不是问题,因为图片不会经常发生改变,但是这对开发人员来说相当不友好。
由于某种原因,如果读者不希望浏览使用器缓存,则可以禁用它。比如,要在Google Chrome中禁用缓存,步骤如下:
禁用浏览器缓存后,会带来一个明显的问题,那就是每次访问该页面时都必须从服务器下载图片,要记得平时关闭禁用缓存。
现在刷新浏览器,新的图片就会加载出来了。
HTTP状态码是用于表示网页服务器HTTP响应状态的3位数字代码。状态码的第一个数字代表响应的5中状态之一,这里我们只介绍涉及的3xx
和2xx
系列。
2xx
系列代表请求已成功被服务器接收、理解并接受,这系列中常见的200状态码和201状态码。
202
Accepted。 3xxx
系列代表需要客户端采取进一步的操作才能完成请求,这些状态码用来重定向,后续的请求地址(重定向目标)在本次响应的Location域中指明。这系列中常见的有301状态码和302状态码。
简单来说:3xx
系列表示请求的是客户端,而2xx
系列表示请求的是服务器端。
从性能角度来看,只有在服务器上更改了图片才能对其进行下载。如果图片未更改,请使用浏览器中缓存的照片。这意味着我们将拥有两全其美的优势。
Image TagHelper可以帮助我们实现这一效果。要使用Image TagHelper,请包含asp-append-version
属性并将其设置为true
。
现在我们回到项目中,打开Index.cshtml
和Details.cshtml
并对它们进行修改,代码如下。
<img src="~/images/noimgage.png" asp-append-verison="true"/>
Image TagHelper增强了img标签属性,为静态图像文件提供了缓存清楚行为,通过散列计算生成唯一的散列值并将其附加到图片的URL中。唯一的散列值会提示客户端(或某些代理)从服务器重新加载图片,而不是从浏览器的缓存中重新加载。以下是生成的代码。
只有当每次服务器上的图片更改时,才会计算并缓存新的散列值。如果图片未更改,则不会重新计算散列值。使用此散列值,浏览器会跟踪服务器上的图片内容是否已更改。使用Image TagHelper可以帮助我们解决很多由潜在因素造成的问题。
我们在ASP.NET Core应用程序开发过程中使用了Bootstrap。为了便于调试,希望在本地开发计算机(在开发环境中)通过应用程序加载没有压缩的Bootstrap的CSS文件(bootstrap.css)。而在Staging、Production或除Development环境以外的任何其他环境中,希望应用程序能从CDN(内容分发网络)加载压缩后的Bootstrap的CSS文件(bootstrap.min.css)以获得更好的性能。
但是,如果CDN出现故障或者出于某种原因,当应用程序无法访问CDN的时候,我们希望应用程序不要访问CDN,并从应用程序Web服务器加载压缩后的Bootstrap文件。
很多开发框架都有这种功能,现在我们可以使用ASP.NET Core Environment TagHelper轻松实现这一点。在我们理解Environment TagHelper之前,我们先来了解如何设置应用程序环境的名称。
Environment TagHelper支持根据应用程序环境呈现不同的内容,它使用ASPNETCORE_ENVIRONMENT变量的值作为环境的名称。
如果应用程序时Development ,则此示例加载没有压缩的bootstrap.css文件,代码如下:
<environment include="Development">
<link href="~/lib/bootstrap/css/bootstrap.css" rel="stylesheet"/>
environment>
如果应用程序环境是Staging或者Production,则此示例从CDN加载压缩后的bootstrap.min.css文件,代码如下。
<environment include="Staging,Production">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
environment>
include属性接受将单个环境名称以逗号分隔的形式生成列表。在Environment TagHelper中还有exclude属性,当托管环境与exclude属性值中列出的环境名称不匹配时,将呈现标签中的内容。
如果应用程序不是Development,则从此示例从CDN加载压缩后的bootstrap.min.css文件,代码如下:
<environment exclude="Development">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"/>
environment>
元素中的integrity属性用于检查子资源完整性。Subresource Integrity(SRI)是一种安全功能,允许浏览器检查被检索的文件是否被恶意更改。当浏览器下载文件时,它会重新计算散列值并将其与完整性属性散列值进行比较。如果散列值匹配,则浏览器允许下载文件,否则将被阻止。
如果CDN出现故障或出于某种原因应用程序无法访问CDN,则希望应用程序从应用程序Web服务求加载压缩后的Bootstrap文件,代码如下。
<environment include="Development">
<link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
environment>
<environment exclude="Development">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous" asp-fallback-href="~/lib/twitter-bootstrap/css/bootstrap.css" asp-fallback-test-class="sr-only" asp-fallback-test-property="positon" asp-fallback-test-value="absolute" asp-suppress-fallback-integrity="true" />
environment>
如果应用程序环境是Development,则从应用程序Web服务器加载没有压缩的bootstrap.css文件;如果应用程序环境不是Development,则从CDN加载缩小的bootstrap.min.css文件。
使用asp-fallback-href
属性指定回退源。这意味着,如果CDN关闭,则应用程序将回退并从应用程序Web服务器加载缩小的Bootstrap文件。
以下3个属性及其相关值用于检查CDN是否关闭。
asp-fallback-test-calss="sr-only"
。asp-fallback-test-propery="position"
。asp-fallback-test-value="absolute"
。当然,这会设计计算散列值,并将其与文件的完整性属性散列值进行比较。对于大多数应用程序,CDN失效的时候都是回退到它们自己的服务器,方法是将asp-suppress-fallback-integrity
属性设置为true
,当然读者也可以选择关闭从本地服务器下载的文件完整性检查。
❗请注意,Bootstrap的所有Javascript插件都依赖于jQuery,因此jQuery必须在Bootstrap之前引入。读者可以使用Libman管理工具下载jQuery,也可前去jQuery官网下载。
<html>
<head>
<meta name="viewport" content="width=device-width" />
<environment include="Development">
<link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
<script src="~/lib/jquery/jquery.js">script>
<script src="~/lib/twitter-bootstrap/js/bootstrap.js">script>
environment>
<environment exclude="Development">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous" asp-fallback-href="~/lib/twitter-bootstrap/css/bootstrap.css" asp-fallback-test-class="sr-only" asp-fallback-test-property="positon" asp-fallback-test-value="absolute" asp-suppress-fallback-integrity="true" />
environment>
<link href="~/css/site.css" rel="stylesheet" />
<title>@ViewBag.Titletitle>
head>
<body>
<div class="container">
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<a class="navbar-brand" asp-controller="home" asp-action="index">
<img src="~/images/student.png" width="30" height="30" />
a>
<button class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#collapsibleNavbar">
<span class="navbar-toggler-icon">span>
button>
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="index">列表a>
li>
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="create">创建a>
li>
ul>
div>
nav>
@RenderBody()
div>
@if (IsSectionDefined("Scripts"))
{@RenderSection("Scripts", required: false)}
body>
html>
注意,对于非开发环境(Staging 、Production等),我们没有使用所需的元素来从CDN加载所需的Jquery和Bootstrap Javascript文件。
请注意,按钮(查看、编辑、删除)是相互连接在一起的,我们要在这些按钮之间包含边框,需使用Bootstrap提供的边距类(m-1、m-2等)。在类名中,m
代表边距,数字1、2等代表所需空间的大小。
我们在ASP.NET Core中使用以下常用的TagHelpers创建表单。
我们希望使用Form TagHelpers创建表单,并使用Bootstrap对其进行样式设置,完成创建学生信息。
我们在HomeController中添加如下代码。
public IActionResult Create()
{
return View();
}
然后在路径为Views/Homes/的文件夹中添加视图Create.cshtml
。
要创建表单可以使用Form TagHelper。需要注意的是,我们实际是使用asp-controller
和asp-action
TagHelper。这两个TagHelper指定控制器并在提交表单时将表单数据发布到指定的操作方法上。我们希望在提交表单时发出POST请求,因此将method
属性设置为post。
<form asp-controller="home" asp-action="create" method="post">
form>
从客户端浏览器上呈现表单时,上面的代码会生成以下HTML代码。正如读者在提交表单时从生成的HTML代码中看到的那样,它将被发布到HomeController的Index()方法中,编译后打开源代码可以看到渲染出来的HTML代码。
<form method="post" action="/home/create">
form>
请注意,默认情况下在提交表单时,它将被发布到当前页面表单的控制器的操作方法中。这意味着,即使我们没有使用asp-controller
和asp-action
TagHelper指定对应的控制器和操作方法,表单仍然被发布到HomeController的Index()方法中,但是建议写的时候还是显示声明。
Input TagHelper将HTML中的元素绑定到Razor视图中的模型表达式。
在我们的例子中想要设计一个表单来创建新学生信息。因此,Create.cshtml视图的模型是Student类,需要使用@model
指令,即@model Student
。
为了获取学生姓名,我们需要一个文本框并将其绑定到Student模型类的Name属性。我们使用asp-for
TagHelper将input的属性值设置为Student模型类的name属性。
请注意,Visual Studio会提供智能提示。如果在Student类上将属性名称Name更改为FullName,但不更改分配给的TagHelper的值,则会出现编译器错误。
<input asp-for="Name" />
上面的代码会生成一个带有id和name属性的元素。请注意,它们的值均为Name。
<input type="text" id="Name" name="Name" value="" />
name属性是必须的,它用于在提交表单时将输入元素的值映射到模型类的对应属性。这是通过ASP.NET Core中称为模型绑定的过程完成的。
Label TagHelper会生成带有for属性的标签。属性链接与和它相关的输入元素的标签进行绑定,代码如下。
<label asp-for="Name">label> <input asp-for="Name" />
上面的代码生成以下HTML代码:
<label for="Name">Namelabel>
<input type="text" id="Name" name="Name" value="" />
Label标签链接到input标签,因为这两种标签的属性和input标签的id属性具有相同的值(Name)。
同样,以下代码生成
<label asp-for="Email">label> <input asp-for="Email" />
Select TagHelper会生成select标签及其关联的元素。在我们的例子中需要通过select显示主修科目列表。最后,我们需要一个select标签和一个带有主修科目选项列表的元素,如下所示。
<label for="Major">主修科目label>
<select id="Major" name="Major">
<option value="0">计科option>
<option value="1">信安option>
<option value="2">网工option>
select>
主修科目的元素的选项内容可以像上面的示例中那样进行硬编码,也可以来自枚举或数据库表。我们还没有连接数据库,因此,对于我们的示例可以从枚举中获取选项。
我们在Models/EnumTypes的文件夹中创建一个MajorEnum.cs枚举类。
public enum EnumTypes
{
None,
FirstGrade,
SecondGrade,
GradeThree
}
修改Models文件夹中Student.cs文件的代码,将Major属性数据类型改为MajorEnum。在Create.cshtml视图中添加以下代码。
<label asp-for="Major">label>
<select asp-for="Major" asp-items="Html.GetEnumSelectList()" >
select>
请注意,我们使用asp-items
属性值帮助程序和Html.GetEnumsSelectList
获取
上面的代码生成以下HTML代码。
<label for="Major">Majorlabel>
<select data-val="true" data-val-required="The Major field is required." id="Major" name="Major">
<option value="0">Noneoption>
<option value="1">FirstGradeoption>
<option value="2">SecondGradeoption>
<option value="3">GradeThreeoption>
select>
@model Student
@{ ViewBag.Title = "创建学生信息"; }
<form asp-controller="home" asp-action="create" method="post" >
<div>
<label asp-for="Name" >label>
<input asp-for="Name"/>
div>
<div>
<label asp-for="Email">label>
<input asp-for="Email"/>
div>
<div>
<label asp-for="Major">label>
<select asp-for="Major"
asp-items="Html.GetEnumSelectList()" >
<option value="">请选择option>
select>
div>
<button type="submit">创建button>
form>
以上的代码会生成以下的HTML代码。
<form method="post" action="/home/create">
<div>
<label for="Name">Namelabel>
<input type="text" id="Name" name="Name" value="" />
div>
<div>
<label for="Email">Emaillabel>
<input type="text" id="Email" name="Email" value="" />
div>
<div>
<label for="Major">Majorlabel>
<select data-val="true" data-val-required="The Major field is required." id="Major" name="Major">
<option value="">请选择option>
<option value="0">Noneoption>
<option value="1">FirstGradeoption>
<option value="2">SecondGradeoption>
<option value="3">GradeThreeoption>
select>
div>
<button type="submit" class="btn btn-primary">创建button>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8IVmefmn13tElsuhIOlxBd9qxTAKjc5MLl_DnUHtxiZb-ztIjcXBKCtrVcKqrDEmn17EthiSYdJ8fxT6emkGJopOw5HrMAKYZOEg-4khyt_WLv8rEhhTqGLRm73T8eS1Lx8RcxZJ_jnEQEVwulC-eC4" />
form>
运行后效果如图所示:
这就是基本的HTML代码生成的效果,这里功能是有了,但是美化并不好,因此要对它进行优化。现在使用Bootstrap进行优化。
@model Student
@{ ViewBag.Title = "创建学生信息"; }
<form asp-controller="home" asp-action="create" method="post" class="mt-3">
<div asp-validation-summary="All" class="text-danger">div>
<div class="form-group row">
<label asp-for="Name" class="col-sm-2 col-form-label">label>
<div class="col-sm-10">
<input asp-for="Name" class="form-control" placeholder="请输入名字" />
<span asp-validation-for="Name" class="text-danger">span>
div>
div>
<div class="form-group row">
<label asp-for="Email" class="col-sm-2 col-form-label">label>
<div class="col-sm-10">
<input asp-for="Email" class="form-control" placeholder="请注入邮箱地址" />
<span asp-validation-for="Email" class="text-danger">span>
div>
div>
<div class="form-group row">
<label asp-for="Major" class="col-sm-2 col-form-label">label>
<div class="col-sm-10">
<select asp-for="Major"
class="custom-select mr-sm-2"
asp-items="Html.GetEnumSelectList()" >
<option value="">请选择option>
select>
<span asp-validation-for="Major" class="text-danger">span>
div>
div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">创建button>
div>
div>
form>
运行后,效果如图所示:
至此,我们的Create页面做完了。虽然是一个简单的页面,但我们用到的组件一点也不少。