惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
小众软件
小众软件
C
Check Point Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Visual Studio Blog
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
T
Tailwind CSS Blog
Recorded Future
Recorded Future
雷峰网
雷峰网
WordPress大学
WordPress大学
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
T
Tor Project blog
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
G
Google Developers Blog
Security Latest
Security Latest
MongoDB | Blog
MongoDB | Blog
T
Threatpost
I
InfoQ
T
Tenable Blog
T
The Exploit Database - CXSecurity.com
S
Security Affairs
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
C
Cisco Blogs
博客园 - 三生石上(FineUI控件)
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
C
CXSECURITY Database RSS Feed - CXSecurity.com
美团技术团队
Google DeepMind News
Google DeepMind News
The Hacker News
The Hacker News
D
Docker
博客园 - 【当耐特】
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
PCI Perspectives
PCI Perspectives
Microsoft Azure Blog
Microsoft Azure Blog
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
Cloudbric
Cloudbric
A
About on SuperTechFans
F
Fortinet All Blogs

博客园 - 神八

RabbitMQ上手记录–part 6-Shovel RabbitMQ上手记录–part 5-节点集群高可用(多服务器) RabbitMQ上手记录–part 4-节点集群(单机多节点) RabbitMQ上手记录–part 3-发送消息 RabbitMQ上手记录–part 2 - 安装RabbitMQ RabbitMQ 上手记录-part 1-基础概念 在Docker平台实现MySQL Replication(复制) 把AspDotNetCoreMvc程序运行在Docker上-part5:使用docker-compose 把AspDotNetCoreMvc程序运行在Docker上-part4:实现负载均衡 把AspDotNetCoreMvc程序运行在Docker上-part3:使用独立的存储容器 把AspDotNetCoreMvc程序运行在Docker上-part2:修改容器以及发布镜像 把AspDotNetCoreMvc程序运行在Docker上-part1 基于ASP.Net Core学习Docker技术第一步:在CentOS7安装Docker平台 在ubuntu下使用visual studio code编写python Angular2 不明真相第一个Demo例子 ASP.NET MVC Core的TagHelper(基础篇) ASP.NET MVC Core的ViewComponent ASP.NET MVC Core Starter Kit 创建.NET Core程序的Nuget Package
ASP.NET MVC Core的TagHelper (高级特性)
神八 · 2017-04-11 · via 博客园 - 神八

这篇博文ASP.NET MVC Core的TagHelper(基础篇)介绍了TagHelper的基本概念和创建自定义TagHelper的方式,接着继续介绍一些新的看起来比较高级的特性。(示例代码紧接着上一遍博文)

一、使用自定义的标记元素

之前基础篇介绍的TagHelper的功能是给已有的HTML元素提供一个自定义的属性标记,然后服务器认出这个标记后,将标记转化成最终的HTML。这里将要介绍的功能是,定义个全新的Tag,看起来跟普通的HTML元素一样。是不是觉得很熟悉呢(前提是你用过AngularJS),完全类似于AngularJS的强大的元素定义功能。

比如我们这里创建一个新的标记元素,formbutton,使用方式如下

<formbutton type="submit" bg-color="danger" />

当然这个标记完全不是HTML内部定义的,浏览器也不能认出这是个啥玩意。

这个Tag跟自定义的属性标记一样,都会被MVC Core框架识别出来,然后转化成最终的HTML。

接下来我们创建这个TagHelper

在TagHelpers文件夹新建一个类

[HtmlTargetElement("formbutton")]
    public class FormButtonTagHelper : TagHelper
    {
        public string Type { get; set; } = "Submit";

        public string BgColor { get; set; } = "primary";

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "button";
            output.TagMode = TagMode.StartTagAndEndTag;
            output.Attributes.SetAttribute("class", $"btn btn-{BgColor}");
            output.Attributes.SetAttribute("type", Type);
            output.Content.SetContent(Type == "submit" ? "Add" : "Reset");
        }
    }

这个class定义的两个属性Type和BgColor,如大部分的猜想,这两个属性会匹配成html中定义的属性,然后把值自动赋给TagHelper Instance中的属性。

Process一连串的output调用也比较直接,大概意思是要生成一个button元素,并且根据用户提供的Type和BgColor生成class和type两个html属性的值。

其中SetContent是要设置需要输出的内容,由于TagMode是StartTagAndEndTag,所以内容会显示在标记之间。

接下来在home/create这个页面使用我们的自定义标记

