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)]
简单来说,它指定了我们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的地方添加就好
@addTagHelper 你的TagHelper , 你的TagHelper所在的命名空间
@addTagHelper * , 你的TagHelper所在的命名空间
可以添加,当然也可以删除,删除是@removeTagHelper
当我们在自己的框架中完全重写了一套自己的TagHelper,那么这个时候,微软自己的TagHelper我们就可以通过下面的方法来移除了。
@removeTagHelper * , Microsoft.AspNetCore.Mvc.TagHelpers