TagHelper是怎么实现的

众所周知,在asp.net core中编写Razor视图的时候,用了一种新的写法--TagHelper

那这个TagHelper是怎么回事呢?

首先来看看TagHelper的项目位置,它是位于Microsoft.AspNetCore.Mvc.TagHelpers。

如果看到project.json,可以发现,它还依赖一个比较重要的东西Microsoft.AspNetCore.Mvc.Razor

为什么这么说呢,其实很简单,看了里面诸多TagHelper,就会发现,里面都是继承了

Microsoft.AspNetCore.Razor.TagHelpers下面的TagHelper这个抽象类。

下面就以我们天天用到的表单--FormTagHelper为例来说一下,他是怎么实现的。

首先要看看TagHelper这个抽象类:

1     public abstract class TagHelper : ITagHelper
2     {
3         protected TagHelper();
4         public virtual int Order { get; }
5         public virtual void Init(TagHelperContext context);
6         public virtual void Process(TagHelperContext context, TagHelperOutput output);
7         public virtual Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
8     }

里面包含两比较重要的方法:Process和ProcessAsync

其实看方法名就应该知道一个是同步的方法一个是异步的方法

因为这个是输出html的方法,你说,这能不重要吗?下面来看看FormTagHelper的具体实现吧!

1 [HtmlTargetElement("form", Attributes = ActionAttributeName)]

先来看看HtmlTargetElement这个Attribute是用来干嘛的

简单来说,它指定了我们html标签(<form></form>)以及一些相关的元素。

可以看到,诸多Attributes = XXXAttributeName,其中的XXXAttributeName是在类里面定义的变量。

1         private const string ActionAttributeName = "asp-action";
2         private const string AntiforgeryAttributeName = "asp-antiforgery";
3         private const string AreaAttributeName = "asp-area";
4         private const string ControllerAttributeName = "asp-controller";
5         private const string RouteAttributeName = "asp-route";
6         private const string RouteValuesDictionaryName = "asp-all-route-data";
7         private const string RouteValuesPrefix = "asp-route-";
8         private const string HtmlActionAttributeName = "action";    

再来看看下面的图,相对比一看,是不是就很清晰了呢?

我们可以看到下面的好几个属性,如Controller,它的上面是有 HtmlAttributeName来标注的

而且这个指向的名字还是ControllerAttributeName(也就是asp-controller)。这个就是用来接收asp-controller的值。

1 [HtmlAttributeName(ControllerAttributeName)]
2 public string Controller { get; set; }

相对来说,这样做只是起了个别名。

1     [HtmlTargetElement("form", Attributes = ActionAttributeName)]
2     [HtmlTargetElement("form", Attributes = AntiforgeryAttributeName)]
3     [HtmlTargetElement("form", Attributes = AreaAttributeName)]
4     [HtmlTargetElement("form", Attributes = ControllerAttributeName)]
5     [HtmlTargetElement("form", Attributes = RouteAttributeName)]
6     [HtmlTargetElement("form", Attributes = RouteValuesDictionaryName)]
7     [HtmlTargetElement("form", Attributes = RouteValuesPrefix + "*")]
8     public class FormTagHelper : TagHelper

当然,我们也是可以不指定别名的,也可以不用在HtmlTargetElement指明Attributes

好比如下的代码,就可以直接用Controller

1 [HtmlTargetElement("form")]
2  public class FormTagHelper : TagHelper
3  {
4   public string Controller { get; set; }
5  }

还有一个RouteValues的属性,它是一个键值对,用来存放参数的,具体可以怎么用呢?

总的来说有两种用法。可以看到它指向asp-all-route-data和asp-route-

1 [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]

用法如下:一种是用asp-all-route-data来接收一个IDictionary类型的变量,一种是通过asp-route-*的方式来接收参数*的值。

这两种写法是等价的。

下面就是FormTagHelper的构造函数和一个Generator属性

1         public FormTagHelper(IHtmlGenerator generator)
2         {
3             Generator = generator;
4         }
5          protected IHtmlGenerator Generator { get; }

由于在Core中,依赖注入随处可见,看到这个写法马上就是想到了这个

