饮水思源,来自: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,我们可以参照一下几步:

    1. 在project.json文件中添加具有TagHelpers的package的依赖;
    2. 在所用的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了。

    3. 在View中需要使用的地方添加TagHelper,比如我们添加一个AnchorTagHelper:
      1
      <a asp-controller="Home" asp-action="About">About</a

微软随着ASP.NET5一起发布的TagHelpers。它们分别是:

  1. AnchorTagHelper
  2. CacheTagHelper
  3. EnvironmentTagHelper
  4. InputTagHelper
  5. LabelTagHelper
  6. SelectTagHelper
  7. OptionTagHelper
  8. TextAreaTagHelper
  9. ValidationMessageTagHelper
  10. ValidationSummaryTagHelper
  11. FormTagHelper
  12. LinkTagHelper
  13. 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
    指定环境名,当有多个时候以逗号分隔。这里判断的依据是,读取IHostingEnvironmentEnvironmentName的值,与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>
我们可以赋任何类型是IEnumerable<SelectListItem>的实例给asp-items,可能是某个变量或者某个实例的一个属性等。
比如:
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不会变成粗体,这样就很容易发现书写错误。

    1. 如何定义一个TagHelper的Attribute?

      Attribute就是Property。也就是说TagHelper类中的具有getter和setter的公开Property就是它的Attribute。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class MyTagHelper: TagHelper
      {
          //私有property不是Attribute
          private int ID {getset;}
       
          //这个才会是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>
    2. Attribute名和Property名关系

      • Attribute名一般都是小写,会将Property名将大写字母变小写前面加上"-",如果大写字母在第一个位置,那么只需要改成小写,前面不需要加“-”。这点和TagHelper的作用的Tag名和TagHelper类之间的关系有些类似。

        比如

        1
        2
        3
        4
        public class MyTagHelper: TagHelper
        {
           public string ParentName {getset;}
        }

        其对应的View上的html是:

        1
        <my parent-name="Mike"></my>
      • 当然我们也可以在Property头上添加HtmlAttributeName来添加自定义的Attribute名,这样就让这两者之间毫无关系。

        比如

        1
        2
        3
        4
        5
        6
        7
        8
        public class MyTagHelper: TagHelper
        {  
             [HtmlAttributeName("male")]
             public bool Sex {getset;}
              
             [HtmlAttributeName("NickName")]
             public string Name {getset;}
        }

        这样MyTagHelper里面的Sex Property对应的Attribute在View上名字是male,而Name Property对应的Attribute是NickName:

        1
        <my male="true" NickName="yy"></my>

        由于自定义名字,你定义成什么那么对应的Attribute就是什么,不受大小写限制,但是不能包括这些特殊字符'@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*'。

    3. 不想让一个公开的Property成为Attribute(经查证,HtmlAttributeNotBound是在Beta5中才有的。)

      有时候某个Property不得不公开出来,但是又不想其作为一个Attribute,怎么办?
      最简单的一个方法就是在这个Property上添加HtmlAttributeNotBound,这样就不会生成相应的Attribute了。

      1
      2
      3
      4
      5
      6
      public class MyTagHelper: TagHelper
      {  
           [HtmlAttributeNotBound()]
           public bool Sex {getset;}    
           public string Name {getset;}
      }

      还有一个不推荐的方法就是将setter设成internal或者private。

    4. Attribute类型

      通常Property的所有类型,Attribute都支持。那么怎么在Tag上设置Attribute的值?

      • 设置常量(bool, number, string, enum)

        1
        <my name="yy" age="34" married="true" sex="Sex.Male"></my>  

        其中Sex定义如下

        1
        2
        3
        4
        5
        public enum Sex
        {
            Male = 0,
            FeMale =1
        } 
      • 设置成一个变量
        1
        2
        3
        4
        5
        @{
            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,定义如下:

        1
        2
        3
        4
        5
        public class Location
        {
           public string Country{getset;}
           public string City{getset;}
        }
    5. 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相关的信息:

  1. AllAttributes

    TagHelper支持的所有Attribute集合,它是只读的。

  2. Items

    类型是IDictionary<object, object>,它是用来和其他TagHelper进行联系枢纽,

    1
    2
    3
    4
    5
    6
    7
    8
    /// <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<objectobject> 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 { getset; }
     public string City { getset; }
     public string District { getset; }
 
     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 { getset; }
     public int Age { getset; }
 
     private Location _location;
     public Location Location
     {
         get return _location ?? (_location = new Location()); }
     }
 }
 
 // 定义子TagHelper中数据类型
 // 大家可以根据各自需求,决定是否需要定义这样一个类型
 public class Location
 {
     public string Country { getset; }
     public string City { getset; }
     public string District { getset; }
 }

对于类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<stringobject> 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 { getset; }
 
    /// <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 { getset; }
 
    /// <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<stringobject> 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 { getset; }

这样我们就可以在其他地方,通过属性ViewContext获取当前View的上下文信息。

通过这种方式,你可以获取到更多的系统实例对象,如ActionContextHttpContextHttpRequestHttpResponse、 ViewDataDictionary以及ActionBindingContext等

ScriptTagHelper

05-26 11:49