Tag Helper Components trong ASP.NET Core
Bởi Scott Addie và Fiyaz Bin Hasan
Tag Helper Component (thành phần Tag Helper) là một Tag Helper cho phép bạn có điều kiện sửa đổi hoặc thêm các phần tử HTML từ server-side code. Tính năng này có sẵn trong ASP.NET Core 2.0 trở lên.
ASP.NET Core bao gồm hai Tag Helper Components tích hợp sẵn: head và body. Chúng nằm trong namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers và có thể được sử dụng trong cả MVC lẫn Razor Pages. Tag Helper Components không cần đăng ký với ứng dụng trong _ViewImports.cshtml.
Các trường hợp sử dụng
Hai trường hợp sử dụng phổ biến của Tag Helper Components bao gồm:
- Inject (chèn) một
<link>vào<head>. - Inject một
<script>vào<body>.
Các phần sau mô tả những trường hợp sử dụng này.
Inject vào phần tử HTML head
Bên trong phần tử HTML <head>, các file CSS thường được import bằng phần tử HTML <link>. Code sau inject một phần tử <link> vào phần tử <head> bằng cách dùng Tag Helper Component head:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorPagesSample.TagHelpers
{
public class AddressStyleTagHelperComponent : TagHelperComponent
{
private readonly string _style =
@"<link rel=""stylesheet"" href=""/css/address.css"" />";
public override int Order => 1;
public override Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "head",
StringComparison.OrdinalIgnoreCase))
{
output.PostContent.AppendHtml(_style);
}
return Task.CompletedTask;
}
}
}Trong code trên:
AddressStyleTagHelperComponentimplement TagHelperComponent. Abstraction này:- Cho phép khởi tạo class với TagHelperContext.
- Cho phép sử dụng Tag Helper Components để thêm hoặc sửa đổi các phần tử HTML.
- Thuộc tính Order định nghĩa thứ tự mà các Components được render.
Ordercần thiết khi có nhiều cách sử dụng Tag Helper Components trong ứng dụng. - ProcessAsync so sánh giá trị thuộc tính TagName của execution context với
head. Nếu so sánh đúng, nội dung của field_styleđược inject vào phần tử HTML<head>.
Inject vào phần tử HTML body
Tag Helper Component body có thể inject một phần tử <script> vào phần tử <body>. Code sau minh họa kỹ thuật này:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorPagesSample.TagHelpers
{
public class AddressScriptTagHelperComponent : TagHelperComponent
{
public override int Order => 2;
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "body",
StringComparison.OrdinalIgnoreCase))
{
var script = await File.ReadAllTextAsync(
"TagHelpers/Templates/AddressToolTipScript.html");
output.PostContent.AppendHtml(script);
}
}
}
}Một file HTML riêng được dùng để lưu trữ phần tử <script>. File HTML làm code sạch hơn và dễ bảo trì hơn. Code trên đọc nội dung của TagHelpers/Templates/AddressToolTipScript.html và append nó với Tag Helper output. File AddressToolTipScript.html bao gồm markup sau:
<script>
$("address[printable]").hover(function() {
$(this).attr({
"data-toggle": "tooltip",
"data-placement": "right",
"title": "Home of Microsoft!"
});
});
</script>Code trên liên kết một Bootstrap tooltip widget với bất kỳ phần tử <address> nào bao gồm thuộc tính printable. Hiệu ứng hiển thị khi con trỏ chuột di chuyển qua phần tử.
Đăng ký Component
Tag Helper Component phải được thêm vào collection Tag Helper Components của ứng dụng. Có ba cách để thêm vào collection:
- Đăng ký qua services container
- Đăng ký qua Razor file
- Đăng ký qua Page Model hoặc controller
Đăng ký qua services container
Nếu class Tag Helper Component không được quản lý bởi ITagHelperComponentManager, nó phải được đăng ký với hệ thống dependency injection (DI). Code Startup.ConfigureServices sau đăng ký các class AddressStyleTagHelperComponent và AddressScriptTagHelperComponent với transient lifetime (vòng đời tạm thời):
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddTransient<ITagHelperComponent,
AddressScriptTagHelperComponent>();
services.AddTransient<ITagHelperComponent,
AddressStyleTagHelperComponent>();
}Đăng ký qua Razor file
Nếu Tag Helper Component không được đăng ký với DI, nó có thể được đăng ký từ Razor Pages page hoặc MVC view. Kỹ thuật này được sử dụng để kiểm soát markup được inject và thứ tự thực thi component từ Razor file.
ITagHelperComponentManager được dùng để thêm Tag Helper Components hoặc xóa chúng khỏi ứng dụng. Code sau minh họa kỹ thuật này với AddressTagHelperComponent:
@using RazorPagesSample.TagHelpers;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager manager;
@{
string markup;
if (Model.IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}
manager.Components.Add(new AddressTagHelperComponent(markup, 1));
}Trong code trên:
- Directive
@injectcung cấp một instance củaITagHelperComponentManager. Instance được gán cho biến có tênmanagerđể truy cập trong Razor file. - Một instance của
AddressTagHelperComponentđược thêm vào collection Tag Helper Components của ứng dụng.
AddressTagHelperComponent được sửa đổi để chấp nhận constructor nhận tham số markup và order:
private readonly string _markup;
public override int Order { get; }
public AddressTagHelperComponent(string markup = "", int order = 1)
{
_markup = markup;
Order = order;
}Tham số markup được cung cấp được sử dụng trong ProcessAsync như sau:
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "address",
StringComparison.OrdinalIgnoreCase) &&
output.Attributes.ContainsName("printable"))
{
TagHelperContent childContent = await output.GetChildContentAsync();
string content = childContent.GetContent();
output.Content.SetHtmlContent(
$"<div>{content}<br>{_markup}</div>{_printableButton}");
}
}Đăng ký qua Page Model hoặc controller
Nếu Tag Helper Component không được đăng ký với DI, nó có thể được đăng ký từ Razor Pages page model hoặc MVC controller. Kỹ thuật này hữu ích để tách biệt logic C# khỏi Razor files.
Constructor injection được sử dụng để truy cập một instance của ITagHelperComponentManager. Tag Helper Component được thêm vào collection Tag Helper Components của instance. Razor Pages page model sau minh họa kỹ thuật này với AddressTagHelperComponent:
using System;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesSample.TagHelpers;
public class IndexModel : PageModel
{
private readonly ITagHelperComponentManager _tagHelperComponentManager;
public bool IsWeekend
{
get
{
var dayOfWeek = DateTime.Now.DayOfWeek;
return dayOfWeek == DayOfWeek.Saturday ||
dayOfWeek == DayOfWeek.Sunday;
}
}
public IndexModel(ITagHelperComponentManager tagHelperComponentManager)
{
_tagHelperComponentManager = tagHelperComponentManager;
}
public void OnGet()
{
string markup;
if (IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}
_tagHelperComponentManager.Components.Add(
new AddressTagHelperComponent(markup, 1));
}
}Trong code trên:
- Constructor injection được dùng để truy cập instance của
ITagHelperComponentManager. - Instance của
AddressTagHelperComponentđược thêm vào collection Tag Helper Components của ứng dụng.
Tạo Component
Để tạo Tag Helper Component tùy chỉnh:
- Tạo một public class kế thừa từ TagHelperComponentTagHelper.
- Áp dụng thuộc tính
[HtmlTargetElement]cho class. Chỉ định tên của phần tử HTML mục tiêu. - Tùy chọn: Áp dụng thuộc tính
[EditorBrowsable(EditorBrowsableState.Never)]cho class để ẩn kiểu khỏi hiển thị trong IntelliSense.
Code sau tạo Tag Helper Component tùy chỉnh nhắm đến phần tử HTML <address>:
using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
namespace RazorPagesSample.TagHelpers
{
[HtmlTargetElement("address")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class AddressTagHelperComponentTagHelper : TagHelperComponentTagHelper
{
public AddressTagHelperComponentTagHelper(
ITagHelperComponentManager componentManager,
ILoggerFactory loggerFactory) : base(componentManager, loggerFactory)
{
}
}
}Sử dụng Tag Helper Component address tùy chỉnh để inject HTML markup như sau:
public class AddressTagHelperComponent : TagHelperComponent
{
private readonly string _printableButton =
"<button type='button' class='btn btn-info' onclick=\"window.open(" +
"'https://binged.it/2AXRRYw')\">" +
"<span class='glyphicon glyphicon-road' aria-hidden='true'></span>" +
"</button>";
public override int Order => 3;
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "address",
StringComparison.OrdinalIgnoreCase) &&
output.Attributes.ContainsName("printable"))
{
var content = await output.GetChildContentAsync();
output.Content.SetHtmlContent(
$"<div>{content.GetContent()}</div>{_printableButton}");
}
}
}Method ProcessAsync ở trên inject HTML được cung cấp cho SetHtmlContent vào phần tử <address> khớp. Việc inject xảy ra khi:
- Giá trị thuộc tính
TagNamecủa execution context bằngaddress. - Phần tử
<address>tương ứng có thuộc tínhprintable.
Ví dụ, câu lệnh if đánh giá là true khi xử lý phần tử <address> sau:
<address printable>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>