众所周知,在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 2 3 4 5 6 7 8 |
public abstract class TagHelper : ITagHelper { protected TagHelper(); public virtual int Order { get; } public virtual void Init(TagHelperContext context); public virtual void Process(TagHelperContext context, TagHelperOutput output); public virtual Task ProcessAsync(TagHelperContext context, TagHelperOutput output); } |
里面包含两比较重要的方法:Process和ProcessAsync 其实看方法名就应该知道一个是同步的方法一个是异步的方法 因为这个是输出html的方法,你说,这能不重要吗?下面来看看FormTagHelper的具体实现吧!
1 |
[HtmlTargetElement("form", Attributes = ActionAttributeName)] |
先来看看HtmlTargetElement这个Attribute是用来干嘛的 简单来说,它指定了我们html标签(<form></form>)以及一些相关的元素。 可以看到,诸多Attributes = XXXAttributeName,其中的XXXAttributeName是在类里面定义的变量。
1 2 3 4 5 6 7 8 |
private const string ActionAttributeName = "asp-action"; private const string AntiforgeryAttributeName = "asp-antiforgery"; private const string AreaAttributeName = "asp-area"; private const string ControllerAttributeName = "asp-controller"; private const string RouteAttributeName = "asp-route"; private const string RouteValuesDictionaryName = "asp-all-route-data"; private const string RouteValuesPrefix = "asp-route-"; private const string HtmlActionAttributeName = "action"; |
再来看看下面的图,相对比一看,是不是就很清晰了呢? 我们可以看到下面的好几个属性,如Controller,它的上面是有 HtmlAttributeName来标注的 而且这个指向的名字还是ControllerAttributeName(也就是asp-controller)。这个就是用来接收asp-controller的值。
1 2 |
[HtmlAttributeName(ControllerAttributeName)] public string Controller { get; set; } |
相对来说,这样做只是起了个别名。
1 2 3 4 5 6 7 8 |
[HtmlTargetElement("form", Attributes = ActionAttributeName)] [HtmlTargetElement("form", Attributes = AntiforgeryAttributeName)] [HtmlTargetElement("form", Attributes = AreaAttributeName)] [HtmlTargetElement("form", Attributes = ControllerAttributeName)] [HtmlTargetElement("form", Attributes = RouteAttributeName)] [HtmlTargetElement("form", Attributes = RouteValuesDictionaryName)] [HtmlTargetElement("form", Attributes = RouteValuesPrefix + "*")] public class FormTagHelper : TagHelper |
当然,我们也是可以不指定别名的,也可以不用在HtmlTargetElement指明Attributes 好比如下的代码,就可以直接用Controller
1 2 3 4 5 |
[HtmlTargetElement("form")] public class FormTagHelper : TagHelper { public string Controller { get; set; } } |
还有一个RouteValues的属性,它是一个键值对,用来存放参数的,具体可以怎么用呢? 总的来说有两种用法。可以看到它指向asp-all-route-data和asp-route-
1 |
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] |
用法如下:一种是用asp-all-route-data来接收一个IDictionary类型的变量,一种是通过asp-route-*的方式来接收参数*的值。 这两种写法是等价的。 下面就是FormTagHelper的构造函数和一个Generator属性
1 2 3 4 5 |
public FormTagHelper(IHtmlGenerator generator) { Generator = generator; } protected IHtmlGenerator Generator { get; } |
由于在Core中,依赖注入随处可见,看到这个写法马上就是想到了这个 果不其然,发现其对应了一个实现类:DefaultHtmlGenerator。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class DefaultHtmlGenerator : IHtmlGenerator { public DefaultHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor, IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder, ClientValidatorCache clientValidatorCache); public virtual TagBuilder GenerateActionLink(ViewContext viewContext, string linkText, string actionName, string controllerName, string protocol, string hostname, string fragment, object routeValues, object htmlAttributes); public virtual IHtmlContent GenerateAntiforgery(ViewContext viewContext); public virtual TagBuilder GenerateForm(ViewContext viewContext, string actionName, string controllerName, object routeValues, string method, object htmlAttributes); public virtual TagBuilder GenerateLabel(ViewContext viewContext, ModelExplorer modelExplorer, string expression, string labelText, object htmlAttributes); public virtual TagBuilder GenerateTextArea(ViewContext viewContext, ModelExplorer modelExplorer, string expression, int rows, int columns, object htmlAttributes); public virtual TagBuilder GenerateTextBox(ViewContext viewContext, ModelExplorer modelExplorer, string expression, object value, string format, object htmlAttributes); 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); protected virtual TagBuilder GenerateLink(string linkText, string url, object htmlAttributes); ....省略部分 } |
这个类里面,我们看到了熟悉的TagBuilder,就算不去看它里面的实现都能知道它是用来干嘛的 它就是用来创建我们的Html标签,相信用过MVC的,多多少少都扩展过HtmlHelper,这是类似的。 最后,也是最最重要的重写的Process方法。 可以看到开始就判断了表单<form>中是否包含了action这个属性output.Attributes.ContainsName(HtmlActionAttributeName) 如果包含,就是正常的html标签。换句话说,正常的html写法和我们的TagHelper方法会有冲突,只能用其中一种。 当我们这样写的时候,编译能通过。 但是,运行的时候就会出错。 再下面的处理就是用了TagBuilder去处理了。 收集路由的数据放到一个字典中->区域是否存在->用Generator去创建form表单,返回TagBuilder对象->TagHelperOutput对象把tagbuilder的innerhtml等信息输出。 如下面的写法:
1 2 3 |
<form method="post" asp-action="Get" asp-controller="Product" asp-antiforgery="false" asp-route-id="2"> <button type="submit">submit</button> </form> |
生成对应的html如下:
1 2 3 |
<form method="post" action="/Product/Get/2"> <button type="submit">submit</button> </form> |
到这里,FormTagHelper的讲解就算是OK,至于其他的,原理都是差不多,就不再累赘了。 来看看,到底有多少种TagHelper(还没有部分没有列出来),以及它们包含的属性。 下面是我们自己写一个TagHelper——CatcherATagHelper,这个TagHelper是干什么的呢?它只是一个精简版的A标签。
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 83 84 85 |
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Catcher.EasyDemo.Controllers.TagHelpers { [HtmlTargetElement("catcher-a")] public class CatcherATagHelper:TagHelper { public CatcherATagHelper(IHtmlGenerator generator, IUrlHelperFactory urlHelperFactory) { this.Generator = generator; UrlHelperFactory = urlHelperFactory; } [HtmlAttributeNotBound] public IUrlHelperFactory UrlHelperFactory { get; } protected IHtmlGenerator Generator { get; } public override int Order { get { return -1000; } } public string Action { get; set; } public string Controller { get; set; } public string LinkText { get; set; } [ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { //method 1 if (Action != null || Controller != null) { output.Attributes.Clear(); var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); output.TagName = "a"; output.Attributes.SetAttribute("href", urlHelper.Action(Action, Controller)); //whether the inner html is null if (output.Content.IsEmptyOrWhiteSpace) { output.PreContent.SetContent(LinkText); } } //method 2 //TagBuilder tagBuilder; //if (Action != null || Controller != null) //{ // tagBuilder = Generator.GenerateActionLink( // ViewContext, // linkText: string.Empty, // actionName: Action, // controllerName: Controller, // protocol: string.Empty, // hostname: string.Empty, // fragment: string.Empty, // routeValues: null, // htmlAttributes: null); // output.TagName = "a"; // //whether the inner html is null // if (output.Content.IsEmptyOrWhiteSpace) // { // output.PreContent.SetContent(LinkText); // } // output.MergeAttributes(tagBuilder); //} } } } |
这里提供了两种写法供大家参考 一种是借助IUrlHelperFactory去生成链接 一种是借助IHtmlGenerator去生成链接 写好了之后要怎么用呢? 不知道大家有没有留意_ViewImports.cshtml这个文件
1 2 3 |
@using Catcher.EasyDemo.Website @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @inject Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration TelemetryConfiguration |
这个是默认情况下帮我们添加的TagHelper 我们可以在要用到那个TagHelper的地方添加就好 Index.cshtml addTagHelper的用法如下: @addTagHelper 你的TagHelper , 你的TagHelper所在的命名空间 或者更直接 @addTagHelper * , 你的TagHelper所在的命名空间 可以添加,当然也可以删除,删除是@removeTagHelper 当我们在自己的框架中完全重写了一套自己的TagHelper,那么这个时候,微软自己的TagHelper我们就可以通过下面的方法来移除了。 @removeTagHelper […]
View Details