@model City
@{ Layout = "_Layout"; }
<form method="post" action="/Home/Create">
    <div class="form-group">
        <label for="Name">Name:</label>
        <input class="form-control" name="Name" />
    </div>
    <div class="form-group">
        <label for="Country">Country:</label>
        <input class="form-control" name="Country" />
    </div>
    <div class="form-group">
        <label for="Population">Population:</label>
        <input class="form-control" name="Population" />
    </div>
    <formbutton type="submit" bg-color="danger" />
    <formbutton type="reset" />
    <a bs-button-color="primary" href="/Home/Index">Cancel</a>
</form>

我们使用formbuttion分别创建了一个submit和reset按钮,并且给submit按钮设置了danger样式

那么这两个按钮输出后的html分别是

<button class="btn btn-danger" type="submit">Add</button>

<button class="btn btn-primary" type="reset">Reset</button>

这是创建一个基本的自定义TagHelper的使用方式。

二、在目标元素之前或者之后插入内容

上一个栗子,比较中规中矩,实际上我们经常需要给元素前后插入一些内容,通常是一些外围包含元素。比如有如下元素

<div title="Cities"></div>

我们希望这个标记在输出成html的时候能在前后都自动加上一个div class=panel-body的色块,那么我么可以利用TagHelperOutput提供的方法实现。

可以创建如下的自定义TagHelper来说明

在TagHelpers文件夹新建类ContentWrapperTagHelper

[HtmlTargetElement("div", Attributes = "title")]
    public class ContentWrapperTagHelper : TagHelper
    {
        public bool IncludeHeader { get; set; } = true;
        public bool IncludeFooter { get; set; } = true;

        public string Title { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.Attributes.SetAttribute("class", "panel-body");

            var title = new TagBuilder("h1");
            title.InnerHtml.Append(Title);

            var container = new TagBuilder("div");
            container.Attributes["class"] = "bg-info panel-body";
            container.InnerHtml.AppendHtml(title);


            if (IncludeHeader)
            {
                output.PreElement.SetHtmlContent(container);
            }

            if (IncludeFooter)
            {
                output.PostElement.SetHtmlContent(container);
            }
        }
    }

1.这里指定了TagHelper的应用范围是包含了title属性的div元素

2.分别提供了IncludeHeader和IncludeFooter的属性,默认都是true

3.然后分别使用PreElement和PostElement设置前后内容

我们把这个标签应用在_Layout文件中

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Cities</title>
    <link href="/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</head>
<body class="panel-body">
   <div title="Cities">@RenderBody()</div>
</body>
</html>

运行就能看到头部和底部分别都输出了一个色块,并且包含了标题内容

三、在已有标记内容中插入内容

上一个栗子讲的是插入元素,这里演示一下插入内容到标签中,比如已有标签里面已经有内容了,可以在内容之前或者之后插入内容。

在TagHelpers目录新建一个TableCellTagHelper类

[HtmlTargetElement("td", Attributes = "wrap")]
    public class TableCellTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.PreContent.SetHtmlContent("<b><i>");
            output.PostContent.SetHtmlContent("</i></b>");
        }
    }

通过使用TagHelperOutput的PreContent和PostContent,分别在已有内容的前后插入了一段html标记包裹,这个TagHelper只会用于带有wrap属性的td标记。

把这个标记用在Home/Index.cshtml页面,把city的名称的td加入wrap属性即可

@model IEnumerable<City>
@{ Layout = "_Layout"; }
<table class="table table-condensed table-bordered">
    <thead class="bg-primary">
        <tr>
            <th>Name</th>
            <th>Country</th>
            <th class="text-right">Population</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var city in Model)
        {
            <tr>
                <td wrap>@city.Name</td>
                <td>@city.Country</td>
                <td class="text-right">@city.Population?.ToString("#,###")</td>
            </tr>
        }
    </tbody>
</table>
<a href="/Home/Create" class="btn btn-primary">Create</a>

运行后可以看到已有的内容都被<i><b></b></i>包裹起来,呈现的是加粗和斜体的效果。

四、使用ViewModel提供的属性值

在VIew里面输出ViewModel的值,经常会用到一些强类型的帮助方法,比如asp-for="Name"等,那么实际上就会读取ViewModel的Name的属性值。

自定义的TagHelper也支持这种方式,我们来看一下如何调用,还是继续在TagHelpers目录新建一个类,如下

