Note: see the link below for the English version of this article.

https://duongnt.com/tag-helpers

Tag Helpers và custom tag trong ASP.NET Core

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.

HelloWorldTagHelper demo

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-stamptrue.

<exchange-rate-table include-time-stamp="true"></exchange-rate-table>

include-time-stamp="true"

Còn dưới đây là kết quả khi đặt include-time-stamp to false.

include-time-stamp="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.

AllExchangeRateRowsTagHelper demo

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 sourcetarget 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.

ExchangeRateRowTagHelper demo

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.

HttpContextTagHelper demo

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.

A software developer from Vietnam and is currently living in Japan.

Leave a Reply