天天看點

TagHelper是怎麼實作的

探讨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";          

再來看看下面的圖,相對比一看,是不是就很清晰了呢?

TagHelper是怎麼實作的

我們可以看到下面的好幾個屬性,如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  }      
TagHelper是怎麼實作的

還有一個RouteValues的屬性,它是一個鍵值對,用來存放參數的,具體可以怎麼用呢?

總的來說有兩種用法。可以看到它指向asp-all-route-data和asp-route-

1 [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]      
TagHelper是怎麼實作的

用法如下:一種是用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方法會有沖突,隻能用其中一種。

當我們這樣寫的時候,編譯能通過。

TagHelper是怎麼實作的

但是,運作的時候就會出錯。

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是怎麼實作的

下面是我們自己寫一個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的地方添加就好

TagHelper是怎麼實作的
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

TagHelper是怎麼實作的

如果您認為這篇文章還不錯或者有所收獲,可以點選右下角的【推薦】按鈕,因為你的支援是我繼續寫作,分享的最大動力!

作者:Catcher Wong ( 黃文清 )

來源:http://catcher1994.cnblogs.com/

聲明:

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。如果您發現部落格中出現了錯誤,或者有更好的建議、想法,請及時與我聯系!!如果想找我私下交流,可以私信或者加我微信。

繼續閱讀