前言

以前写的 Asp.net core 学习笔记之 Tag Helper, 这篇是整理版.

参考

Docs – Author Tag Helpers in ASP.NET Core

Creating a Custom Tag Helper in ASP.NET Core: Gathering Data

介绍

TagHelper 有点像 Angular 的指令 directive, 绝大部分情况下都是用来装修 element 的. 比如 add class.

下面是 ASP.NET Core build-in 的 tag, 应该可以感觉出来它都用在什么地方了. 我用它来实现 router link active 的功能.

Create Tag Helper

[HtmlTargetElement(Attributes = "[routerLinkActive]")]
public class RouterLinkActiveTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
}
}

创建一个 RouterLinkActiveTagHelper class 继承 TagHelper

HtmlTargetElement 负责定义 selector, 和 Angular 一样它需要一个 selector 做匹配

Process 负责对 element 做任何修改, 比如 add class, innerHtml, appendHtml 等等.

还有一种 selector 是 for tag 的. 比较少用到, 也是和 Angular 一样的概念, directive 通常是匹配 attribute 但其实是可以匹配 element tag 的 (有些 Angular 玩家都不知道呢).

[HtmlTargetElement("my-email", TagStructure = TagStructure.NormalOrSelfClosing)]

要使用前需要添加 view import

第一个参数是具体的 namespace + class name, * 代表匹配全部

第二个参数是 Assembly 的名字

@addTagHelper TestWeb.TagHelpers.*, TestWeb

表示, TestWeb Assembly 里面, TestWeb.TagHelpers namespace 下的所有 class

然后就可以使用了

<body>
<a routerLinkActive href="/contact">Contact</a>
</body>

data-* 和 asp-* 是不合格的 selector

asp- 是 ASP.NET Core 保留和专用的 selector, 所以我们用不到.

data- 不能用是因为

所以切记, 不要用 asp- 和 data- 作为 selector

@Input()

@input 原至 Angular, 就是在使用指令的时候, 传入一些变量作为操控.

<a routerLinkActive company-name="Abc" company-age="15" href="/contact">Contact</a>

传入 company name 和 age

通过 property 接收

[HtmlAttributeName("company-name")]
public string CompanyName { get; set; } = ""; public int? CompanyAge { get; set; }

它默认是 kebab-case map to PascalCase, 如果想取别名就使用 HtmlAttributeName 声明就可以了.

有自动做类型转换. empty string != null 哦, null 指的是完全没有放 attribute.

Process

public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (output.Attributes.TryGetAttribute("class", out var attribute))
{
var value = attribute.Value; // readonly
} output.AddClass("active", HtmlEncoder.Default);
output.Attributes.Add("data-attribute", "value");
output.Content.SetHtmlContent("<h1>Email1</h1>"); // 注: self closing tag, 这句会无效, 因为不会有 content
output.Content.AppendHtml("<h1>Email2</h1>"); // 注: self closing tag, 这句会无效, 因为不会有 content output.TagName = "div"; // 换 tag
output.TagMode = TagMode.SelfClosing; // 换 closing mode
base.Process(context, output);
}

常见的操作有, add class, append html, inner html, get attribute 等等.

Read ViewContext

ViewContext 包含了常用到的 ViewBag, ViewData, RouteData, HttpContext 等等.

[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; } = null!;

通过 ViewContent Attribute 声明就可以了.

Parent Child 沟通

参考: The Very Basics of Nesting for Tag Helpers

这个会比较复杂一些.

[HtmlTargetElement("email", TagStructure = TagStructure.NormalOrSelfClosing)]
public class EmailTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
context.Items.Add(typeof(EmailContext), new EmailContext());
var childContent = await output.GetChildContentAsync();
var emailChildren = ((EmailContext)context.Items[typeof(EmailContext)]).EmailChildren;
var contentString = childContent.GetContent(); // get content string
}
} [HtmlTargetElement("email-child", TagStructure = TagStructure.NormalOrSelfClosing)]
public class EmailChildTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var emailContext = (EmailContext)context.Items[typeof(EmailContext)];
emailContext.EmailChildren.Add(this);
}
}

parent 通过 Context.Items 传入一个容器 (也可以传入子层需要的资料), child 把资料放入容器中.

parent await GetChildContentAsync, 等待子层完成后, 再打开容器, 把子层放进去的资料拿出来.

这样就可以 parent child 沟通了, 看上去容器是多余的, 子层为什么不能也使用 context.Items.Add 的方式回传给 parent 呢?