果不其然,发现其对应了一个实现类:DefaultHtmlGenerator。

 1     public class DefaultHtmlGenerator : IHtmlGenerator
 2     {
 3         public DefaultHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor, IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder, ClientValidatorCache clientValidatorCache);
 4         public virtual TagBuilder GenerateActionLink(ViewContext viewContext, string linkText, string actionName, string controllerName, string protocol, string hostname, string fragment, object routeValues, object htmlAttributes);
 5         public virtual IHtmlContent GenerateAntiforgery(ViewContext viewContext);
 6         public virtual TagBuilder GenerateForm(ViewContext viewContext, string actionName, string controllerName, object routeValues, string method, object htmlAttributes);
 7         public virtual TagBuilder GenerateLabel(ViewContext viewContext, ModelExplorer modelExplorer, string expression, string labelText, object htmlAttributes);
 8         public virtual TagBuilder GenerateTextArea(ViewContext viewContext, ModelExplorer modelExplorer, string expression, int rows, int columns, object htmlAttributes);
 9         public virtual TagBuilder GenerateTextBox(ViewContext viewContext, ModelExplorer modelExplorer, string expression, object value, string format, object htmlAttributes);
10         protected virtual TagBuilder GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, string expression, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes);
11         protected virtual TagBuilder GenerateLink(string linkText, string url, object htmlAttributes);
12      ....省略部分
13     }

这个类里面,我们看到了熟悉的TagBuilder,就算不去看它里面的实现都能知道它是用来干嘛的

它就是用来创建我们的Html标签,相信用过MVC的,多多少少都扩展过HtmlHelper,这是类似的。

最后,也是最最重要的重写的Process方法。

可以看到开始就判断了表单<form>中是否包含了action这个属性output.Attributes.ContainsName(HtmlActionAttributeName)

如果包含,就是正常的html标签。换句话说,正常的html写法和我们的TagHelper方法会有冲突,只能用其中一种。

当我们这样写的时候,编译能通过。

但是,运行的时候就会出错。

再下面的处理就是用了TagBuilder去处理了。

收集路由的数据放到一个字典中->区域是否存在->用Generator去创建form表单,返回TagBuilder对象->TagHelperOutput对象把tagbuilder的innerhtml等信息输出。

如下面的写法:

1 <form method="post" asp-action="Get" asp-controller="Product" asp-antiforgery="false" asp-route-id="2">
2   <button type="submit">submit</button>
3 </form>

生成对应的html如下:

1 <form method="post" action="/Product/Get/2">
2   <button type="submit">submit</button>
3 </form>

到这里,FormTagHelper的讲解就算是OK,至于其他的,原理都是差不多,就不再累赘了。

来看看,到底有多少种TagHelper(还没有部分没有列出来),以及它们包含的属性。

下面是我们自己写一个TagHelper——CatcherATagHelper,这个TagHelper是干什么的呢?它只是一个精简版的A标签。

 1 using Microsoft.AspNetCore.Mvc;
 2 using Microsoft.AspNetCore.Mvc.Rendering;
 3 using Microsoft.AspNetCore.Mvc.Routing;
 4 using Microsoft.AspNetCore.Mvc.TagHelpers;
 5 using Microsoft.AspNetCore.Mvc.ViewFeatures;
 6 using Microsoft.AspNetCore.Razor.TagHelpers;
 7
 8 namespace Catcher.EasyDemo.Controllers.TagHelpers
 9 {
10     [HtmlTargetElement("catcher-a")]
11     public class CatcherATagHelper:TagHelper
12     {
13         public CatcherATagHelper(IHtmlGenerator generator, IUrlHelperFactory urlHelperFactory)
14         {
15             this.Generator = generator;
16             UrlHelperFactory = urlHelperFactory;
17         }
18
19         [HtmlAttributeNotBound]
20         public IUrlHelperFactory UrlHelperFactory { get; }
21
22         protected IHtmlGenerator Generator { get; }
23
24         public override int Order
25         {
26             get
27             {
28                 return -1000;
29             }
30         }
31
32         public string Action { get; set; }
33
34         public string Controller { get; set; }
35
36         public string LinkText { get; set; }
37
38         [ViewContext]
39         [HtmlAttributeNotBound]
40         public ViewContext ViewContext { get; set; }
41
42         public override void Process(TagHelperContext context, TagHelperOutput output)
43         {
44             //method 1
45             if (Action != null || Controller != null)
46             {
47                 output.Attributes.Clear();
48
49                 var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
50
51                 output.TagName = "a";
52
53                 output.Attributes.SetAttribute("href", urlHelper.Action(Action, Controller));
54                 //whether the inner html is null
55                 if (output.Content.IsEmptyOrWhiteSpace)
56                 {
57                     output.PreContent.SetContent(LinkText);
58                 }
59             }
60             //method 2
61             //TagBuilder tagBuilder;
62             //if (Action != null || Controller != null)
63             //{
64             //    tagBuilder = Generator.GenerateActionLink(
65             //            ViewContext,
66             //            linkText: string.Empty,
67             //            actionName: Action,
68             //            controllerName: Controller,
69             //            protocol: string.Empty,
70             //            hostname: string.Empty,
71             //            fragment: string.Empty,
72             //            routeValues: null,
73             //            htmlAttributes: null);
74
75             //    output.TagName = "a";
76             //    //whether the inner html is null
77             //    if (output.Content.IsEmptyOrWhiteSpace)
78             //    {
79             //        output.PreContent.SetContent(LinkText);
80             //    }
81             //    output.MergeAttributes(tagBuilder);
82             //}
83         }
84     }
85 }

