• 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";    
    复制代码

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

    我们可以看到下面的好几个属性,如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的地方添加就好

     Index.cshtml
    addTagHelper的用法如下:

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

    或者更直接

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

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

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

    @removeTagHelper * , Microsoft.AspNetCore.Mvc.TagHelpers

    如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
  • 相关阅读:
    Java 得到指定时间加半个小时之后得时间
    MySQL查询point类型类型的坐标,返回经度纬度
    MySQL通过实体经纬度字段插入数据库point类型的经纬度字段
    MySQL通过POIN数据类型查询指定范围内数据
    Java 根据两个经纬度,得到两点距离
    mysql通过经纬度查询400公里范围内的小区
    位运算
    Hibernate多对多删除问题的解决
    mysql 中 时间和日期函数
    Struts2数据传输的背后机制:ValueStack(值栈)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5797874.html
Copyright © 2020-2023  润新知