C# - C Sharp: View component
View components
View component tương tự như partial view, nhưng chúng mạnh hơn nhiều. View component không sử dụng liên kết mô hình, chúng phụ thuộc vào dữ liệu được truyền khi gọi View component. Bài viết này được viết bằng cách sử dụng controller và view, nhưng View component hoạt động với Razor Pages.
Một View component:
- Hiển thị một đoạn thay vì toàn bộ phản hồi.
- Bao gồm các lợi ích tách biệt mối quan tâm và khả năng kiểm tra giống nhau được tìm thấy giữa bộ điều khiển và chế độ xem.
- Có thể có các tham số và logic nghiệp vụ.
- Thường được gọi từ một trang bố trí.
View component được dành cho bất kỳ nơi nào logic kết xuất có thể tái sử dụng quá phức tạp đối với partial view, chẳng hạn như:
- Menu điều hướng động
- Tag cloud, nơi nó truy vấn cơ sở dữ liệu
- Bảng đăng nhập
- giỏ hàng
- Bài báo được xuất bản gần đây
- Nội dung thanh bên trên blog
- Bảng đăng nhập sẽ được hiển thị trên mỗi trang và hiển thị các liên kết để đăng xuất hoặc đăng nhập, tùy thuộc vào trạng thái đăng nhập của người dùng
Một View component bao gồm hai phần:
- Lớp, thường bắt nguồn từ ViewComponent
- Kết quả mà nó trả về, thường là một dạng view.
Giống như controller, một View component có thể là một POCO, nhưng hầu hết các nhà phát triển đều tận dụng các phương thức và thuộc tính có sẵn bằng cách dẫn xuất từ ViewComponent.
Khi cân nhắc xem View component có đáp ứng các thông số kỹ thuật của ứng dụng hay không, hãy cân nhắc sử dụng Razor component để thay thế. Razor component cũng kết hợp đánh dấu với code C# để tạo ra các đơn vị giao diện người dùng có thể tái sử dụng. Razor component được thiết kế cho năng suất của nhà phát triển khi cung cấp logic và thành phần giao diện người dùng phía máy khách. Để biết thêm thông tin, hãy xem ASP.NET Core Razor component. Để biết thông tin về cách kết hợp Razor component vào ứng dụng MVC hoặc Razor Pages, hãy xem Kết xuất trước và tích hợp ASP.NET Core Razor component.
Tạo một View component
Phần này chứa các yêu cầu cấp cao để tạo View component. Ở phần sau của bài viết, chúng ta sẽ xem xét chi tiết từng bước và tạo View component.
Lớp View component
Một lớp View component có thể được tạo bởi bất kỳ cách nào sau đây:
- Xuất phát từ ViewComponent
- Trang trí một lớp với attribute [ViewComponent] hoặc xuất phát từ một lớp với attribute [ViewComponent]
- Tạo một lớp có tên kết thúc bằng hậu tố ViewComponent
Giống như controller, View component phải là các lớp public, không lồng nhau và không trừu tượng. Tên View component là tên lớp đã loại bỏ hậu tố ViewComponent. Nó cũng có thể được chỉ định rõ ràng bằng cách sử dụng property Name.
Một lớp View component:
- Hỗ trợ hàm tạo dependency injection
- Không tham gia vào vòng đời của controller, do đó không thể sử dụng các bộ lọc trong View component
Để ngăn một lớp có hậu tố phân biệt chữ hoa chữ thường ViewComponent được coi là một View component, hãy trang trí lớp bằng attribute [NonViewComponent]:
using Microsoft.AspNetCore.Mvc; [NonViewComponent] public class ReviewComponent { public string Status(string name) => JobStatus.GetCurrentStatus(name); }
Các phương thức View component
Một View component xác định logic của nó trong:
- Phương thức
InvokeAsync
trả vềTask<IViewComponentResult>
. - Phương thức đồng bộ
Invoke
trả về IViewComponentResult.
Các tham số đến trực tiếp từ việc gọi View component, không phải từ liên kết mô hình. Một View component không bao giờ xử lý trực tiếp một yêu cầu. Thông thường, một View component khởi tạo một mô hình và chuyển nó tới một View bằng cách gọi phương thức View
. Tóm lại, các phương thức View component:
- Xác định một phương thức
InvokeAsync
trả về mộtTask<IViewComponentResult>
hoặc một phương thứcInvoke
đồng bộ trả về mộtIViewComponentResult
. - Thông thường khởi tạo một model và truyền nó tới một view bằng cách gọi phương thức ViewComponent.View.
- Các tham số đến từ phương thức gọi, không phải HTTP. Không có model binding.
- Không thể truy cập trực tiếp dưới dạng điểm cuối HTTP. Chúng thường được gọi trong một view. Một View component không bao giờ xử lý một yêu cầu.
- Bị quá tải trên signature thay vì bất kỳ chi tiết nào từ yêu cầu HTTP hiện tại.
Đường dẫn tìm kiếm View
Bộ thực thi tìm kiếm View trong các đường dẫn sau:
- /Views/{Controller Name}/Components/{View Component Name}/{View Name}
- /Views/Shared/Components/{View Component Name}/{View Name}
- /Pages/Shared/Components/{View Component Name}/{View Name}
- /Areas/{Area Name}/Views/Shared/Components/{View Component Name}/{View Name}
Đường dẫn tìm kiếm áp dụng cho các dự án sử dụng controller + view và Razor Page.
Tên view mặc định cho View component là Default
, có nghĩa là file View thường sẽ được đặt tên Default.cshtml
. Một tên view khác có thể được chỉ định khi tạo kết quả View component hoặc khi gọi phương thức View
.
Bạn nên đặt tên cho file View Default.cshtml
và sử dụng đường dẫn Views/Shared/Components/{View Component Name}/{View Name}. View component PriorityList
được sử dụng trong mẫu này sử dụng Views/Shared/Components/PriorityList/Default.cshtml
cho view View component.
Tùy chỉnh đường dẫn tìm kiếm View
Để tùy chỉnh đường dẫn tìm kiếm view, hãy sửa đổi collection ViewLocationFormats của Razor. Ví dụ: để tìm kiếm các view trong đường dẫn /Components/{View Component Name}/{View Name}
, hãy thêm một mục mới vào collection:
using Microsoft.EntityFrameworkCore; using ViewComponentSample.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews() .AddRazorOptions(options => { options.ViewLocationFormats.Add("/{0}.cshtml"); }); builder.Services.AddDbContext<ToDoContext>(options => options.UseInMemoryDatabase("db")); var app = builder.Build(); // Remaining code removed for brevity.
Trong đoạn code trên, trình giữ chỗ {0}
đại diện cho đường dẫn Components/{View Component Name}/{View Name}
.
Gọi một View component
Để sử dụng View component, hãy gọi phần sau bên trong view:
@await Component.InvokeAsync("Name of view component",
{Anonymous Type Containing Parameters})
Các tham số được truyền cho phương thức InvokeAsync
. View component PriorityList
được phát triển trong bài viết được gọi từ file view Views/ToDo/Index.cshtm
. Trong đoạn mã sau, phương thức InvokeAsync
được gọi với hai tham số:
</table> <div> Maxium Priority: @ViewData["maxPriority"] <br /> Is Complete: @ViewData["isDone"] @await Component.InvokeAsync("PriorityList", new { maxPriority = ViewData["maxPriority"], isDone = ViewData["isDone"] } ) </div>
Gọi một View component dưới dạng Tag Helper
Có thể gọi View component dưới dạng Tag Helper:
<div> Maxium Priority: @ViewData["maxPriority"] <br /> Is Complete: @ViewData["isDone"] @{ int maxPriority = Convert.ToInt32(ViewData["maxPriority"]); bool isDone = Convert.ToBoolean(ViewData["isDone"]); } <vc:priority-list max-priority=maxPriority is-done=isDone> </vc:priority-list> </div>
Các tham số phương thức và lớp có vỏ bọc Pascal cho Tag Helper được dịch sang dạng kebab của chúng. Tag Helper để gọi một View component sử dụng phần tử <vc></vc>
. View component được chỉ định như sau:
<vc:[view-component-name] parameter1="parameter1 value" parameter2="parameter2 value"> </vc:[view-component-name]>
Để sử dụng View component làm Tag Helper, hãy đăng ký hợp ngữ chứa View component bằng lệnh @addTagHelper
. Nếu View component nằm trong một tổ hợp có tên MyWebApp
, hãy thêm lệnh sau vào file _ViewImports.cshtml
:
@addTagHelper *, MyWebApp
View component có thể được đăng ký làm Tag Helper cho bất kỳ tệp nào tham chiếu đến View component. Xem Quản lý phạm vi Tag Helper để biết thêm thông tin về cách đăng ký Tag Helper.
Phương thức InvokeAsync
được sử dụng trong hướng dẫn này:
@await Component.InvokeAsync("PriorityList", new { maxPriority = ViewData["maxPriority"], isDone = ViewData["isDone"] } )
Trong phần đánh dấu trước đó, View component PriorityList
trở thành priority-list
. Các tham số cho View component được truyền dưới dạng attribute trong trường hợp kebab.
Gọi một View component trực tiếp từ controller
Các View component thường được gọi từ một view, nhưng chúng có thể được gọi trực tiếp từ một phương thức của controller. Mặc dù View component không xác định các điểm cuối như controller, nhưng một action của controller trả về nội dung của ViewComponentResult
có thể được triển khai.
Trong ví dụ sau, View component được gọi trực tiếp từ controller:
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false) { return ViewComponent("PriorityList", new { maxPriority = maxPriority, isDone = isDone }); }
Tạo một View component cơ bản
Tải xuống, xây dựng và kiểm tra mã khởi động. Đây là một dự án cơ bản với controller ToDo
hiển thị danh sách các mục ToDo.
Cập nhật controller để vượt qua trạng thái ưu tiên và hoàn thành
Cập nhật phương thức Index
để sử dụng các tham số trạng thái ưu tiên và hoàn thành:
using Microsoft.AspNetCore.Mvc; using ViewComponentSample.Models; namespace ViewComponentSample.Controllers; public class ToDoController : Controller { private readonly ToDoContext _ToDoContext; public ToDoController(ToDoContext context) { _ToDoContext = context; _ToDoContext.Database.EnsureCreated(); } public IActionResult Index(int maxPriority = 2, bool isDone = false) { var model = _ToDoContext!.ToDo!.ToList(); ViewData["maxPriority"] = maxPriority; ViewData["isDone"] = isDone; return View(model); } }
Thêm một lớp ViewComponent
Thêm một lớp ViewComponent vào ViewComponents/PriorityListViewComponent.cs
:
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using ViewComponentSample.Models; namespace ViewComponentSample.ViewComponents; public class PriorityListViewComponent : ViewComponent { private readonly ToDoContext db; public PriorityListViewComponent(ToDoContext context) => db = context; public async Task<IViewComponentResult> InvokeAsync( int maxPriority, bool isDone) { var items = await GetItemsAsync(maxPriority, isDone); return View(items); } private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone) { return db!.ToDo!.Where(x => x.IsDone == isDone && x.Priority <= maxPriority).ToListAsync(); } }
Ghi chú về mã:
- Lớp View component có thể được chứa trong bất kỳ thư mục nào trong dự án.
- Bởi vì tên lớp PriorityListViewComponent kết thúc bằng hậu tố ViewComponent, nên bộ thực thi sẽ sử dụng chuỗi
PriorityList
khi tham chiếu thành phần lớp từ một view. - Attribute [ViewComponent] có thể thay đổi tên được sử dụng để tham chiếu View component. Ví dụ, lớp có thể được đặt tên là
XYZ
với attribute[ViewComponent]
như sau:
[ViewComponent(Name = "PriorityList")] public class XYZ : ViewComponent
- Attribute
[ViewComponent]
trong đoạn code trên báo cho bộ chọn View component sử dụng:- Tên
PriorityList
khi tìm kiếm view được liên kết với thành phần - Chuỗi "PriorityList" khi tham chiếu thành phần lớp từ một view.
- Tên
- Thành phần này sử dụng dependency injection để cung cấp ngữ cảnh dữ liệu.
InvokeAsync
hiển thị một phương thức có thể được gọi từ một view và nó có thể nhận một số đối số tùy ý.- Phương thức
InvokeAsync
trả về tập các mụcToDo
thỏa mãn các tham sốisDone
vàmaxPriority
.
Tạo View component Razor view
- Tạo thư mục Views/Shared/Components. Thư mục này phải được đặt tên là Components.
- Tạo thư mục Views/Shared/Components/PriorityList. Tên thư mục này phải khớp với tên của lớp View component hoặc tên của lớp trừ hậu tố. Nếu attribute
ViewComponen
được sử dụng, tên lớp sẽ cần khớp với chỉ định attribute. - Tạo Razor view
Views/Shared/Components/PriorityList/Default.cshtml
:
@model IEnumerable<ViewComponentSample.Models.TodoItem> <h3>Priority Items</h3> <ul> @foreach (var todo in Model) { <li>@todo.Name</li> } </ul>
- Razor view lấy một danh sách
TodoItem
và hiển thị chúng. Nếu phương thứcInvokeAsync
View component không truyền tên của view, thì Default được sử dụng cho tên view theo quy ước. Để ghi đè kiểu mặc định cho một controller cụ thể, hãy thêm view vào thư mục view dành riêng cho controller (ví dụ: Views/ToDo/Components/PriorityList/Default.cshtml). - Nếu View component dành riêng cho controller, nó có thể được thêm vào thư mục dành riêng cho controller. Ví dụ,
Views/ToDo/Components/PriorityList/Default.cshtml
là controller cụ thể. - Thêm một
div
chứa lời gọi thành phần danh sách ưu tiên vào cuối tệpViews/ToDo/index.cshtml
:
</table> <div> Maxium Priority: @ViewData["maxPriority"] <br /> Is Complete: @ViewData["isDone"] @await Component.InvokeAsync("PriorityList", new { maxPriority = ViewData["maxPriority"], isDone = ViewData["isDone"] } ) </div>
Đánh dấu @await Component.InvokeAsync
hiển thị cú pháp để gọi các View component. Đối số đầu tiên là tên của thành phần chúng ta muốn gọi hoặc gọi. Các tham số tiếp theo được truyền cho thành phần. InvokeAsync
có thể lấy một số đối số tùy ý.
Kiểm tra ứng dụng. Hình ảnh sau đây hiển thị danh sách ToDo và các mục ưu tiên:
View component có thể được gọi trực tiếp từ controller:
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false) { return ViewComponent("PriorityList", new { maxPriority = maxPriority, isDone = isDone }); }
Chỉ định tên View component
Một View component phức tạp có thể cần chỉ định view không mặc định trong một số điều kiện. Đoạn mã sau cho biết cách chỉ định view "PVC" từ phương thức InvokeAsync
. Cập nhật phương thức InvokeAsync
trong lớp PriorityListViewComponent
.
public async Task<IViewComponentResult> InvokeAsync( int maxPriority, bool isDone) { string MyView = "Default"; // Nếu yêu cầu tất cả các tác vụ đã hoàn thành, hãy kết xuất với view "PVC". if (maxPriority > 3 && isDone == true) { MyView = "PVC"; } var items = await GetItemsAsync(maxPriority, isDone); return View(MyView, items); }
Sao chép file Views/Shared/Components/PriorityList/Default.cshtml
view có tên Views/Shared/Components/PriorityList/PVC.cshtml
. Thêm tiêu đề để cho biết view PVC đang được sử dụng.
@model IEnumerable<ViewComponentSample.Models.TodoItem> <h2> PVC Named Priority Component View</h2> <h4>@ViewBag.PriorityMessage</h4> <ul> @foreach (var todo in Model) { <li>@todo.Name</li> } </ul>
Chạy ứng dụng và xác minh view PVC.
Nếu view PVC không được hiển thị, hãy xác minh View component có mức độ ưu tiên từ 4 trở lên được gọi.
Kiểm tra đường dẫn View
- Thay đổi tham số ưu tiên thành ba hoặc ít hơn để view ưu tiên không được return.
- Tạm thời đổi tên
Views/ToDo/Components/PriorityList/Default.cshtml
thành1Default.cshtml
. - Kiểm tra ứng dụng, xảy ra lỗi sau:
An unhandled exception occurred while processing the request. InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched: /Views/ToDo/Components/PriorityList/Default.cshtml /Views/Shared/Components/PriorityList/Default.cshtml
- Sao chép
Views/ToDo/Components/PriorityList/1Default.cshtml
vàoViews/Shared/Components/PriorityList/Default.cshtml
. - Thêm một số đánh dấu vào View component view Share ToDo để cho biết view là từ thư mục Shared.
- Kiểm tra chế view thành phần Shared.
Tránh các chuỗi mã hóa cứng
Để đảm bảo an toàn cho thời gian biên dịch, hãy thay thế tên View component được mã hóa cứng bằng tên lớp. Cập nhật file PriorityListViewComponent.cs để không sử dụng hậu tố "ViewComponent":
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using ViewComponentSample.Models; namespace ViewComponentSample.ViewComponents; public class PriorityList : ViewComponent { private readonly ToDoContext db; public PriorityList(ToDoContext context) { db = context; } public async Task<IViewComponentResult> InvokeAsync( int maxPriority, bool isDone) { var items = await GetItemsAsync(maxPriority, isDone); return View(items); } private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone) { return db!.ToDo!.Where(x => x.IsDone == isDone && x.Priority <= maxPriority).ToListAsync(); } }
File view:
</table> <div> Testing nameof(PriorityList) <br /> Maxium Priority: @ViewData["maxPriority"] <br /> Is Complete: @ViewData["isDone"] @await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = ViewData["maxPriority"], isDone = ViewData["isDone"] } ) </div>
Một phương thức tải chồng Component.InvokeAsync
có kiểu CLR sử dụng toán tử typeof
:
</table> <div> Testing typeof(PriorityList) <br /> Maxium Priority: @ViewData["maxPriority"] <br /> Is Complete: @ViewData["isDone"] @await Component.InvokeAsync(typeof(PriorityList), new { maxPriority = ViewData["maxPriority"], isDone = ViewData["isDone"] } ) </div>
Thực hiện công việc đồng bộ
Framework xử lý việc gọi một phương thức đồng bộ Invoke
nếu không yêu cầu công việc không đồng bộ. Phương thức sau đây tạo View component Invoke
đồng bộ:
using Microsoft.AspNetCore.Mvc; using ViewComponentSample.Models; namespace ViewComponentSample.ViewComponents { public class PriorityListSync : ViewComponent { private readonly ToDoContext db; public PriorityListSync(ToDoContext context) { db = context; } public IViewComponentResult Invoke(int maxPriority, bool isDone) { var x = db!.ToDo!.Where(x => x.IsDone == isDone && x.Priority <= maxPriority).ToList(); return View(x); } } }
File Razor của View component:
<div> Testing nameof(PriorityList) <br /> Maxium Priority: @ViewData["maxPriority"] <br /> Is Complete: @ViewData["isDone"] @await Component.InvokeAsync(nameof(PriorityListSync), new { maxPriority = ViewData["maxPriority"], isDone = ViewData["isDone"] } ) </div>
View component được gọi trong file Razor (ví dụ: Views/Home/Index.cshtml
) bằng cách sử dụng một trong các phương pháp sau:
Để sử dụng phương pháp IViewComponentHelper, hãy gọi Component.InvokeAsync
:
@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })
Để sử dụng Tag Helper, hãy đăng ký hợp ngữ chứa View component bằng lệnh @addTagHelper
(view component nằm trong một hợp ngữ có tên MyWebApp
):
@addTagHelper *, MyWebApp
Sử dụng View component Tag Helper trong file đánh dấu Razor:
<vc:priority-list max-priority="999" is-done="false"> </vc:priority-list>
Signature của phương thức PriorityList.Invoke
là đồng bộ, nhưng Razor tìm và gọi phương thức có Component.InvokeAsync
trong tệp đánh dấu.