Note: see the link below for the English version of this article.
https://duongnt.com/tag-helpers
Khi viết code cho phần front-end, đôi khi ta cần lặp lại một nhóm các tag HTML cho nhiều trang khác nhau. Khi đó ta nên đóng gói chúng lại thành một object để giảm thiểu việc lặp lại code. Lúc này, tính năng Tag Helpers của ASP.NET Core là rất hữu ích.
Các bạn có thể tải code ví dụ trong bài từ đường link dưới đây.
https://github.com/duongntbk/TagHelpersDemo
Tổng quát về Tag Helpers
Nói một cách khái quát, Tag Helper được sử dụng để tạo tag HTML mới, hoặc để tạo attribute cho tag HTML sẵn có. Khi gặp custom tag này, compiler sẽ tự chuyển nó thành tập hợp các HTML tag theo như logic của ta.
Cách sử dụng Tag Helpers tương đối đơn giản, ta kế thừa từ lớp TagHelper
và định nghĩa lại hàm Process
(xử lý đồng bộ) hoặc ProcessAsync
(xử lý không đồng bộ).
Phiên bản Tag Helpers đơn giản nhất
Chúng ta phân tích một lớp Tag Helpers đơn giản ở đường dẫn này. Hãy xem qua từng dòng code.
[HtmlTargetElement("hello-world")]
Ta dùng attribute HtmlTargetElement
để đặt tên cho tag của mình. Bằng cách này, ta có thể thêm tag vào trang web bằng code <hello-world></hello-world>
.
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
Ở đây, output
chính là code HTML mà tag của ta sẽ sinh ra. Trong trường hợp này ta dùng div
, nhưng ta cũng có thể dùng các tag khác tại đây. Ngoài ra, ta dùng lệnh TagMode.StartTagAndEndTag
để đóng tag div
lại.
var hello = new TagBuilder("span");
hello.InnerHtml.AppendHtml("Hello,");
output.Content.AppendHtml(hello);
Đầu tiên, ta tạo một span
để chứa dòng chữ Hello,
. Rồi ta thêm span
đó vào output
.
var world = new TagBuilder("a");
world.InnerHtml.AppendHtml("World!");
world.Attributes.Add("href", "https://example.com");
output.Content.AppendHtml(world);
Sau đó, ta thêm một tag a
để chứa dòng chữ World!
. Đường link này sẽ trỏ tới https://example.com
.
Sử dụng HelloWorldTagHelper trong trang web
Ta cần bổ sung đoạn code dưới đây vào Pages/_ViewImports.cshtml
để bật tính năng Tag Helpers.
@addTagHelper *, <project name>
// Trong project ví dụ, ta dùng code dưới đây.
// @addTagHelper *, TagHelpersDemo
Sau đó, khi ta thêm code <hello-world></hello-world>
vào trang, compiler sẽ tự động chuyển nó thành giao diện dưới đây.
Tag này chưa có gì ấn tượng, nhưng khởi đầu như vậy là không tồi.
Thêm property cho custom tag
Ta sẽ dùng custom tag này trong phần còn lại của bài. Dù trông có vẻ phức tạp nhưng nó không khác biệt nhiều so với tag HelloWorldTagHelper
ở trên.
Ta định nghĩa một tag mới gọi là exchange-rate-table
để hiển thị bảng tỷ giá giữa một số loại tiền. Ta cũng cho phép người dùng thêm thời gian hiện tại vào phần caption
. Người dùng có thể hiển thị thời gian bằng cách đặt include-time-stamp
thành true
. Ở trong lớp ExchangeRateTableTagHelper
, ta có thể đọc giá trị của attribute đó rồi hiển thị thời gian nếu cần.
Cách đơn giản nhất để đọc giá trị của include-time-stamp
là bằng sử dụng tham số TagHelperContext context
.
var flag = (bool)context.AllAttributes["include-time-stamp"].Value;
Nhưng tôi không khuyên dùng cách này. Bởi vì nếu vậy ta phải tự mình kiểm tra null, xác thực tên attribute,… Thay vào đó, ta nên tạo một public property trong lớp ExchangeRateTableTagHelper
và kết nối nó với include-time-stamp
bằng attribute HtmlAttributeName
.
[HtmlAttributeName("include-time-stamp")]
public bool IncludeTimeStamp { get; set; }
Bằng cách này, ta có thể sử dụng IncludeTimeStamp
mỗi khi cần đọc giá trị của include-time-stamp
.
Dưới đây là kết quả khi đặt include-time-stamp
là true
.
<exchange-rate-table include-time-stamp="true"></exchange-rate-table>
Còn dưới đây là kết quả khi đặt include-time-stamp
to false
.
Tag Helpers bên trong Tag Helpers khác
Vì table ta tạo ở phần trước chỉ có phần header nên rõ ràng là nó không có mấy tác dụng. Nhưng có lẽ một số độc giả đã để ý tới hàm này.
var bodySection = new TagBuilder("tbody");
var childContent = await output.GetChildContentAsync();
bodySection.InnerHtml.AppendHtml(childContent);
output.Content.AppendHtml(bodySection);
Và đây là dòng đáng chú ý nhất var childContent = await output.GetChildContentAsync();
. Dòng này sẽ xuất ra nội dung của các tag con của ExchangeRateTableTagHelper
, sau đó ta có thể thêm chúng vào trong phần thân của table. Ta sẽ tạo thêm một tag nữa gọi là all-exchange-rates
. Tag này sẽ hiển thị tỷ giá từ tất cả loại tiền trong cơ sở dữ liệu sang một loại tiền chọn trước. Ta nhập loại tiền đó vào một attribute gọi là target
. Trong ví dụ dưới, ta hiển thị tỷ giá sang đồng yên Nhật.
<exchange-rate-table include-time-stamp="true">
<all-exchange-rates target="JPY"></all-exchange-rates>
</exchange-rate-table>
Lớp AllExchangeRateRowsTagHelper
Các bạn có thể tham khảo code hoàn chỉnh của AllExchangeRateRowsTagHelper
tại đây. Dưới đây là các phần quan trọng của lớp này.
[HtmlTargetElement("all-exchange-rates", ParentTag = "exchange-rate-table")]
Ta chỉ cho phép dùng tag all-exchange-rates
bên trong tag exchange-rate-table
. Điều này được quy định thông qua property ParentTag
.
[HtmlAttributeName("target")]
public string TargetText { get; set; }
Ta nhập mã của loại tiền muốn lấy tỷ giá thông qua attribute target
. Attribute này được kết nối với public property với tên gọi TargetText
.
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
Vì ta dùng hàm asynchronous để lấy tỷ giá hối đoái nên ta phải viết lại hàm ProcessAsync
thay vì hàm Process
.
var rateDtos = await _exchangeRateRepository.RetrieveAllAsync(target);
foreach (var rateDto in rateDtos)
{
var row = new TagBuilder("tr");
AddCellToRow(row, rateDto.SourceName);
AddCellToRow(row, rateDto.TargetName);
AddCellToRow(row, $"{rateDto.Rate:0.0000}");
output.Content.AppendHtml(row);
}
Ta lấy tỷ giá từ các loại tiền khác rồi dùng vòng lặp để thêm mỗi tỷ giá vào một dòng mới trong table.
Kết quả cuối cùng là như sau.
Dùng Tag Helpers để tạo attribute mới cho tag sẵn có
Tag Helpers còn có thể được dùng để tạo attribute mới cho các tag HTML sẵn có. Trong phần này, ta sẽ tạo 2 attribute mới với tên gọi source
và target
cho tag tr
. Nếu một tag tr
có 2 attribute này thì ta sẽ tự động lấy tỷ giá giữa 2 loại tiền đó và thêm vào table. Ví dụ dưới đây là cách ta hiển thị tỷ giá giữa yên Nhật và Việt Nam đồng.
<exchange-rate-table include-time-stamp="true">
<tr source="JPY" target="VND"></tr>
</exchange-rate-table>
Các bạn có thể tham khảo code hoàn chỉnh tại đây. Dưới đây là những phần quan trọng.
[HtmlTargetElement("tr", ParentTag = "exchange-rate-table")]
Hai attribute này chỉ có tác dụng nếu tag tr
của ta nằm trong một tag exchange-rate-table
.
[HtmlAttributeName("source")]
public string SourceText { get; set; }
[HtmlAttributeName("target")]
public string TargetText { get; set; }
Ở đây, ta có thể thấy là HtmlAttributeName
lại được dùng để kết nối attribute HTML với property trong lớp Tag Helpers.
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
Và vì ta lại dùng hàm asynchronous để lấy tỷ giá, ta lại phải viết lại hàm ProcessAsync
.
Kết quả cuối cùng là đúng như ta dự đoán.
Tất nhiên ta cũng có thể thêm nhiều tag tr
bên trong cùng một tag exchange-rate-table
. Lúc này table của ta sẽ có nhiều dòng.
Lấy thông tin từ HTTP context bằng Tag Helpers
Đôi khi, ta cần đọc thông tin từ HTTP context trong lúc hiển thị nội dung tag. Khi đó ta cần thêm IHttpContextAccessor
vào lớp Tag Helpers. Đây là một ví dụ.
private IHttpContextAccessor _httpContextAccessor;
public HttpContextTagHelper(IHttpContextAccessor httpContextAccessor) =>
_httpContextAccessor = httpContextAccessor;
Nhớ là để dùng được HTTP context thì ta phải thêm service đó vào file Startup.cs
.
services.AddHttpContextAccessor()
Thông qua IHttpContextAccessor
, ta có thể đọc thông tin về cả request lẫn response. Một số thông tin ta có thể đọc là header của request, giá trị query, tên miền, số cổng,… Trong phần dưới ta sẽ hiển thị tất cả các giá trị trong query của request.
foreach (var kvp in _httpContextAccessor.HttpContext.Request.Query)
{
var pair = new TagBuilder("span");
pair.InnerHtml.AppendHtml($"{kvp.Key}: {kvp.Value}");
output.Content.AppendHtml(pair);
output.Content.AppendHtml("<br/>");
}
Khi mở đường link /?foo=bar&bar=foo
các bạn sẽ thấy giao diện như dưới đây.
Kết thúc
Nếu khéo sử dụng Tag Helpers thì ta sẽ dễ dàng tái sử dụng code. Dù tôi không phải là dân chuyên front-end, nhưng mỗi khi cần thì Tag Helpers vẫn là một công cụ đáng giá đối với tôi.