因为它是一个 copy... 我也不知道为什么它要这样设计.

RouterLinkActive

用法

<div routerLinkActive class="container">
<a routerLinkActive href="/services/aircon-general-servicing">Aicon General Servicing</a>
<a routerLinkActive href="/services/aircon-repair?page=1">Aicon Repair Page 1</a>
<a routerLinkActive href="/services/aircon-repair?page=2">Aicon Repair Page 2</a>
</div>

当前 URL 和当前 element 的 href 或者子孙 element 的 href 吻合的话, routerLinkActive 就要 add class "active".

[HtmlTargetElement(Attributes = "[routerLinkActive]")]
public class RouterLinkActiveTagHelper : TagHelper
{
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; } = null!; public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var selfAndDescendantLinks = new List<string>(); var selfLink = GetSelfLinkFromHref();
if (selfLink != null)
{
selfAndDescendantLinks.Add(selfLink);
} var descendantLinks = await GetDescendantLinksAsync();
selfAndDescendantLinks.AddRange(descendantLinks); // note 解忧:
// link 是 encode 的, 而 Request.Path 是 decode 的, 所以需要加 .ToString 让它 encode, 这样才能 match with link
var requestPathWithQuery = ViewContext.HttpContext.Request.Path.ToString() + ViewContext.HttpContext.Request.QueryString.ToString();
if (selfAndDescendantLinks.Contains(requestPathWithQuery))
{
output.AddClass("active", HtmlEncoder.Default);
} await base.ProcessAsync(context, output); string? GetSelfLinkFromHref()
{
output.Attributes.TryGetAttribute("href", out var hrefAttribute);
return hrefAttribute?.Value.ToString();
}
async Task<List<string>> GetDescendantLinksAsync()
{
var childHtml = output.Content.IsModified ? output.Content.GetContent() : (await output.GetChildContentAsync()).GetContent();
var regex = new Regex(@"<a .*href=""(\S*)""");
var matchedHrefs = regex.Matches(childHtml);
return matchedHrefs.ToArray().Select(matchedHref => matchedHref.Groups[1].Value).ToList();
}
}
}

找出 href 的 link, 然后和当前 URL 做匹配, 匹配到的话就添加 class active.

Disable Tag Helper

有一个 MyTagHelper, target element anchor attrubute href

只要在 element tag 或者 attribute 前加上感叹号 (!), 就可以了.

下面 3 个写法都可以.

<!a href="/contact" @(true ? "skip-test" : "")>Contact</a>
<!a href="/contact" @(true ? "skip-test" : "")>Contact</!a>
<a !href="/contact" @(true ? "skip-test" : "")>Contact</a>

The tag helper must not have C# in the element's attribute declaration area

参考:

stackoverflow – The tag helper 'input' must not have C# in the element's attribute declaration area

stackoverflow – Razor is not writing my "selected" value into the page

这个 tag helper 的一个 limitation, 不允许 dynamic 指令

它的情况是这样的, 比如有一个 MyTagHelper, target element anchor attrubute href

我想动态添加 attribute 像下面这样, 是不可以的.

动态添加 value 就可以

而且我发现如果 tag helper 是在另一个 dll 里面, 它不会提示错误, 反而会有意想不到的 bug. 注意咯

