ASP.NET Core – TagHelper
前言
以前写的 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的更多相关文章
- ASP.NET Core MVC TagHelper实践HighchartsNET快速图表控件-开源
ASP.NET Core MVC TagHelper最佳实践HighchartsNET快速图表控件支持ASP.NET Core. 曾经在WebForms上写过 HighchartsNET快速图表控件- ...
- 【无私分享:ASP.NET CORE 项目实战(第九章)】创建区域Areas,添加TagHelper
目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 在Asp.net Core VS2015中,我们发现还有很多不太简便的地方,比如右击添加视图,转到试图页等功能图不见了,虽然我 ...
- asp.net core的TagHelper简单使用
TagHelper(标签助手)是ASP.NET Core非常好的一种新特性.可以扩展视图,让其看起来像一个原生HTML标签. 应该使用TagHelper替换HtmlHelper,因其更简洁更易用,且支 ...
- [转]【无私分享:ASP.NET CORE 项目实战(第九章)】创建区域Areas,添加TagHelper
本文转自:http://www.cnblogs.com/zhangxiaolei521/p/5808417.html 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 在Asp ...
- asp.net core高级应用:TagHelper+Form
上一篇博客我讲解了TagHelper的基本用法和自定义标签的生成,那么我就趁热打铁,和大家分享一下TagHelper的高级用法~~,大家也可以在我的博客下随意留言. 对于初步接触asp.net cor ...
- asp.net core新特性(1):TagHelper
进步,才是人应该有的现象.-- 雨果 今天开始,我就来说说asp.net core的新特性,今天就说说TagHelper标签助手.虽然学习.net,最有帮助的就是microsoft的官方说明文档了,里 ...
- Asp.Net Core 入门(八)—— Taghelper
Taghelper是一个服务端的组件,可以在Razor文件中创建和渲染HTML元素,类似于我们在Asp.Net MVC中使用的Html Taghelper.Asp.Net Core MVC内置的Tag ...
- ASP.NET Core 3.0 : 二十五. TagHelper
什么是TagHelper?这是ASP.NET Core 中新出现的一个名词,它的作用是使服务器端代码可以在Razor 文件中参与创建和呈现HTML 元素.(ASP.NET Core 系列目录) 一.概 ...
- ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由
原文:Routing to Controller Actions 作者:Ryan Nowak.Rick Anderson 翻译:娄宇(Lyrics) 校对:何镇汐.姚阿勇(Dr.Yao) ASP.NE ...
- ASP.NET Core 中文文档 第四章 MVC(4.6)Areas(区域)
原文:Areas 作者:Dhananjay Kumar 和 Rick Anderson 翻译:耿晓亮(Blue) 校对:许登洋(Seay) Areas 是 ASP.NET MVC 用来将相关功能组织成 ...
随机推荐
- 解决方案 | Windows 验证账号出现 0x80190001错误解决
一.问题描述 点击windows开始→账户→更改账户设置→验证,出现下面的错误. 二.解决方法 网上流行的是这个方法,https://blog.csdn.net/qq_36393978/article ...
- FFmpeg开发笔记(四十二)使用ZLMediaKit开启SRT视频直播服务
<FFmpeg开发实战:从零基础到短视频上线>一书在第10章介绍了轻量级流媒体服务器MediaMTX,通过该工具可以测试RTSP/RTMP等流媒体协议的推拉流.不过MediaMTX的功能 ...
- Jmeter函数助手8-counter
counter函数用于线程计数,类似计数器. TRUE每个用户有自己的计数器:FALSE使用全局计数器:即线程之间是否需要共享累加计数器,TRUE否,FALSE是 存储结果的变量名(可选) 1.线程之 ...
- 对比python学julia(第三章:游戏编程)--(第一节)初识游戏库(3)
1.1. 键盘和鼠标控制 在游戏应用程序中,通常使用键盘和鼠标作为游戏的操作设备.游戏的窗口都能接收来自键盘和鼠标设备的输人.当用户在键盘上按下按建或释放按键时,会产生相应的键盘事件:当用户移动 ...
- 3、SpringBoot2之配置文件
3.1.环境搭建 3.1.1.在project创建新module 3.1.2.选择maven 3.1.3.设置module名称和路径 3.1.4.module初始状态 3.1.5.引入springbo ...
- 【微信小程序】 全局数据共享
1.什么是全局数据共享 全局数据共享(又叫做:状态管理)是为了解决组件之间数据共享的问题. 开发中常用的全局数据共享方案有: Vuex.Redux. MobX等. 2.小程序中的全局数据共享方案 在小 ...
- WPF【无限滚动图片浏览】自定义控件
自定义控件 自定义控件是我比较陌生的一个主题.我好久没练习过wpf了,需要巩固记忆.我想了一会儿,打开动漫之家,忽然觉得这个看漫画的图片浏览控件有意思.于是特地花了一天做了这个图片控件.我原本以为很容 ...
- 如果美国断供中国所有的Intel和AMD芯片,国内各行各业会不会崩溃
说一个我个人观点,我认为如果国内完全没有X86芯片的供应,那么各行各业的发展会明显进入发展迟缓阶段,首先受影响的就是软件开发领域,因为没有新的芯片也就意味着袋电脑性能停滞或者倒退,那么开发出新的更耗资 ...
- 《Python数据可视化之matplotlib实践》 源码 第四篇 扩展 第十二章
图 12.1 import matplotlib.pyplot as plt import numpy as np barSlices=12 theta=np.linspace(0.0, 2*np. ...
- (待续)【转载】 Deep Reinforcement Learning Doesn't Work Yet(这里有一篇深度强化学习劝退文)
原文: https://www.alexirpan.com/2018/02/14/rl-hard.html ============================================== ...