Tạo Tag Helpers (Trình trợ giúp thẻ) trong ASP.NET Core
Bởi Rick Anderson
Xem hoặc tải xuống mã mẫu (cách tải xuống)
Bắt đầu với Tag Helpers
Hướng dẫn này cung cấp phần giới thiệu lập trình Tag Helpers. Giới thiệu về Tag Helpers mô tả các lợi ích mà Tag Helpers cung cấp.
Một tag helper là bất kỳ lớp nào implement (cài đặt) interface ITagHelper. Tuy nhiên, khi bạn tạo một tag helper, bạn thường kế thừa từ TagHelper, làm như vậy cho phép bạn truy cập vào method Process.
- Tạo một dự án ASP.NET Core mới gọi là AuthoringTagHelpers. Bạn sẽ không cần xác thực cho dự án này.
- Tạo một thư mục để chứa Tag Helpers gọi là TagHelpers. Thư mục TagHelpers không bắt buộc, nhưng đây là một quy ước hợp lý.
Một Tag Helper tối giản
Trong phần này, bạn viết một tag helper cập nhật thẻ email. Ví dụ:
<email>Support</email>
Server sẽ sử dụng email tag helper để chuyển đổi markup đó thành:
<a href="mailto:Support@contoso.com">Support@contoso.com</a>
Đó là, một thẻ anchor (neo) làm cho email này thành một email link. Bạn có thể muốn làm điều này nếu bạn đang viết một blog engine và cần nó gửi email cho marketing, hỗ trợ, và các liên hệ khác, tất cả đến cùng một domain.
- Thêm lớp
EmailTagHelpersau vào thư mục TagHelpers.
```csharp
using Microsoft.AspNetCore.Razor.TagHelpers; using System.Threading.Tasks;
namespace AuthoringTagHelpers.TagHelpers { public class EmailTagHelper : TagHelper { public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = "a"; // Replaces <email> with <a> tag } } } ```
- Tag helpers sử dụng quy ước đặt tên target các phần tử của tên lớp gốc (trừ phần TagHelper của tên lớp). Trong ví dụ này, tên gốc của EmailTagHelper là email, vì vậy thẻ
<email>sẽ được target. - Lớp
EmailTagHelperkế thừa từTagHelper. LớpTagHelpercung cấp các methods và properties để viết Tag Helpers. - Method
Processđược override kiểm soát những gì tag helper thực hiện khi được thực thi. LớpTagHelpercũng cung cấp phiên bản bất đồng bộ (ProcessAsync) với cùng các tham số. - Tham số context cho
Process(vàProcessAsync) chứa thông tin liên kết với việc thực thi thẻ HTML hiện tại. - Tham số output cho
Process(vàProcessAsync) chứa một phần tử HTML stateful (có trạng thái) đại diện cho nguồn gốc được sử dụng để tạo thẻ HTML và nội dung.
- Để làm cho lớp
EmailTagHelpercó sẵn cho tất cả Razor views, thêm directiveaddTagHelpervào fileViews/_ViewImports.cshtml:
``cshtml @using AuthoringTagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, AuthoringTagHelpers ``
- Cập nhật markup trong file
Views/Home/Contact.cshtmlvới các thay đổi sau:
```cshtml @{ ViewData["Title"] = "Contact"; } <h2>@ViewData["Title"].</h2> <h3>@ViewData["Message"]</h3>
<address> One Microsoft Way<br /> Redmond, WA 98052<br /> <abbr title="Phone">P:</abbr> 425.555.0100 </address>
<address> <strong>Support:</strong><email>Support</email><br /> <strong>Marketing:</strong><email>Marketing</email> </address> ```
- Chạy ứng dụng và sử dụng trình duyệt để xem source HTML để xác minh rằng các thẻ email được thay thế bằng anchor markup (Ví dụ:
<a>Support</a>).
SetAttribute và SetContent
Trong phần này, chúng ta sẽ cập nhật EmailTagHelper để nó tạo ra một thẻ anchor hợp lệ cho email. Chúng ta sẽ cập nhật nó để lấy thông tin từ Razor view (dưới dạng thuộc tính mail-to) và sử dụng nó trong việc tạo anchor.
Cập nhật lớp EmailTagHelper với đoạn code sau:
public class EmailTagHelper : TagHelper
{
private const string EmailDomain = "contoso.com";
// Can be passed via <email mail-to="..." />.
// PascalCase gets translated into kebab-case.
public string MailTo { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a"; // Replaces <email> with <a> tag
var address = MailTo + "@" + EmailDomain;
output.Attributes.SetAttribute("href", "mailto:" + address);
output.Content.SetContent(address);
}
}- Tên lớp và thuộc tính viết hoa kiểu Pascal (Pascal-cased) cho tag helpers được dịch sang kebab case (viết thường, ngăn cách bằng dấu gạch ngang). Do đó, để sử dụng thuộc tính
MailTo, bạn sẽ sử dụng<email mail-to="value"/>. - Dòng cuối cùng đặt nội dung hoàn chỉnh cho tag helper chức năng tối thiểu của chúng ta.
- Cập nhật markup trong file
Views/Home/Contact.cshtmlvới các thay đổi sau:
```cshtml @{ ViewData["Title"] = "Contact Copy"; } <h2>@ViewData["Title"].</h2> <h3>@ViewData["Message"]</h3>
<address> One Microsoft Way Copy Version <br /> Redmond, WA 98052-6399<br /> <abbr title="Phone">P:</abbr> 425.555.0100 </address>
<address> <strong>Support:</strong><email mail-to="Support"></email><br /> <strong>Marketing:</strong><email mail-to="Marketing"></email> </address> ```
- Chạy ứng dụng và xác minh rằng nó tạo ra các links chính xác.
Nếu bạn viết email tag self-closing (<email mail-to="Rick" />), output cuối cùng cũng sẽ là self-closing. Để kích hoạt khả năng viết thẻ chỉ với thẻ bắt đầu (<email mail-to="Rick">), bạn phải đánh dấu lớp với:
``csharp
[HtmlTargetElement("email", TagStructure = TagStructure.WithoutEndTag)]
public class EmailVoidTagHelper : TagHelper
``
Bạn cũng có thể ánh xạ một tên thuộc tính khác vào một property bằng cách sử dụng thuộc tính [HtmlAttributeName]:
[HtmlAttributeName("recipient")]
public string? MailTo { get; set; }Tag Helper cho thuộc tính recipient:
<email recipient="…"/>
ProcessAsync
Trong phần này, chúng ta sẽ viết một email helper bất đồng bộ.
- Thay thế lớp
EmailTagHelperbằng code sau:
``csharp public class EmailTagHelper : TagHelper { private const string EmailDomain = "contoso.com"; public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { output.TagName = "a"; // Replaces <email> with <a> tag var content = await output.GetChildContentAsync(); var target = content.GetContent() + "@" + EmailDomain; output.Attributes.SetAttribute("href", "mailto:" + target); output.Content.SetContent(target); } } ``
Lưu ý:
- Phiên bản này sử dụng method bất đồng bộ
ProcessAsync.GetChildContentAsyncbất đồng bộ trả về mộtTaskchứaTagHelperContent. - Sử dụng tham số
outputđể lấy nội dung của phần tử HTML.
RemoveAll, PreContent.SetHtmlContent và PostContent.SetHtmlContent
- Thêm lớp
BoldTagHelpersau vào thư mục TagHelpers:
```csharp using Microsoft.AspNetCore.Razor.TagHelpers;
namespace AuthoringTagHelpers.TagHelpers { [HtmlTargetElement(Attributes = "bold")] public class BoldTagHelper : TagHelper { public override void Process(TagHelperContext context, TagHelperOutput output) { output.Attributes.RemoveAll("bold"); output.PreContent.SetHtmlContent("<strong>"); output.PostContent.SetHtmlContent("</strong>"); } } } ```
- Thuộc tính
[HtmlTargetElement]truyền một tham số thuộc tính chỉ định rằng bất kỳ phần tử HTML nào chứa thuộc tính HTML có tên "bold" sẽ khớp. - Vì bạn không muốn thay thế nội dung thẻ hiện có, bạn phải viết thẻ
<strong>mở bằng methodPreContent.SetHtmlContentvà thẻ</strong>đóng bằng methodPostContent.SetHtmlContent.
Truyền một model vào Tag Helper
- Thêm lớp
WebsiteInformationTagHelpervào thư mục TagHelpers:
```csharp using System; using AuthoringTagHelpers.Models; using Microsoft.AspNetCore.Razor.TagHelpers;
namespace AuthoringTagHelpers.TagHelpers { public class WebsiteInformationTagHelper : TagHelper { public WebsiteContext Info { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = "section"; output.Content.SetHtmlContent( $@"<ul><li><strong>Version:</strong> {Info.Version}</li> <li><strong>Copyright Year:</strong> {Info.CopyrightYear}</li> <li><strong>Approved:</strong> {Info.Approved}</li> <li><strong>Number of tags to show:</strong> {Info.TagsToShow}</li></ul>"); output.TagMode = TagMode.StartTagAndEndTag; } } } ```
- Như đã đề cập trước đó, tag helpers dịch tên lớp và property viết hoa kiểu Pascal (Pascal-cased) C# cho tag helpers sang kebab case. Do đó, để sử dụng
WebsiteInformationTagHelpertrong Razor, bạn sẽ viết<website-information />. - Các phần tử tự đóng không có nội dung. Cho ví dụ này, Razor markup sẽ sử dụng thẻ tự đóng, nhưng tag helper sẽ tạo ra một phần tử section (không tự đóng và bạn đang viết nội dung bên trong phần tử
section). Do đó, bạn cần đặtTagModethànhStartTagAndEndTagđể viết output.
Condition Tag Helper (Tag Helper điều kiện)
Condition tag helper render output khi được truyền giá trị true.
- Thêm lớp
ConditionTagHelpersau vào thư mục TagHelpers:
```csharp using Microsoft.AspNetCore.Razor.TagHelpers;
namespace AuthoringTagHelpers.TagHelpers { [HtmlTargetElement(Attributes = nameof(Condition))] public class ConditionTagHelper : TagHelper { public bool Condition { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output) { if (!Condition) { output.SuppressOutput(); } } } } ```
- Thay thế nội dung của file
Views/Home/Index.cshtmlbằng markup sau:
```cshtml @using AuthoringTagHelpers.Models @model WebsiteContext
@{ ViewData["Title"] = "Home Page"; }
<div> <h3>Information about our website (outdated):</h3> <Website-InforMation info="Model" /> <div condition="Model.Approved"> <p> This website has <strong surround="em">@Model.Approved</strong> been approved yet. Visit www.contoso.com for more information. </p> </div> </div> ```
- Thay thế method
Indextrong controllerHomebằng code sau:
``csharp public IActionResult Index(bool approved = false) { return View(new WebsiteContext { Approved = approved, CopyrightYear = 2015, Version = new Version(1, 3, 3, 7), TagsToShow = 20 }); } ``
- Chạy ứng dụng và điều hướng đến trang home. Markup trong
divđiều kiện sẽ không được render. Thêm query string?approved=truevào URL (ví dụ:http://localhost:1235/Home/Index?approved=true).approvedđược đặt thành true và markup điều kiện sẽ được hiển thị.
Sử dụng toán tử nameof để chỉ định thuộc tính cần target thay vì chỉ định một chuỗi như bạn đã làm với bold tag helper:
``csharp
[HtmlTargetElement(Attributes = nameof(Condition))]
``
Toán tử nameof sẽ bảo vệ code nếu nó được refactor (tái cấu trúc) sau này.
Tránh xung đột Tag Helper
Trong phần này, bạn viết một cặp auto-linking tag helpers. Cái đầu tiên sẽ thay thế markup chứa URL bắt đầu bằng HTTP thành thẻ anchor HTML chứa cùng URL. Cái thứ hai sẽ làm tương tự cho URL bắt đầu bằng WWW.
- Thêm lớp
AutoLinkerHttpTagHelpersau vào thư mục TagHelpers:
``csharp [HtmlTargetElement("p")] public class AutoLinkerHttpTagHelper : TagHelper { public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { var childContent = await output.GetChildContentAsync(); // Find Urls in the content and replace them with their anchor tag equivalent. output.Content.SetHtmlContent(Regex.Replace( childContent.GetContent(), @"\b(?:https?://)(\S+)\b", "<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version} } } ``
- Thêm lớp
AutoLinkerWwwTagHelperđể chuyển đổi text www thành thẻ anchor:
```csharp [HtmlTargetElement("p")] public class AutoLinkerWwwTagHelper : TagHelper { public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { var childContent = output.Content.IsModified ? output.Content.GetContent() : (await output.GetChildContentAsync()).GetContent();
// Find Urls in the content and replace them with their anchor tag equivalent. output.Content.SetHtmlContent(Regex.Replace( childContent, @"\b(www\.)(\S+)\b", "<a target=\"_blank\" href=\"http://$0\">$0</a>")); // www version } } ```
- Để kiểm soát thứ tự thực thi tag helper, sử dụng thuộc tính
Order. Thuộc tínhOrderxác định thứ tự thực thi liên quan đến các tag helper khác targeting cùng phần tử. Giá trị order mặc định là zero và các instance có giá trị thấp hơn được thực thi trước.
``csharp public class AutoLinkerHttpTagHelper : TagHelper { // This filter must run before the AutoLinkerWwwTagHelper as it searches and replaces http and // the AutoLinkerWwwTagHelper adds http to the markup. public override int Order { get { return int.MinValue; } } ``
Kiểm tra và lấy nội dung con
Tag helpers cung cấp một số properties để lấy nội dung.
- Kết quả của
GetChildContentAsynccó thể được gắn thêm vàooutput.Content. - Bạn có thể kiểm tra kết quả của
GetChildContentAsyncvớiGetContent. - Nếu bạn sửa đổi
output.Content, TagHelper body sẽ không được thực thi hoặc render trừ khi bạn gọiGetChildContentAsync:
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = output.Content.IsModified ? output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}- Nhiều lệnh gọi đến
GetChildContentAsynctrả về cùng một giá trị và không thực thi lạiTagHelperbody trừ khi bạn truyền vào tham số false chỉ định không sử dụng kết quả được cache.
Tải minified partial view TagHelper
Trong môi trường production, hiệu suất có thể được cải thiện bằng cách tải các minified partial views. Để tận dụng minified partial view trong production:
- Tạo/thiết lập một quy trình pre-build để minify (nén) partial views.
- Sử dụng code sau để tải minified partial views trong các môi trường không phải development:
public class MinifiedVersionPartialTagHelper : PartialTagHelper
{
public MinifiedVersionPartialTagHelper(ICompositeViewEngine viewEngine,
IViewBufferScope viewBufferScope)
: base(viewEngine, viewBufferScope)
{
}
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// Append ".min" to load the minified partial view.
if (!IsDevelopment())
{
Name += ".min";
}
return base.ProcessAsync(context, output);
}
private bool IsDevelopment()
{
return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
== EnvironmentName.Development;
}
}