饮水思源,来自:http://www.cnblogs.com/liontone 的BLOG中关于taghelper中的内容
概要
TagHelper是ASP.NET 5的一个新特性。也许在你还没有听说过它的时候, 它已经在技术人员之间引起了大量讨论,甚至有一部分称它为服务器控件的回归。实际上它只不过是一个简化版本,把HTML和服务器内容混合在一起,没有控件生命周期,状态保持和事件。它不像服务器控件那样,对页面所有内容都具有访问权限。它只能访问到自己所生成的内容。
什么是TagHelper?
我们曾经在MVC项目中使用在HtmlHelps。比如当需要在View上添加一个导航栏的时候,我们就会添加下面代码到页面上去:
1 2 3 4 5 | < ol > … < li >@Html.ActionLink("Home", "Index", "Home")< li > … </ ol > |
这里有一些HTML元素。还有以@开头的一些C#代码,当View解析的时候这些C#代码会被解析成HTML元素。
当我们用TagHelper,我们同样可以用下面的代码来获得上面同样的效果:
1 2 3 4 5 | < ol > … < li >< a controller="Home" action="Index">Home</ a ></ li > … </ ol > |
在这里,A元素的属性controller和action并不是HTML5的属性,而是这个TagHelper的属性。
这里需要着重申明一点是,虽然TagHelper看起来有点像我们之前服务器控件的写法,但是它不是服务器控件的再次回归。它更像是给用户一个简洁的方式来表达用户的意向。它本身并没有生命周期之类的东西。它会预处理服务器内容,然后赋值给相应的属性。我们来看一下关于微软提供的SelectTagHelper的一个用法:
1 | < select asp-for="Country" asp-items="ViewBag.Countries"> |
其中文本“Country”被赋给asp-for属性,变量ViewBag.Countries被赋给属性asp-items.
怎么使用TagHelpers?
关于如何使用TagHelpers,我们可以参照一下几步:
- 在project.json文件中添加具有TagHelpers的package的依赖;
- 在所用的View上注册TagHelper:1
@addTagHelper “[the full type name of taghelper,] the assembly name”
第一参数是TagHelper类的全名,当你只需要使用某一个TagHelper时候,你可以在此指定你所用的TagHelper类全名,包括它的NameSpace。如果你要使用程序集中所有的TagHelpers,在这里你可以使用“*”或者省略这一个参数。举个例子,如果你需要使用微软提供的TagHelpers可以通过添加下面的代码在注册:
1@addTagHelper
"*, Microsoft.AspNet.Mvc.TagHelpers"
或者
1@addTagHelper
"Microsoft.AspNet.Mvc.TagHelpers"
如果你只希望使用AnchorTagHelper,那么只需要一下代码:
1@addTagHelper
"Microsoft.AspNet.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNet.Mvc.TagHelpers"
第二个参数是程序集名。
当然,如果你想取消某个TagHelper的注册,可以使用removeTagHelper,比如1@removeTagHelper
"Microsoft.AspNet.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNet.Mvc.TagHelpers"
一旦TagHelper被取消注册了,其对应的Tag就不能被解析成这种TagHelper了。
- 在View中需要使用的地方添加TagHelper,比如我们添加一个AnchorTagHelper:1
<
a
asp-controller="Home" asp-action="About">About</
a
>
微软随着ASP.NET5一起发布的TagHelpers。它们分别是:
- AnchorTagHelper
- CacheTagHelper
- EnvironmentTagHelper
- InputTagHelper
- LabelTagHelper
- SelectTagHelper
- OptionTagHelper
- TextAreaTagHelper
- ValidationMessageTagHelper
- ValidationSummaryTagHelper
- FormTagHelper
- LinkTagHelper
- ScriptTagHelper
AnchorTagHelper
这个TagHelper被应用在所有锚元素<a>上,它拥有一下属性:
- asp-action
指定action方法名。 - asp-controller
指定controller名。 - asp-fragment
指定URL片段名。 - asp-host
指定访问的主机(host)名。 - asp-protocol
指定访问协议,比如http或者https。 - asp-route
指定路由名。
最终这个TagHelper会被解析成具有href属性的锚元素,这个href的内容就是基于以上这些属性的值生成的。
1 | < a asp-action="Create">Create New</ a > |
CacheTagHelper
应用在cache元素上,使用IMemoryCache实例来缓存cache元素内容在当前进程的内存中。它支持下面的属性:
- vary-by
string类型,TagHelper将基于该值来缓存内容,该值会被用来生成cache key。 - vary-by-header
string类型,指定请求头(request header),只能指定单个头名(header name),TagHelper将基于该值来缓存内容,该值会被用来生成cache key。 - vary-by-query
string类型,指定请求参数,当有多个请求参数时,以逗号分隔。TagHelper将基于它来缓存内容,该值会被用来生成cache key。 - vary-by-route
string类型,指定路由数据参数,当有多个路由数据参数时,以逗号分隔。TagHelper将基于它来缓存内容,该值会被用来生成cache key。 - vary-by-cookie
string类型,指定cookies名,当有多个cookies时,以逗号分隔。TagHelper将基于这些cookies名来缓存内容,该值会被用来生成cache key。 - vary-by-user
bool类型,指定是否为每个登陆的用户使用缓存,用户信息被用来生成cache key。 - expires-on
DateTime类型,指定缓存失效的时间。 - expires-after
TimeSpan类型,指定经过多少时间,缓存失效,这个时间是从加入缓存开始计时。 - expires-sliding
TimeSpan类型,指定缓存没被使用后经过多少时间失效。 - priority
enum类型,具有以下可能的值:- CachePreservationPriority.Low
- CachePreservationPriority.Normal
- CachePreservationPriority.High
- CachePreservationPriority.NeverRemove
存储在IMemoryCache实例中的缓存受限于当前可用内存。如果当服务器将要内存溢出时,内存cache就会清除缓存来释放内容。此时,我们可以用这个属性来指定当前缓存的优先级,这样内存在释放时就会考虑释放优先级低的缓存。
EnvironmentTagHelper
应用在environment元素上,根据不同的names的设置有条件的render不同的内容。它支持以下属性:
- names
指定环境名,当有多个时候以逗号分隔。这里判断的依据是,读取IHostingEnvironment
的EnvironmentName
的值,与environment元素中的names匹配,当匹配上的时候就render出里面的内容,否则移除该environment元素。
在很多情况下,我们想再开发环境使用一套配置信息,在生产环境又是另外一套,这时候就需要使用条件判断语句了,不过在新版的MVC中,使用EnvironmentTagHelper提供的Environment元素标签就可以了,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | < environment names="Development"> < script src="~/lib/jquery/jquery.js"></ script > < script src="~/lib/bootstrap/js/bootstrap.js"></ script > < script src="~/lib/hammer.js/hammer.js"></ script > < script src="~/lib/bootstrap-touch-carousel/js/bootstrap-touch-carousel.js"></ script > </ environment > < environment names="Staging,Production"> < script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.2.min.js" asp-fallback-src="~/lib/jquery/jquery.min.js" asp-fallback-test="window.jQuery"> </ script > < script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js" asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js" asp-fallback-test="window.jQuery"> </ script > < script src="//ajax.aspnetcdn.com/ajax/hammer.js/2.0.4/hammer.min.js" asp-fallback-src="~/lib/hammer.js/hammer.js" asp-fallback-test="window.Hammer"> </ script > < script src="//ajax.aspnetcdn.com/ajax/bootstrap-touch-carousel/0.8.0/js/bootstrap-touch-carousel.js" asp-fallback-src="~/lib/bootstrap-touch-carousel/js/bootstrap-touch-carousel.js" asp-fallback-test="window.Zepto"> </ script > </ environment > |
在上述代码中,我们定于,如果是Development环境就使用本地的js文件,否则(Staging或Production环境)就先加载cdn的文件。
InputTagHelper
这个TagHelper被应用在input元素上,与HtmlHelpers中的TextBoxForHTML一样,这个TagHelper会生成一个绑定到model中某个字段的Input元素。它支持一下属性:
- asp-for
- asp-format
asp-for用来指定绑定model哪个字段到TagHelper上,很多其他的TagHelpers也具有这个属性。
asp-format用来设置显示的Format,通常被用来给货币、日期和时间类型的值设置Format,比如,Birthday是model里的一个日期类型的字段。
1 | < input asp-for="Birthday" asp-format="{0:yyyy-MM-dd}"/> |
注意: asp-for的类型ModelExpression是ASP.NET MVC 6里面新定义的一个类,其构造函数带有一个字符类型的参数,用来指定Model中字段的名字。我们也可以把一个内嵌对象赋给asp-for,比如:
1 | < input asp-for="Address.Street" type="text" /> |
LabelTagHelper
与HtmlExtension.LabelFor功能一样,它只有一个属性asp-for,用来指定绑定Model里某个字段。它作用在label元素上。
1 | < label asp-for="Birthday"/> |
SelectTagHelper
SelectTagHelper作用在Select元素上,支持asp-for和asp-items属性。
asp-for与我们上面介绍的一样,用来绑定model中某个字段。
asp-items,被用来指定Select元素的Option集合,它的值类型是IEnumerable<SelectListItem>。
1 | < select asp-for="Country" asp-items="ViewBag.Countries"> |
如果要在Select中添加一个默认选择的项,我们可以这样做:
1 2 3 | < select asp-for="Country" asp-items="ViewBag.Countries"> < option selected="selected"value="">Choose Country</ option > </ select > |
1 2 3 4 5 6 7 8 | @{ SelectListItem[] items = { new SelectListItem() { Text = "item 1" }, new SelectListItem() { Text = "item 2" } }; } < select asp-for="Country" asp-items="items"></ select > |
OptionTagHelper
应用在option元素上,和select元素一起使用,通常被用来读取option元素信息,而不改变元素内容。唯一可能修改的是在有的情况下,会根据父亲select元素将option的selected状态设成"selected"。
1 2 3 | < select asp-for="Country"asp-items="ViewBag.Countries"> < option selected="selected" value="">Choose Country</ option > </ select > |
TextAreaTagHelper
应用在textarea元素上,目前只支持唯一一个属性asp-for,
1 | < textarea asp-for="Information"></ textarea > |
ValidationMessageTagHelper
与HtmlHelper中的ValidationMessageFor一样,这个TagHelper是用来显示验证失败信息。它应用在span元素上,而且只有唯一的一个属性asp-validation-for,被用来指定所验证的对象----Model中某个字段。
1 2 | < input asp-for="Birthday" asp-format="{0:yyyy-MM-dd}"/> < span asp-validation-for="Birthday"/> |
ValidationSummaryTagHelper
像HTMLHelper扩展的ValidationSummary一样,它是用来验证错误的汇总信息。它只支持一个属性asp-validation-summary,具有以下几种值:
- None
不显示任何验证信息 - ModelOnly
只显示Model错误信息,不包括属性错误信息 - All
显示所有信息
它应用在div元素上,
1 | < div class="validation" asp-validation-summary="ModelOnly"/> |
FormTagHelper
与HtmlHelper中的BeginForm一样,它用来生成一个form元素,它应用在form元素上,支持以下属性:
- asp-action
- asp-controller
- asp-anti-forgery
1 | < form asp-action="FormSave" asp-controller="Home" asp-anti-forgery="true"> |
LinkTagHelper
应用在link元素上,支持备用的样式文件。它具有以下属性:
- href
指定样式资源的链接地址。 - asp-href-include
指定所有需要被加载的样式文件路径格式,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。 - asp-href-exclude
指定那些不需要被加载的样式文件路径格式,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。 - asp-fallback-href
指定备用资源链接地址。 - asp-fallback-href-include
指定所有需要被加载的备用样式文件路径格式,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。 - asp-fallback-href-exclude
指定那些不需要被加载的备用样式文件路径格式,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。 - asp-fallback-test-class
用来检测加载失败的样式名。 - asp-fallback-test-property
用来检测资源加载失败所用的测试属性。 - asp-fallback-test-value
用来检测资源加载失败所用的测试值。 - asp-file-version
bool值,用来指定是否需要将文件版本信息加入到url地址中。
例如,在下面例子中,当从网络上(http://ajax.aspnetcdn.com/ajax/bootstrap-touch-carousel/0.8.0/css/bootstrap-touch-carousel.css)加载样式文件失败时,加载本地相应的样式文件(~/lib/bootstrap-touch-carousel/css/bootstrap-touch-carousel.css)。通过检测样式类carousel-caption中display属性是否是none来判断网络上样式文件是否加载成功。
1 2 3 4 5 6 | < link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap-touch-carousel/0.8.0/css/bootstrap-touch-carousel.css" asp-fallback-href="~/lib/bootstrap-touch-carousel/css/bootstrap-touch-carousel.css" asp-fallback-test-class="carousel-caption" asp-fallback-test-property="display" asp-fallback-test-value="none" /> |
ScriptTagHelper
应用在script元素上,和LinkTagHelper一样,它也具有fallback功能, 只不过这里判断的不是class样式,而是检测某个对象是否存在,来判断默认的js文件是否加载成功。
- src
指定要加载的js源地址。 - asp-src-include
指定要加载的js文件格式,当有多个文件格式时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。 - asp-src-exclude
指定不需要加载的js文件格式,当有多个文件格式时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。 - asp-fallback-src
指定备用的js源地址。 - asp-fallback-src-include
指定需要加载的备用js文件格式,当有多个文件格式时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。 - asp-fallback-src-exclude
指定不需要加载的备用js文件格式,当有多个文件格式时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。 - asp-fallback-test
指定用来检测js加载成功与否的对象 - asp-file-version
bool值,用来指定是否需要将文件版本信息加入到url地址中。
1 2 3 4 | < script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.2.min.js" asp-fallback-src="~/lib/jquery/jquery.min.js" asp-fallback-test="window.jQuery"> </ script > |
前面介绍了TagHelper的基本概念和内嵌的TagHelpers,想必大家对TagHelper都有一定的了解。TagHelper看上去有点像WebControl,但它不同于WebControl,没有复杂的生命周期、状态保持、服务器事件以及较高权限,它只能修改自己Tag的内容。有时觉得它更像angular写出来的一个widget,有自己特有的Tag,并对其进行解析生成出widget ui和启动脚本,但是它具有更高的权限,能访问服务器端信息。
在这章,将要介绍如何自定义TagHelper,考虑到内容比较多,会分成几个小章节来介绍。
TagHelper基类
通过查看内嵌的TagHelpers的源码,发现这些TagHelpers都继承自Microsoft.AspNet.Razor.Runtime.TagHelpers.TagHelper抽象类,而这个类又实现了接口Microsoft.AspNet.Razor.Runtime.TagHelpers.ITagHelper。这个接口是TagHelper最底层的接口,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /// <summary> /// Contract used to filter matching HTML elements. /// </summary> public interface ITagHelper { /// <summary> /// 获取实现ITagHelper接口的类的解析顺序. /// 值小的先用该类来解析。 /// </summary> int Order { get ; } /// <summary> /// 根据所给参数context和output异步解析实现ITagHelper的当前类. /// </summary> /// <param name="context">当前HTML Tag的相关信息</param> /// <param name="output">用于生成HTML Tag的HTML元素.</param> /// <returns>用于更新output的Task实例.</returns> Task ProcessAsync(TagHelperContext context, TagHelperOutput output); } |
TagHelper类,主要实现了上面接口,添加了一个同步方法来解析当前类,定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /// <summary> /// Class used to filter matching HTML elements. /// </summary> public abstract class TagHelper : ITagHelper { public virtual int Order { get ; } = 0; //同步解析当前TagHelper public virtual void Process(TagHelperContext context, TagHelperOutput output) { } //异步解析当前TagHelper public virtual async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { Process(context, output); } } |
一般自定义TagHelper时,只需要设计的类继承于TagHelper类,重载Process方法,根据context,设置output输出html元素和脚本即可。
TagHelper类名
自定义的TagHelper类名格式为***TagHelper,其中省略部分***就是该TagHelper应用的Tag。比如我们定义:
1 2 3 4 | public class MyTagHelper: TagHelper { .... } |
那么页面上所有Tag是my都会进入到这个类来解析,注意Tag名要是小写。
如果***中包括好几个大写字母,那么对于的Tag名是将大写字母变小写前面加上"-",如果大写字母在第一个位置,那么只需要改成小写,前面不需要加“-”。比如
1 2 3 4 | public class MyFirstOneTagHelper: TagHelper { ... } |
那么作用的Tag是
1 | < my-first-one ... ></ my-first-one > |
我们还可以在类上添加TargetElementAttribute来设置作用的Tag,比如定义如下的类时,其作用的Tag名为my。
1 2 3 4 5 | [TargetElement( "my" )] public class MyFirstOneTagHelper: TagHelper { ... } |
Attributes
在最新的VS2015RC版,开始支持了TagHelper的智能提示,主要体现在在写TagHelper有Attributes的提示,正确的Tag和Attribute会变成粗体,错误的Attribute不会变成粗体,这样就很容易发现书写错误。
如何定义一个TagHelper的Attribute?
Attribute就是Property。也就是说TagHelper类中的具有getter和setter的公开Property就是它的Attribute。
12345678910111213public
class
MyTagHelper: TagHelper
{
//私有property不是Attribute
private
int
ID {
get
;
set
;}
//这个才会是Attribute<br> public string Name {get; set;}
<br>
//只读的property也不是Attribute<br> public string Sex {get;}
public
override
void
Process(TagHelperContext context, TagHelperOutput output)
{
...
}
}
上面我们定义了MyTagHelper,它只有一个Attribute是Name。所以View上html应该是这样:
1<
my
name="yy"></
my
>
Attribute名和Property名关系
Attribute名一般都是小写,会将Property名将大写字母变小写前面加上"-",如果大写字母在第一个位置,那么只需要改成小写,前面不需要加“-”。这点和TagHelper的作用的Tag名和TagHelper类之间的关系有些类似。
比如
1234public
class
MyTagHelper: TagHelper
{
public
string
ParentName {
get
;
set
;}
}
其对应的View上的html是:
1<
my
parent-name="Mike"></
my
>
当然我们也可以在Property头上添加HtmlAttributeName来添加自定义的Attribute名,这样就让这两者之间毫无关系。
比如
12345678public
class
MyTagHelper: TagHelper
{
[HtmlAttributeName(
"male"
)]
public
bool
Sex {
get
;
set
;}
[HtmlAttributeName(
"NickName"
)]
public
string
Name {
get
;
set
;}
}
这样MyTagHelper里面的Sex Property对应的Attribute在View上名字是male,而Name Property对应的Attribute是NickName:
1<
my
male="true" NickName="yy"></
my
>
由于自定义名字,你定义成什么那么对应的Attribute就是什么,不受大小写限制,但是不能包括这些特殊字符'@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*'。
不想让一个公开的Property成为Attribute(经查证,HtmlAttributeNotBound是在Beta5中才有的。)
有时候某个Property不得不公开出来,但是又不想其作为一个Attribute,怎么办?
最简单的一个方法就是在这个Property上添加HtmlAttributeNotBound,这样就不会生成相应的Attribute了。123456public
class
MyTagHelper: TagHelper
{
[HtmlAttributeNotBound()]
public
bool
Sex {
get
;
set
;}
public
string
Name {
get
;
set
;}
}
还有一个不推荐的方法就是将setter设成internal或者private。
Attribute类型
通常Property的所有类型,Attribute都支持。那么怎么在Tag上设置Attribute的值?
- 设置常量(bool, number, string, enum)1
<
my
name="yy" age="34" married="true" sex="Sex.Male"></
my
>
其中Sex定义如下
12345public
enum
Sex
{
Male = 0,
FeMale =1
}
- 设置成一个变量12345
@{
var
name =
"yy"
;<br>
var
age = 34;
}
<my name=
"@name"
age=
"age"
></my>
这里有一点需要注意,当Attribute的Type(也就是Property的Type)是string类型时,在设置Attribute值时需要在这个变量前加上@符号,用以和文本常量“name”作区分,其他的类型可以省去这个@符号。
- 设置成一个表达式1
<
my
name="yy" location="new Location{Country="China", City="Shanghai"}"></
my
>
其中location Attribute的Type是Location,定义如下:
12345public
class
Location
{
public
string
Country{
get
;
set
;}
public
string
City{
get
;
set
;}
}
- 设置常量(bool, number, string, enum)
Property类型是复杂类型
例如上面location Attribute, 这是一个复杂类型,有公开的构造函数(默认不写构造函数,会创建一个不带参数的构造函数),我们很自然想到上面4里介绍的方法来设置这个Attribute值的几种方式:
- 一就是创建出一个实例出来,然后赋给一个变量,再将这个变量赋给Attribute;
- 另外一种就是直接把创建实例的表达式赋给Attribute。
但是有时候我们会遇到这样的问题:
- 构造函数没有public出来,用户无法创建出这样的实例;
- 类型非常复杂,如果通过code方式创建这样的实例,代码会比较多,而且设置了哪些值会不直观,如果像WebControl那样可以有一个内嵌的Tag来设置会比较直观。
正是基于这些问题和需要,我们需要将其类型或者Property设计成一个内嵌的TagHelper,关于如何设计成一个内嵌的TagHelper,怎么保持父子TagHelper的联系,我们下篇再介绍。
关于TagHelper的那些事情——自定义TagHelper(内嵌TagHelper)
内嵌TagHelper
上一篇文章中提到有时候需要设计一种内嵌的TagHelper,如下:
1 2 3 | <my name= "yy" age= "35" > <location country= "China" city= "Shanghai" district= "PuDong" ></location> </my> |
location就是一个内嵌的TagHelper,我们可以在location里设置与它相关的Attributes,有时候设置有多层内嵌的TagHelper。那么怎样设计出这样的一个内嵌的TagHelper来呢?其实它和一般的TagHelper没什么的区别,大家可以利用前面我们介绍的来设计出它的TagHelper类及Attributes。和一般的TagHelper的主要区别是,它和父TagHelper关联。如何建立这种关联?这也是这章我们需要搞清楚的。
通过前面章节学习,我们知道设计的自定义TagHelper类都会继承于抽象类TagHelper,在这个类中有二个方法:
1 2 3 4 5 | //同步处理 public virtual void Process(TagHelperContext context, TagHelperOutput output); //异步处理 public virtual Task ProcessAsync(TagHelperContext context, TagHelperOutput output); |
一般我们会根据实际情况来选择重载哪个方法。在TagHelper抽象类中,异步处理是调用同步处理方法。在这两个方法中参数相同,在这里详细介绍第一个参数context。
它的类型是TagHelperContext,主要是存放TagHelper相关的信息:
AllAttributes
TagHelper支持的所有Attribute集合,它是只读的。
Items
类型是IDictionary<object, object>,它是用来和其他TagHelper进行联系枢纽,
12345678/// <summary>
/// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s.
/// </summary>
/// <remarks>
/// This <see cref="IDictionary{object, object}"/> is copy-on-write in order to ensure items added to this
/// collection are visible only to other <see cref="ITagHelper"/>s targeting child elements.
/// </remarks>
public
IDictionary<
object
,
object
> Items {
get
; }
从上面的描述可以看出,这个集合是copy-on-write,也就是说当前TagHelper在Items获取父TagHelper中的Items信息,也可以修改,删除或者添加某一项,但是不会影响到父TagHelper的Items值,同时也会将修改后的Items信息传给其子TagHelper,子TagHelper的任何修改不会影响到它。
在上面我们提到,设计支持内嵌TagHelper类,关键是要建立父子TagHelper的联系,看到这里,我想大家都应该清楚如何建立这种关联。对了,就是利用context中Items。
主要是在TagHelper的Process方法中做以下事情。
1 2 3 4 5 6 7 | public virtual void Process(TagHelperContext context, TagHelperOutput output) { // 从context.items获取父TagHelper信息 // 处理自身Attributes // 将自身信息存放在context.items中 // 处理自己的子TagHelper } |
更具体的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | //定义父TagHelper public class PersonTagHelper : TagHelper { public string Name { get { return Person.Name; } set { Person.Name = value; } } public int Age { get { return Person.Age; } set { Person.Age = value; } } private Person _person; private Person Person { get { return _person ?? (_person = new Person()); } set { _person = value; } } public override void Process(TagHelperContext context, TagHelperOutput output) { // 保存信息给子TagHelper使用 context.Items[ "Parent" ] = Person; // 执行并获取子TagHelper内容 context.GetChildContentAsync(); // 输出html元素和启动脚本 // 这里会用到第二个参数output,后面会介绍到如何使用。 } } // 定义子TagHelper public class LocationTagHelper: TagHelper { public string Country { get ; set ; } public string City { get ; set ; } public string District { get ; set ; } public override void Process(TagHelperContext context, TagHelperOutput output) { // 获取来自父TagHelper的信息,并保存到变量里去 var parent = context.Items[ "Parent" ] as Person; // 处理Attributes的设置 parent.Location.Country = Country; parent.Location.City = City; parent.Location.District = District; // 保存自身信息,便于子TagHelper使用 context.Items[ "Parent" ] = parent.Location; // 执行并获取子TagHelper内容 context.GetChildContentAsync(); } } // 定义了父TagHelper中对应的对象实例 // 大家可以根据各自需求,决定是否需要定义这样一个类型 public class Person { public string Name { get ; set ; } public int Age { get ; set ; } private Location _location; public Location Location { get { return _location ?? (_location = new Location()); } } } // 定义子TagHelper中数据类型 // 大家可以根据各自需求,决定是否需要定义这样一个类型 public class Location { public string Country { get ; set ; } public string City { get ; set ; } public string District { get ; set ; } } |
对于类Person和Location的定义大家可以根据具体情况来决定是否需要。
这个例子只是简单展示如何利用Items信息来构建父子TagHelper间的联系,大家在具体的项目开发中根据实际需求,写出不一样的代码来,但有一点不变,就是要利用Items。
关于TagHelper的那些事情——自定义TagHelper(格式化输出、依赖注入使用)
自定义TagHelper的最后一步就是在Process方法或ProcessAsync方法中添加展现代码。熟悉WebControl开发的朋友都知道Render方法,在这个方法中会添加展现的Html元素和启动脚本,TagHelper的这一步我们要做的也就是和Render方法一样。
这里我们主要利用上面方法中的第二个参数output来往View上输出展现部分。
首先让我们看以output类型TagHelperOutput的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | /// <summary> /// Class used to represent the output of an <see cref="ITagHelper"/>. /// </summary> public class TagHelperOutput { /// <summary> /// Instantiates a new instance of <see cref="TagHelperOutput"/>. /// </summary> /// <param name="tagName">The HTML element's tag name.</param> /// <param name="attributes">The HTML attributes.</param> public TagHelperOutput( string tagName, IDictionary< string , object > attributes) { ... } /// <summary> /// The HTML element's tag name. /// </summary> /// <remarks> /// A whitespace or <c>null</c> value results in no start or end tag being rendered. /// </remarks> public string TagName { get ; set ; } /// <summary> /// The HTML element's pre content. /// </summary> /// <remarks>Value is prepended to the <see cref="ITagHelper"/>'s final output.</remarks> public TagHelperContent PreContent{ get ; } /// <summary> /// The HTML element's main content. /// </summary> /// <remarks>Value occurs in the <see cref="ITagHelper"/>'s final output after <see cref="PreContent"/> and /// before <see cref="PostContent"/></remarks> public TagHelperContent Content { get ; } /// <summary> /// The HTML element's post content. /// </summary> /// <remarks>Value is appended to the <see cref="ITagHelper"/>'s final output.</remarks> public TagHelperContent PostContent { get ; } /// <summary> /// <c>true</c> if <see cref="Content"/> has been set, <c>false</c> otherwise. /// </summary> public bool IsContentModified { get ; } /// <summary> /// Indicates whether or not the tag is self-closing. /// </summary> public bool SelfClosing { get ; set ; } /// <summary> /// The HTML element's attributes. /// </summary> /// <remarks> /// MVC will HTML encode <see cref="string"/> values when generating the start tag. It will not HTML encode /// a <c>Microsoft.AspNet.Mvc.Rendering.HtmlString</c> instance. MVC converts most other types to a /// <see cref="string"/>, then HTML encodes the result. /// </remarks> public IDictionary< string , object > Attributes { get ; } /// <summary> /// Changes <see cref="TagHelperOutput"/> to generate nothing. /// </summary> /// <remarks> /// Sets <see cref="TagName"/> to <c>null</c>, and clears <see cref="PreContent"/>, <see cref="Content"/>, /// and <see cref="PostContent"/> to suppress output. /// </remarks> public void SuppressOutput() { ... } } |
TagName:
指定输出到View上最外层Html元素的Tag。
PreContent
指定添加到Html元素主内容(Content)前面的。
Content
指定Html元素的主内容,在PreContent后面,PostContent前面。
PostContent
指定Html元素主内容后面的。
SupressOutput
不生成任何展示内容。
通常我们会根据实际需要设置output中这些属性,其中用的比较多的就是TagName和Content,在TagName指定生成HTML元素的最外层Tag,在Content添加其内部的Html元素和启动脚本。
我们知道ASP.NET 5实现了依赖注入,在TagHelper类中我们也可以通过依赖注入获取更多的系统实例对象,为具体需求所有。我们只需要在TagHelper类中,添加一个相关类型的属性,然后在属性头上添加Activate属性即可自动获取对应实例。比如,获取ViewContext信息,可以在类中添加如下代码:
1 2 | [Activate] public ViewContext ViewContext { get ; set ; } |
这样我们就可以在其他地方,通过属性ViewContext获取当前View的上下文信息。
通过这种方式,你可以获取到更多的系统实例对象,如ActionContext
、HttpContext
、HttpRequest
、HttpResponse
、 ViewDataDictionary
以及ActionBindingContext等