Nguon: Microsoft Learn · .NET 8.0

View components (thành phần view) trong ASP.NET Core

Nguồn: View components

Bởi Rick Anderson

View components

View components (thành phần view) tương tự như partial views (view một phần), nhưng mạnh mẽ hơn nhiều. View components không sử dụng model binding (ràng buộc mô hình). Chúng phụ thuộc vào dữ liệu được truyền vào khi gọi view component. Bài viết này được viết sử dụng controllers và views, nhưng view components cũng hoạt động với Razor Pages.

Một view component:

View components được thiết kế cho bất kỳ nơi nào có logic render có thể tái sử dụng quá phức tạp cho partial view, chẳng hạn như:

Một view component gồm hai phần:

Giống như controllers, view component có thể là POCO, nhưng hầu hết các developer 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 xem xét liệu view components có đáp ứng thông số kỹ thuật của ứng dụng không, hãy xem xét sử dụng Razor components thay thế. Razor components cũng kết hợp markup với mã C# để tạo ra các đơn vị UI có thể tái sử dụng. Razor components được thiết kế cho năng suất của developer khi cung cấp logic UI phía client và composition. Để biết thêm thông tin, xem ASP.NET Core Razor components.

Tạo 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 từng bước chi tiết và tạo một view component.

Lớp view component

Bất kỳ cách nào sau đây đều có thể tạo một lớp view component:

Giống như controllers, view components 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 với hậu tố ViewComponent bị xóa. Nó cũng có thể được chỉ định rõ ràng bằng thuộc tính Name.

Một lớp view component:

Để ngăn một lớp có hậu tố ViewComponent không phân biệt hoa thường bị coi là view component, hãy trang trí lớp đó với thuộc tính [NonViewComponent]:

csharp
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 định nghĩa logic của nó trong:

Tham số đến trực tiếp từ việc gọi view component, không phải từ model binding. View component không bao giờ xử lý trực tiếp một request. Thông thường, view component khởi tạo một model và truyền nó cho view bằng cách gọi phương thức View. Tóm lại, các phương thức view component:

Đường dẫn tìm kiếm view

Runtime tìm kiếm view theo các đường dẫn sau:

Đường dẫn tìm kiếm áp dụng cho các dự án sử dụng controllers + views và Razor Pages.

Tên view mặc định cho view component là Default, có nghĩa là các file view thường sẽ được đặt tên là Default.cshtml. Có thể chỉ định tên view khác khi tạo kết quả view component hoặc khi gọi phương thức View.

Chúng tôi khuyến nghị đặt tên file view là 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 dùng Views/Shared/Components/PriorityList/Default.cshtml cho view của 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 views trong đường dẫn /Components/{View Component Name}/{View Name}, hãy thêm một mục mới vào collection:

csharp
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();

// Phần còn lại được bỏ qua cho ngắn gọn.

Trong đoạn code trên, placeholder {0} đại diện cho đường dẫn Components/{View Component Name}/{View Name}.

Gọi view component

Để sử dụng view component, hãy gọi như sau bên trong view:

cshtml
@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 này được gọi từ file view Views/ToDo/Index.cshtml. Trong đoạn code sau, phương thức InvokeAsync được gọi với hai tham số:

cshtml
</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Gọi view component như Tag Helper

View Component có thể được gọi như một Tag Helper:

cshtml
<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ố lớp và phương thức dạng Pascal-case cho Tag Helpers được chuyển thành kebab case. Tag Helper để gọi view component sử dụng phần tử <vc></vc>. View component được chỉ định như sau:

cshtml
<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

Để sử dụng view component như Tag Helper, hãy đăng ký assembly chứa view component bằng chỉ thị @addTagHelper. Nếu view component nằm trong một assembly có tên MyWebApp, hãy thêm chỉ thị sau vào file _ViewImports.cshtml:

cshtml
@addTagHelper *, MyWebApp

View component có thể được đăng ký như Tag Helper cho bất kỳ file nào tham chiếu đến view component. Xem Managing Tag Helper Scope để biết thêm thông tin về cách đăng ký Tag Helpers.

Phương thức InvokeAsync được sử dụng trong hướng dẫn này:

cshtml
@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

Trong markup trên, view component PriorityList trở thành priority-list. Các tham số cho view component được truyền dưới dạng thuộc tính theo kiểu kebab case.

Gọi view component trực tiếp từ controller

View components thường được gọi từ view, nhưng chúng có thể được gọi trực tiếp từ phương thức controller. Mặc dù view components không định nghĩa endpoints như controllers, một controller action 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:

csharp
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

Tạo view component cơ bản

Tải xuống, build và kiểm tra code khởi đầu. Đâ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 để truyền priority và trạng thái hoàn thành

Cập nhật phương thức Index để sử dụng tham số priority và trạng thái hoàn thành:

csharp
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 lớp ViewComponent

Thêm lớp ViewComponent vào ViewComponents/PriorityListViewComponent.cs:

csharp
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ề code:

``csharp [ViewComponent(Name = "PriorityList")] public class XYZ : ViewComponent ``

Tạo Razor view cho view component

```cshtml @model IEnumerable<ViewComponentSample.Models.TodoItem>

<h3>Priority Items</h3> <ul> @foreach (var todo in Model) { <li>@todo.Name</li> } </ul> ```

Razor view nhận danh sách TodoItem và hiển thị chúng. Nếu phương thức InvokeAsync của view component không truyền tên view, Default được sử dụng cho tên view theo quy ước.

```cshtml </table>

<div> Maximum Priority: @ViewData["maxPriority"] <br /> Is Complete: @ViewData["isDone"] @await Component.InvokeAsync("PriorityList", new { maxPriority = ViewData["maxPriority"], isDone = ViewData["isDone"] } ) </div> ```

Markup @await Component.InvokeAsync cho thấy cú pháp để gọi view components. Tham số đầu tiên là tên component chúng ta muốn gọi. Các tham số tiếp theo được truyền cho component. InvokeAsync có thể nhận số lượng tham số tùy ý.

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 code sau cho thấy 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.

csharp
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, render 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 sang view có tên Views/Shared/Components/PriorityList/PVC.cshtml. Thêm tiêu đề để chỉ ra view PVC đang được sử dụng.

cshtml
@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>

Kiểm tra đường dẫn view

``txt 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 ``

Tránh hard-coded strings (chuỗi được code cứng)

Để đảm bảo an toàn tại thời điểm biên dịch, hãy thay thế tên view component được code cứng bằng tên lớp. Cập nhật file PriorityListViewComponent.cs để không sử dụng hậu tố "ViewComponent":

csharp
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:

cshtml
</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 overload của phương thức Component.InvokeAsync nhận kiểu CLR sử dụng toán tử typeof:

cshtml
</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 phương thức Invoke đồng bộ nếu không yêu cầu công việc bất đồng bộ. Phương thức sau tạo view component Invoke đồng bộ:

csharp
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:

cshtml
<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) sử dụng một trong các cách tiếp cận sau:

Để sử dụng cách tiếp cận IViewComponentHelper, hãy gọi Component.InvokeAsync:

cshtml
@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

Để sử dụng Tag Helper, hãy đăng ký assembly chứa View Component bằng chỉ thị @addTagHelper (view component nằm trong assembly có tên MyWebApp):

cshtml
@addTagHelper *, MyWebApp

Sử dụng Tag Helper của view component trong file Razor markup:

cshtml
<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

Chữ ký phương thức của PriorityList.Invoke là đồng bộ, nhưng Razor tìm và gọi phương thức với Component.InvokeAsync trong file markup.

Tài nguyên bổ sung