LabelAndInputTagHelper

[HtmlTargetElement("label", Attributes = "helper-for")]
    [HtmlTargetElement("input", Attributes = "helper-for")]
    public class LabelAndInputTagHelper : TagHelper
    {
        public ModelExpression HelperFor { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (output.TagName == "label")
            {
                output.TagMode = TagMode.StartTagAndEndTag;
                output.Content.Append(HelperFor.Name);
                output.Attributes.SetAttribute("for", HelperFor.Name);
            } else if (output.TagName == "input")
            {
                output.TagMode = TagMode.SelfClosing;
                output.Attributes.SetAttribute("name", HelperFor.Name);
                output.Attributes.SetAttribute("class", "form-control");
                if (HelperFor.Metadata.ModelType == typeof(int?))
                {
                    output.Attributes.SetAttribute("type", "number");
                }
            }
        }
    }

这个TagHelper的作用用,将for属性应用到label和input元素上,实现常见的点击label后聚焦到input的功能。

这里一个关键属性是HelperFor,用来读取ViewModel提供的属性的信息,它的类型是ModelExpression,看起来比较高级,用它可以很方便得到ViewModel的信息。

我们把这个TagHelper应用到Home/Create.cshtml页面中

比如之前我们是这样写的

<label for="Name">Name:</label>

<input class="form-control" name="Name" />

现在用了标记之后就可以简化成如下

<label helper-for="Name"/>

<input helper-for="Name"/>

看起来更加的整洁,和符合强迫症程序员的口味。

五. TagHelper之前相互通讯协同

两个不同的TagHelper之前实际上可以通过共享数据的方式实现协同,当然共享数据的方式很多啊,比如粗暴一点的用数据,什么Session之类的(经常面试被问到的Asp.net页面传递有哪些方法啊,通常是老家伙装13的样子在问)

当然我们不会用数据或者Session去保存共享的数据,TagHelperContext为我们提供了一个便利的实现方式,类似于HttpContent.Items,直接看看例子。

在TagHelpers文件夹新建一个CoordinatingTagHelpers文件

[HtmlTargetElement("div", Attributes = "theme")]
    public class ButtonGroupThemeTagHelper : TagHelper
    {
        public string Theme { get; set; }

        public override void Process(TagHelperContext context,
            TagHelperOutput output)
        {
            context.Items["theme"] = Theme;
        }
    }

    [HtmlTargetElement("button", ParentTag = "div")]
    [HtmlTargetElement("a", ParentTag = "div")]
    public class ButtonThemeTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context,
            TagHelperOutput output)
        {
            if (context.Items.ContainsKey("theme"))
                output.Attributes.SetAttribute("class",
                    $"btn btn-{context.Items["theme"]}");
        }
    }

这个文件包含两个TagHelper,第一个是定义了div标签,它在context.Items里设置了theme的值,然后在另外一个TagHelper中读取items的值。

用法简单到没有朋友

<div theme="primary">
        <button type="submit">Add</button>
        <button type="reset">Reset</button>
        <a href="/Home/Index">Cancel</a>
</div>

里面的button的样式会根据外层theme的值来设置对应的样式,比如设置theme="Danger",里面的按钮显示为如下样式

image

六. 禁止内容输出

最后要介绍的是禁止内容输出。禁止内容输出很多方法,最简单的不显示或者加个if else判断。

这里使用TagHelperOutput提供的SuppressOutput方法。

新建如下TagHelper

[HtmlTargetElement(Attributes = "show-for-action")]
    public class SelectiveTagHelper : TagHelper
    {
        public string ShowForAction { get; set; }
        [ViewContext]
        [HtmlAttributeNotBound]
        public ViewContext ViewContext { get; set; }
        public override void Process(TagHelperContext context,
        TagHelperOutput output)
        {
            if (!ViewContext.RouteData.Values["action"].ToString()
            .Equals(ShowForAction, StringComparison.OrdinalIgnoreCase))
            {
                output.SuppressOutput();
            }
        }
    }

这个TagHelper定义了其标签内容只有在当前Action跟目标Action一致的时候在显示内容,否则调用Suppress禁止内容输出

比如如下html标记

<div show-for-action="Index" class="panel-body bg-danger">
<h2>Important Message</h2>
</div>

指定了只有在Index action下才显示important Message

示例代码路径

https://github.com/shenba2014/AspDotNetCoreMvcExamples/tree/master/CustomTagHelper