眾所周知,在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)]
簡單來說,它指定了我們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
好比如下的代碼,就可以直接用Controller
1 [HtmlTargetElement("form")] 2 public class FormTagHelper : TagHelper 3 { 4 public string Controller { get; set; } 5 }
總的來說有兩種用法。可以看到它指向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; }
果不其然,發現其對應了一個實現類: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 }
它就是用來創建我們的Html標簽,相信用過MVC的,多多少少都擴展過HtmlHelper,這是類似的。
最后,也是最最重要的重寫的Process方法。
如果包含,就是正常的html標簽。換句話說,正常的html寫法和我們的TagHelper方法會有沖突,只能用其中一種。
當我們這樣寫的時候,編譯能通過。
但是,運行的時候就會出錯。
再下面的處理就是用了TagBuilder去處理了。
如下面的寫法:
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>
1 <form method="post" action="/Product/Get/2"> 2 <button type="submit">submit</button> 3 </form>

下面是我們自己寫一個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>
@addTagHelper 你的TagHelper , 你的TagHelper所在的命名空間
@addTagHelper * , 你的TagHelper所在的命名空間
可以添加,當然也可以刪除,刪除是@removeTagHelper
當我們在自己的框架中完全重寫了一套自己的TagHelper,那么這個時候,微軟自己的TagHelper我們就可以通過下面的方法來移除了。
@removeTagHelper * , Microsoft.AspNetCore.Mvc.TagHelpers