ASP.NET Core 框架内置了大量的Tag Helpers,以asp-*前缀开始,他们用于增强表单,验证消息,设计布局等,在这节中我们将继续讨论内置的Tag Helpers,例如表单控件,输入控件,选择控件,标签控件,锚点标签,文本控件,CSS,JS 和Cache
1 Form帮助标签
ASP.NET Core表单控件用来增强原始HTML表单的健壮性和高效性,当业务发生变化时这些表单的可维护性很高,这些标记为表单生成action属性以及隐藏的请求验证令牌,以防止跨站点伪造请求
下面给与了表单常用的帮助标签
方法 | 描述 |
asp-controller | 根据应用程序的路由指定目标Controller,如果省略,使用当前视图文件所在的控制器 |
asp-action | 根据应用程序的路由指定目标Action方法,如果省略,使用视图文件当前Action方法 |
asp-route-* | 指定url额外的段,例如 asp-route-id 使用提供id段的值 |
asp-route | 通过指定路由的名称生成action属性 |
asp-area | 指定目标区域的名称 |
asp-antiforgery | 生成一个隐藏的请求验证令牌用来防止跨站点请求攻击,经常和[ValidateAntiForgeryToken]特性一起使用,[ValidateAntiForgeryToken]使用在HTTP Post方法上 |
如果我们的应用程序只有一个路由定义在Programe.cs类中
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
现在我们使用帮助标签来创建表单
<form method="post" asp-controller="Home" asp-action="Create">
...
</form>
在这种情况下表单的action方法将生成/Home/Create,我们检查一下表单中生成的HTML
<form method="post" action="/Home/Create">
...
</form>
在应用程序中的目录下Views->Home文件夹下创建一个新的视图文件,名字为Create.cshtml, 添加下面代码:
@model Product
@{
ViewData["Title"] = "新增";
}
<form method="post" asp-controller="Home" ,asp-action="Create">
<div class="mb-3 row">
<label class="col-sm-1 control-label" for="Name">名称:</label>
<div class="col-sm-11">
<input class="form-control" name="name" />
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-1 control-label" for="Price">价格</label>
<div class="col-sm-11">
<input class="form-control" name="price" />
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-1 control-label" for="Quantity">数量</label>
<div class="col-sm-11">
<input class="form-control" name="quantity" />
</div>
</div>
<div class="col-sm-11 offset-sm-1">
<button type="submit" class="btn btn-primary">新增</button>
</div>
</form>
我们视图中创建一个form标签,使用asp-controller="Home"和asp-action="Create"帮助标签创建html表单的action属性
表单的method是post,当提交表单时Create的方法被调用,在HomeController中添加2个Create方法,一个是HTTPGet另外一个是HttpPost
这两个Action方法的代码如下:
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create(Product product)
{
return RedirectToAction("Index");
}
我们在label元素上使用for属性,用来绑定label元素,输入控件的name属性分别被赋值为-name,price&quantity,这些名称是Product.cs类的属性,我们通过使用控件的名称来绑定action方法的参数
运行应用程序,你将会看到一个表单,在浏览器中检查表单元素并且你将看到action特性被创建为/Home/Create
图片展示如下:
提交表单新的产品将添加到repository,将会跳转到Index视图在产品列表显示所有的产品
注意:表单所在的Create视图位于HomeController中,对应的Action方法为Create,因此我们也不需要应用asp-action和asp-controller帮助标签
下面代码也能工作:
<form method="post">
....
</form>
1.1 asp-antiforgery
asp-antiforgery生成一个隐藏的请求验证令牌,这个令牌为antiforgerytoken,为了防止CSRF攻击,当你在你的表单上使用这个特性,ASP.NET Core做两件事:
1 在表单的隐藏域中添加一个安全的token
2 在响应添加一个cookie
如果表单中包含Cookie和隐藏的值,应用程序将处理这个请求,恶意站点不能访问,因此阻止了CSRF
为了使用这个特性,在表单元素中添加帮助标签asp-antiforgery="true", 并且在对应的请求方法上添加[ValidateAntiForgeryToken]特性
@model Product
@{
ViewData["Title"] = "Create";
}
<form method="post" asp-controller="Home" asp-action="Create" asp-antiforgery="true">
// removed for clarity
</form>
在表单的Create视图中添加asp-antiforgery属性:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Product product)
{
return RedirectToAction("Index");
}
运行应用程序并且检查Create视图的HTML代码,你会在form元素内发现一个隐藏域并且它包含一个token值,如下所示:
现在打开Developer Tools, 进入Application页,接着在左边点击Cookies,你将会看到anti-forgery cookie,它将被一块发送到控制器
[ValidateAntiForgeryToken]特性将自动验证这个cookie和token
2 Label帮助标签
Label帮助标签用来设置HTML中的Label,我们在label上添加asp-for属性,在label标签上创建一个for属性,为了测试我们在为Create视图中的Label添加asp-for属性
运行应用程序,打开浏览器检查生成html源码
3 Input帮助标签
ASP.NET Core Input帮助标签用来将输入元素绑定到模型表达式-asp-for="expression",当我们在input输入控件中添加asp-for属性,input控件会使用模型表达式设置name,id,type和value属性的值,我们修改Create视图中的Input元素for修改为asp-for
运行应用程序并且查看HTML源码,我们发现input输入框包含了id,name,type,value四个属性
你会发现Name和Price输入框的type是Text,但是Quantity输入框的类型是number,这是因为ASP.NET Core基于模型属性类型给HTML控件提供type的值
下面表格告诉我们不同类型的值是如何对应的:
模型类型 | 输入框类型 |
byte, sbyte, int, uint, short, ushort, long, ulong | Number |
float, double, decimal | text |
string | text |
bool | checkbox |
DateTime | datetime |
4 Select帮助标签
Select帮助标签用来指定模型属性并且针对下拉框元素可选
1 asp-for 用模型属性名字设置为Select元素的id和name属性
2 asp-items 给Select元素提供可选的值
我们把Create视图中quantity字段从输入框修改为选择框:
现在我们检查一下在浏览器中生成的HTML代码,我们看到把id和value的值设置为Product.cs类的Quantity属性,代码如下:
ASP.NET Core框架非常智能,选择控件能够自动选择默认值,为了了解它,添加Edit方法在Home控制器中,这个方法将从仓储中返回最后添加的记录,方法代码如下:
public ViewResult Edit() => View("Create", _repository.Products.Last());
进入编辑页面的地址https://localhost:7182/Home/Edit,我们能清楚看到最后一条记录时400被自动选择在选择控件中
检查生成的HTML源码,注意selected特性应用到了包含400值的选项,代码如下:
4.1 asp-items 特性
asp-items 特性使用指定下拉框列表中的元素,它用来帮助我们从数据源生成下拉框列表中的元素,修改Create视图下拉框代码,现在使用asp-items特性:
<select class="form-control" asp-for="Quantity" asp-items="ViewBag.Quantity">
<option disabled selected value="">Select Quantity</option>
</select>
我们指定ViewBag.Quantity作为asp-items特性的值,ViewBag将包含一个SelectList对象将作为下拉框中的元素呈现,现在修改Create和Edit方法
using AspNetCore.BuiltInTagHelpers.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Diagnostics;
using static AspNetCore.BuiltInTagHelpers.Models.Repository;
namespace AspNetCore.BuiltInTagHelpers.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IRepository _repository;
public HomeController(IRepository repository,
ILogger<HomeController> logger)
{
_repository = repository;
_logger = logger;
}
public ViewResult Edit()
{
ViewBag.Quantity = new SelectList(_repository.Products.Select(p => p.Quantity).Distinct());
return View("Create", _repository.Products.Last());
}
public IActionResult Index()
{
return View(_repository.Products);
}
public IActionResult Create()
{
ViewBag.Quantity = new SelectList(_repository.Products.Select(p => p.Quantity).Distinct());
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Product product)
{
_repository.AddProduct(product);
return RedirectToAction("Index");
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
ViewBag.Quantity 属性设置为SelectList 对象,使用仓储Quantity值来填充该对象,经过这么一番修改,我们实现了动态选择
SelectList对象位于Microsoft.AspNetCore.Mvc.Rendering命名空间
运行应用程序,请求 https://localhost:7076/Home/Create 或者https://localhost:7076/Home/Edit 地址,这次你会发现可选元素被创建在ViewBag.Quantity数据源
5 Cache帮助标签
Cache帮助标签使用缓存内容用来帮助提高应用程序的性能,你可以将内容添加到缓存标签内部:
<cache> Some Content </cache>
进入_Layout.cshtml文件并且添加cache元素用来缓存当前时间:
<div class="container">
<main role="main" class="pb-3">
<div class="bg-info text-warning">
<cache>
Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>
</div>
@RenderBody()
</main>
</div>
现在运行应用程序,你将会看到当前时间显示在页面顶部,如下图所示:
现在刷新页面并且我们将看到时间在小时,分钟,秒没有任何变化,这是因为时间被缓存了
下面缓存标签比较重要的属性:
名称 | 描述 |
expires-on | 给缓存指定一个过期的绝对时间 |
expires-after | 指定一个缓存过期的相对时间,TimeSpan值 |
expires-sliding | 滑动时间,最后一次使用时间短, 它指定TimeSpan值中缓存过期的滑动时间 |
vary-by-query | 一个查询字符串的键,被用来管理不同版本的缓存 |
vary-by-cookie | 一个cookie名称用来管理不同版本的缓存 |
vary-by | 指定一个key用来管理不同版本的缓存 |
5.1 expires-after
在_layout.cshtml文件中为缓存代码添加expires-after特性:
<cache expires-after="@TimeSpan.FromSeconds(20)">
Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>
这意味着20秒之后缓存将失效
5.2 expires-on
在_layout.cshtml文件中为缓存代码添加expires-on特性:
<cache expires-on="@DateTime.Parse("2050-01-01")">
Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>
时间被缓存直到2025年
5.3 expires-sliding
在_Layout.cshmtl视图为缓存代码添加expires-sliding特性
<cache expires-sliding="@TimeSpan.FromSeconds(20)" >
Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>
这个缓存将在距离上次使用的20s后过期
为了测试,每两秒加载一次页面,你将会看到相同的时间,等待20秒之后重新加载页面,这时你将看到一个新的时间,这时因为这个缓存在最后一次加载时间20s之后过期
5.4 vary-by
vary-by特性使用指定一个key来管理不同版本的缓存,在_Layout.cshtml视图修改cache元素:
<cache expires-sliding="@TimeSpan.FromSeconds(20)" vary-by="@ViewContext.RouteData.Values["action"]">
Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>
这意味着cache是基于当前action,我们有三个actions-Index,Edit和Create,因此3个版本的缓存将被创建(每个action方法都有一个),expire-sliding特性为每个版本设置滑动时间
6 Anchor帮助标签
Anchor帮助标签使用最广泛的内置帮助标签在ASP.NET Core框架,通过添加新的特性来加强anchor标签,这个特性基于应用程序路由构建anchor标签href
下面表格列举出重要的标签被使用通过anchor 帮助标签
名字 | 描述 |
asp-controller | 指定url将被定位到的控制器 |
asp-action | 指定url将被定位到的action |
asp-area | 指定url将被定位到的area |
asp-fragment | 指定url段(在#字符后面) |
asp-route | 指定url将被定位到的路由名称 |
asp-route-* | 指定额外的值针对url 例如asp-route-id="10"为路由段提供了一个id为10的值 |
进入Index视图并且创建一个链接定位到Create视图:
@{
ViewData["Title"] = "Home Page";
}
@model IEnumerable<Product>
<div class="container">
<div class="row mb-3">
<div class="col-sm-3">
<a class="btn btn-primary" asp-action="Create">新增</a>
</div>
<div class="col-sm-3"></div>
<div class="col-sm-3"></div>
<div class="col-sm-3"></div>
</div>
<div class="row mb-3">
<div class="col-sm">
<table class="table table-bordered align-middle">
<thead>
<tr>
<th>名称</th>
<th>价格</th>
<td>数量</td>
</tr>
</thead>
<tbody>
@foreach (var product in Model)
{
<tr>
<td>@product.Name</td>
<td>@product.Price</td>
<td>@product.Quantity</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
这将生成如下链接:
<a class="btn btn-primary" href="/Home/Create">新增</a>
如下图所示
我们没有提供asp-controller帮助标签,因为使用了相同的控制器(Index和Create方法在相同的控制器),如果我们使用asp-route-* 特性在anchor帮助标签,如下代码:
<a class="btn btn-primary" asp-action="Create">新增</a>
生成的连接将包含路由id作为url的第三个段
<a class="btn btn-dark" href="/Home/Create/20">新增</a>
7 Javascript帮助标签
JavaScript 帮助标签管理包含和排除Javascript文件从视图文件,使用JavaScript帮助标签使用重要属性:
名称 | 描述 |
asp-src-include | 指定在视图中包含的JavaScript文件 |
asp-src-exclude | 指定在视图中排除的JavaScript文件 |
asp-append-version | 指定是否把文件版本附加到连接地址,使用 Cache Busting |
asp-fallback-src-include | 如果使用的CDN出现一些问题时,指定备用的JavaScript文件 |
asp-fallback-src-exclude | 如果使用CDN出现一些问题,指定排除的JavaScript文件 |
asp-fallback-test | JavaScript代码段,用来测试能否正确加载CDN |
我们也可以使用一些列通配符来创建匹配的文件,这些通配符可以配合asp-src-include, asp-src-exclude, asp-fallback-src-include和asp-fallback-src-exclude帮助标签一块使用
常用模式
模式 | 例子 | 描述 |
? | JS/myscript?.js | 匹配单个模式排除'/',左边的例子匹配 ‘JS/myscript1.js’, ‘JS/myscriptZ.js’, ‘JS/myscripts.js’等 |
* | JS/*.js | 匹配任何期望的字符/除外,左边给与的例子匹配‘JS/myscript.js’, ‘JS/validate.js’, ‘JS/js123.js’等 |
** | JS/**.js | 匹配任何数量的字符包括/,左边给与的例子匹配‘JS/Validate/myscript.js’,‘JS/jQuery/validate.js’, ‘JS/js123.js’等 |
这种模式非常有用,可以提供给我们一种基于客户自定义逻辑的方式创建匹配规则
7.1 asp-src-include例子
我们在_Layout.cshmtl中包含bootstrap.js文件,文件位于wwwroot/lib/bootstrap/dist/js/bootstrap.js
如下图所示
我们注意到5个JS文件在相同的文件夹
bootstrap.js
bootstrap.bundle.js
bootstrap.esm.js
bootstrap.js.map
bootstrap.min.js
现在进入_Layout.cshtml 文件并且添加script标签使用asp-src-include属性
运行应用程序并且检查生成的HTML源代码,你将会发现下面JS文件被添加到head节点
因为我们在asp-src-include属性中通过使用通配符来包含这些文件,我们可以使用下面代码用来只包含bootstrap.bundle.js文件
<script asp-src-include="lib/bootstrap/**/*bundle.js"></script>
7.2 asp-src-exclude例子
使用asp-src-exclude属性来排除view中的js文件,这次我们在layout文件中使用下面代码,这将从视图中移除所有的‘slim’, ‘min’ & ‘bundle’ 的JavaScript文件
<script asp-src-include="/lib/bootstrap/**/b*.js" asp-src-exclude="**.slim.*,**.min.*,**.bundle.*"></script>
运行应用程序检查生成的HTML源代码,你将发现只有bootstrap.js 和bootstrap.esm.js
<script src="/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="/lib/bootstrap/dist/js/bootstrap.esm.js"></script>
7.3 Busting asp-fragment Attribute
浏览器每次只下载一次图片,css文件和JS文件,并且将他们缓存在本地,当用户访问浏览器,我们将从缓存中读取这些文件,这样避免从服务器端加载文件从而使页面加载更快
假设我们在服务器修改JavaScript文件,客户端仍然缓存老的JS文件,新的JavaScript文件没有加载,因此用户没有看到改变
如何处理这个问题,有一些方法告诉浏览器下载文件当js文件修改完,我们可以使用Cache Busting来解决这个问题
可以使用asp-fragment特性设置Cache Busting,它在JS文件的URL中添加一个checksum查询字符串作为版本号,如果修改JS文件,checksum将会改变,并且浏览器知道现在需要下载最新的js文件从服务器端来重新加载缓存
<script asp-src-include="/lib/bootstrap/**/b*.js"
asp-src-exclude="**.slim.*,**.min.*,**.bundle.*"
asp-append-version="true">
</script>
生成的HTML包含如下
当浏览器向服务器发出下一个请求时,如果发现服务器上js文件的checksum已经更改
因此,它从服务器下载该文件的最新版本。然后缓存新下载的文件,并在“?”字符后添加新的checksome
8 CDN帮助标签
CDN包含了大量数据服务分部在全球,所有这些数据都保存在网站服务器的静态文件下,如图像、CSS、JS,当用户在浏览器上打开网站时,浏览器不是从网站的服务器请求静态文件,而是从离用户位置最近的数据服务器请求
这减少了网站的加载时间也节省了宽带,如果网站比较小建议使用CDN
asp-fallback-src-include,asp-fallback-src-exclude和asp-fallback-test 指定CDN, 假如有别的因素导致CDN加载jquery失败,在这种情况下我们的网站本地加载JQuery,更新layout文件,添加如下脚本
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"
asp-fallback-src-include="/lib/jquery/**/*.min.js"
asp-fallback-src-exclude="**.slim.**"
asp-fallback-test="window.jQuery">
</script>
src指定从哪里加载jQuery,在CDN加载失败的情况下,asp-fallback-src-include和asp-fallback-src-exclude 使用本地备用的JQuery以及排除本地jQuery文件,asp-fallback-test帮助标签定义JavaScript代码(‘window.jQuery’) ,用来测试是否jQuery从CDN加载成功
如果代码执行失败,那么我们可以确信CDN已经无法使用,因此asp-fallback-src-include和asp-fallbback-src-exclude将会从网站服务器加载
9 CSS帮助标签
CSS帮助标签是用来管理包含和排除CSS文件从视图,下面显示了各种特性:
名字 | 描述 |
asp-href-include | 指定视图中包含的CSS文件 |
asp-href-exclude | 指定视图中排除的CSS文件 |
asp-append-version | 指定css文件查询的版本号,用作Cache Busting |
asp-fallback-href-include | 指定包含的CSS文件,在CDN出现问题时 |
asp-fallback-href-exclude | 指定排除的CSS文件,在CDN出现问题时 |
asp-fallback-href-test-class | 指定一个CSS文件类测试CDN是否可用 |
asp-fallback-href-test-property | 使用CSS类的属性测试CDN |
asp-fallback-href-test-value | 指定一个测试值,用来确定CDN是否可用 |
9.1 asp-href-include例子
Bootstrap文件位于wwwroot/lib/bootstrap/dist/css目录,如下图所示:
在head节点下添加如下特性,包含所有的bootstrap的css文件
<link rel="stylesheet" asp-href-include="/lib/bootstrap/**/*min.css" />
我们针对CSS文件使用 (**和*) 匹配模式
** – 匹配任何数量的字符包含'/'
* – 匹配任何数量的字符,排除'/'
这将添加文件名称已.css结尾,因此将包含下列文件
添加了大量的css文件像grid,reboot,rtl ,我们可以使用asp-href-exclude来移除它
9.2 asp-href-exclude例子
asp-href-exclude属性用来从视图中排除CSS文件,现在使用它来移除reboot和grid CSS文件. 如下代码所示:
现在,值包含1个CSS文件:
10 从CDN加载CSS
我们可以从CDN中加载CSS文件,在layout页面添加下面代码:
href属性指定CDN的地址,asp-fallback-href-include 和 asp-fallback-href-exclude 表示如果CDN不可用,选择本地站点加载CSS文件
下面3个帮助标签使用测试是否bootstrap文件能够从CDN下载成功:
1. asp-fallback-test-class
2. asp-fallback-test-property
3. asp-fallback-test-value
工作方式是将一个元素添加到的文档中, 这个元素在标记asp-fallback-test-class中定义
如下图所示
asp-fallback-test-property指定CSS属性,asp-fallback-test-value设置属性的值,这些属性用来判断CDN是否正确加载,如果失败fallback的文件将被加载
源代码地址
https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.TagHelpers/AspNetCore.BuiltInTagHelpers
参考文献
[1]https://www.yogihosting.com/aspnet-core-introduction-tag-helpers/