前言

以前写的 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. Unity无法安装Entities 1.2.0 Package的解决方法

    会出现如下的错误提示: 本质原因是国内版的Unity使用了自己的Package加速CDN:packages.unity.cn,而不是官方的packages.unity.com.而这个CDN更新了Ent ...

  2. 自动化车间3D可视化设计思路

    自动化车间3D可视化设计思路 随着国内制造业企业的高速发展,再加上政策支持,高效的生产模式和先进的管理方式越来越受到企业重视.更多的企业将工业信息化技术进行广泛的应用,比如MES系统.数字孪生以及生产 ...

  3. 可视化—AntV G6实现节点连线及展开收缩分组

    AntV 是蚂蚁金服全新一代数据可视化解决方案,主要包含数据驱动的高交互可视化图形语法G2,专注解决流程与关系分析的图表库 G6.适于对性能.体积.扩展性要求严苛的场景. demo使用数字模拟真实的节 ...

  4. [oeasy]python0092_homebrew_家酿俱乐部_比尔盖茨_保罗艾伦

    编码进化 个人电脑 intel 8080 的出现 让 人人都 可能有 一台计算机 Ed Robert 的 创业之路 从 售卖 diy 组装配件 到进军 计算器市场 计算器 毕竟不是 个人计算机 这计算 ...

  5. 第二章 编译FFmpeg并开启H.264编码

    目录 前言 1. 下载x264 2. 编译x264 3. 编译FFmpeg 3.1 可能出现的问题和解决方法 3.1.1 ERROR: x264 not found using pkg-config ...

  6. AT_agc022_a 题解

    洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定字符串 \(S\) , 仅包含互不相同的小写字母, 你需要找到仅包含互不相同的小写 ...

  7. Android低功耗子系统的投票机制以及触发进入系统休眠的过程

    从kernel角度看,系统是否进入休眠应该由内核来控制,因此Linux引入了 wakeup source以及autosleep机制 关于wakeup source的介绍,请参考: Wakeup Sou ...

  8. 【Java】Vue-Element-Admin 嵌入Druid监控面板

    我看到若依做了Druid面板的嵌入,我自己的项目干脆也做一个 一.后台服务SpringBoot: Druid配置项: spring: datasource: url: jdbc:mysql://127 ...

  9. 【WSDL】02 四种客户端调用方式

    WSDL概念和一些语法内容: https://www.w3school.com.cn/wsdl/index.asp SOAP概念: https://www.runoob.com/soap/soap-t ...

  10. 【Java】Input,Output,Stream I/O流 04 对象流&序列化

    对象流,序列化机制 ObjectInputStream ObjectOutputStream 序列化 对象 写入转 数据 持久化 反序列化 数据 读取转 对象 活化 - 任何实现了Serializab ...