ASP.NET Core – TagHelper的更多相关文章

  1. ASP.NET Core MVC TagHelper实践HighchartsNET快速图表控件-开源

    ASP.NET Core MVC TagHelper最佳实践HighchartsNET快速图表控件支持ASP.NET Core. 曾经在WebForms上写过 HighchartsNET快速图表控件- ...

  2. 【无私分享:ASP.NET CORE 项目实战(第九章)】创建区域Areas,添加TagHelper

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 在Asp.net Core VS2015中,我们发现还有很多不太简便的地方,比如右击添加视图,转到试图页等功能图不见了,虽然我 ...

  3. asp.net core的TagHelper简单使用

    TagHelper(标签助手)是ASP.NET Core非常好的一种新特性.可以扩展视图,让其看起来像一个原生HTML标签. 应该使用TagHelper替换HtmlHelper,因其更简洁更易用,且支 ...

  4. [转]【无私分享:ASP.NET CORE 项目实战(第九章)】创建区域Areas,添加TagHelper

    本文转自:http://www.cnblogs.com/zhangxiaolei521/p/5808417.html 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 在Asp ...

  5. asp.net core高级应用:TagHelper+Form

    上一篇博客我讲解了TagHelper的基本用法和自定义标签的生成,那么我就趁热打铁,和大家分享一下TagHelper的高级用法~~,大家也可以在我的博客下随意留言. 对于初步接触asp.net cor ...

  6. asp.net core新特性(1):TagHelper

    进步,才是人应该有的现象.-- 雨果 今天开始,我就来说说asp.net core的新特性,今天就说说TagHelper标签助手.虽然学习.net,最有帮助的就是microsoft的官方说明文档了,里 ...

  7. Asp.Net Core 入门(八)—— Taghelper

    Taghelper是一个服务端的组件,可以在Razor文件中创建和渲染HTML元素,类似于我们在Asp.Net MVC中使用的Html Taghelper.Asp.Net Core MVC内置的Tag ...

  8. ASP.NET Core 3.0 : 二十五. TagHelper

    什么是TagHelper?这是ASP.NET Core 中新出现的一个名词,它的作用是使服务器端代码可以在Razor 文件中参与创建和呈现HTML 元素.(ASP.NET Core 系列目录) 一.概 ...

  9. ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由

    原文:Routing to Controller Actions 作者:Ryan Nowak.Rick Anderson 翻译:娄宇(Lyrics) 校对:何镇汐.姚阿勇(Dr.Yao) ASP.NE ...

  10. ASP.NET Core 中文文档 第四章 MVC(4.6)Areas(区域)

    原文:Areas 作者:Dhananjay Kumar 和 Rick Anderson 翻译:耿晓亮(Blue) 校对:许登洋(Seay) Areas 是 ASP.NET MVC 用来将相关功能组织成 ...

随机推荐

  1. 火山引擎ByteHouse发布高性能全文检索引擎

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群.  随着数字时代的发展,数据的来源和生成方式越来越广泛,数据形态也愈加丰富.   以某电商平台的数据情况举例.该电 ...

  2. 使用JavaScript编写vue指令v-model,v-model原理实现

    首先先要知道的是v-model的作用是实现数据的双向绑定,即: 数据在视图层的双向响应. 实现思路主要分为两步: 第一步:数据层到视图层的响应 将数据响应到视图层的方式,在vue2使用的是Object ...

  3. ElementUI Dialog 结合Vue实现对话框body“二分”布局

    Dialog 结合Vue实现对话框body"二分"布局 需求描述 如下图, 把对话框body内容部分,分成上下两部分,其中上部分高度根据窗口大小动态调整,如果内容过多,则出现滚动条 ...

  4. python统计班级学生

    python统计班级学生 如下场景: 假设我有一个学生类和一个班级类,想要实现的功能为:    执行班级人数增加的操作.获得班级的总人数:    学生类继承自班级类,每实例化一个学生,班级人数都能增加 ...

  5. 【Java】Annotation 注解

    Annotation 注解 注解是一种元数据 MetaData,从JDK5开始 在Java代码中是一个特殊的标记,可以在编译,类加载,运行时读取,执行对应的处理 程序可以在不改变原有逻辑的基础上嵌入一 ...

  6. 【转载】 NCCL(Nvidia Collective multi-GPU Communication Library) Nvidia英伟达的Multi-GPU多卡通信框架NCCL 学习;PCIe 速率调研

    原文地址: https://www.cnblogs.com/xuyaowen/p/nccl-learning.html ---------------------------------------- ...

  7. pyglet.gl.ContextException: Could not create GL context

    参考: https://www.saoniuhuo.com/question/detail-2725960.html ========================================= ...

  8. 设计和实现AI算法算法时有没有必要在代码中加注释,没有用必要在实现之前弄个UML图???

    问题如题: 设计和实现AI算法算法时有没有必要在代码中加注释,没有用必要在实现之前弄个UML图??? 今天看到一个博文: https://www.cnblogs.com/siyuanwai/p/154 ...

  9. Vue Vine:带给你全新的 Vue 书写体验!

    你好,我是 Kagol,个人公众号:前端开源星球. 上个月和 TinyVue 的小伙伴们一起参加了 VueConf 24 大会,有幸认识沈青川大佬,并了解了他的 Vue Vine 项目,Vue Vin ...

  10. 这应该是全网最全的CSP-S初赛复习吧

    点我到洛谷看 \(Update\ 2024/8/2:\) 加入了在数据结构中增加了"树",做出部分更改. linux基础命令 cd 切换目录 ls 列出目前工作目录所含的文件及子目 ...