这里提供了两种写法供大家参考

一种是借助IUrlHelperFactory去生成链接

一种是借助IHtmlGenerator去生成链接

写好了之后要怎么用呢?

不知道大家有没有留意_ViewImports.cshtml这个文件

1 @using Catcher.EasyDemo.Website
2 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
3 @inject Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration TelemetryConfiguration

这个是默认情况下帮我们添加的TagHelper

我们可以在要用到那个TagHelper的地方添加就好

1 @{
2     Layout = null;
3 }
4 @addTagHelper Catcher.EasyDemo.Controllers.TagHelpers.CatcherATagHelper , Catcher.EasyDemo.Controllers
5 <catcher-a action="list" controller="product" link-text="text">With LinkText And InnerHtml</catcher-a>
6 <br />
7 <catcher-a action="list" controller="product" link-text="">Without LinkText</catcher-a>
8 <br />
9 <catcher-a action="list" controller="product" link-text="Only With LinkText"></catcher-a>

Index.cshtml

addTagHelper的用法如下:

@addTagHelper 你的TagHelper , 你的TagHelper所在的命名空间

或者更直接

@addTagHelper * , 你的TagHelper所在的命名空间

可以添加,当然也可以删除,删除是@removeTagHelper

当我们在自己的框架中完全重写了一套自己的TagHelper,那么这个时候,微软自己的TagHelper我们就可以通过下面的方法来移除了。

@removeTagHelper * , Microsoft.AspNetCore.Mvc.TagHelpers

时间: 2024-10-25 01:11:40

TagHelper是怎么实现的的相关文章

关于TagHelper的那些事情(3)

写在开头 前面介绍了TagHelper的基本概念和内嵌的TagHelpers,想必大家对TagHelper都有一定的了解.TagHelper看上去有点像WebControl,但它不同于WebControl,没有复杂的生命周期.状态保持.服务器事件以及较高权限,它只能修改自己Tag的内容.有时觉得它更像angular写出来的一个widget,有自己特有的Tag,并对其进行解析生成出widget ui和启动脚本,但是它具有更高的权限,能访问服务器端信息. 在这章,将要介绍如何自定义TagHelper

【无私分享:ASP.NET CORE 项目实战(第九章)】创建区域Areas,添加TagHelper

目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 在Asp.net Core VS2015中,我们发现还有很多不太简便的地方,比如右击添加视图,转到试图页等功能图不见了,虽然我们可以通过工具栏的自定义命令,把这两个右击菜单添加上,但是貌似是灰色的不能用. 其实,这样也好,通过手动创建,更让我们深刻的理解MVC以及路由之间的关系,很多人认为底层的东西是高大上的,比如一提到汇编,很多人感觉牛的不行不行的,其实术业有专攻,做程序的感觉搞核电的很牛,搞核电的同样也感觉做程序的很牛

ASP.NET 5探险(8):利用中间件、TagHelper来在MVC 6中实现Captcha

(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:由于ASP.NET 5及MVC 6是一个微软全新重新的Web开发平台,之前一些现有的验证码库已经不能直接使用,故而我自行实现了一套简单的验证码库--CaptchaMVC6. CaptchaMVC6我已经开源到GitHub上(同时提供了使用范例给大家参考),大家可以通过"阅读原文"来访问项目页面. CaptchaMVC6虽然一些核心的一些算法和现有的验证码库没有本质区别,不过

asp.net core高级应用:TagHelper+Form

上一篇博客我讲解了TagHelper的基本用法和自定义标签的生成,那么我就趁热打铁,和大家分享一下TagHelper的高级用法~~,大家也可以在我的博客下随意留言. 对于初步接触asp.net core的骚年可以看看我对TagHelper的了解和看法: <asp.net core新特性(1):TagHelper> 之后,我也会继续撰写博文,继续分享asp.net core的一些新特性,比如DI,ViewComponent以及bower等asp.net mvc中没有的新东西. ok,咱们就开始吧

ASP.NET MVC Core的TagHelper (高级特性)

这篇博文ASP.NET MVC Core的TagHelper(基础篇)介绍了TagHelper的基本概念和创建自定义TagHelper的方式,接着继续介绍一些新的看起来比较高级的特性.(示例代码紧接着上一遍博文) 一.使用自定义的标记元素 之前基础篇介绍的TagHelper的功能是给已有的HTML元素提供一个自定义的属性标记,然后服务器认出这个标记后,将标记转化成最终的HTML.这里将要介绍的功能是,定义个全新的Tag,看起来跟普通的HTML元素一样.是不是觉得很熟悉呢(前提是你用过Angula

创建区域Areas,添加TagHelper

创建区域Areas,添加TagHelper 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 在Asp.net Core VS2015中,我们发现还有很多不太简便的地方,比如右击添加视图,转到试图页等功能图不见了,虽然我们可以通过工具栏的自定义命令,把这两个右击菜单添加上,但是貌似是灰色的不能用. 其实,这样也好,通过手动创建,更让我们深刻的理解MVC以及路由之间的关系,很多人认为底层的东西是高大上的,比如一提到汇编,很多人感觉牛的不行不行的,其实术业有专攻,做程序的感觉

关于TagHelper的那些事情——自定义TagHelper(格式化输出、依赖注入使用)

自定义TagHelper的最后一步就是在Process方法或ProcessAsync方法中添加展现代码.熟悉WebControl开发的朋友都知道Render方法,在这个方法中会添加展现的Html元素和启动脚本,TagHelper的这一步我们要做的也就是和Render方法一样. 这里我们主要利用上面方法中的第二个参数output来往View上输出展现部分. 首先让我们看以output类型TagHelperOutput的定义: /// <summary> /// Class used to rep

关于TagHelper的那些事情(2)

写在开始 在上一篇文章中,简单介绍了什么是TagHelper,怎么使用它.接下来我会简单介绍一下微软随着ASP.NET5一起发布的内嵌的TagHelpers.它们分别是: AnchorTagHelper CacheTagHelper EnvironmentTagHelper InputTagHelper LabelTagHelper SelectTagHelper OptionTagHelper TextAreaTagHelper ValidationMessageTagHelper Valid

TagHelper

TagHelper 在新版的MVC6中,微软提供了强大的TagHelper功能,以便让我们摆脱如下的臃肿代码: @Html.LabelFor(model => model.FullName) @Html.EditFor(model => model.FullName) @Html.ValidationMessageFor(model => model.FullName) 引入新功能TagHelper以后,我们只需要这样定义就可以了,代码如下: @addTagHelper "*,

关于TagHelper的那些事情(3)(续)

接上 Attributes 在最新的VS2015RC版,开始支持了TagHelper的智能提示,主要体现在在写TagHelper有Attributes的提示,正确的Tag和Attribute会变成粗体,错误的Attribute不会变成粗体,这样就很容易发现书写错误. 如何定义一个TagHelper的Attribute? Attribute就是Property.也就是说TagHelper类中的具有getter和setter的公开Property就是它的Attribute. public class