C# - C Sharp: View component


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

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ột Task<IViewComponentResult> hoặc một phương thức Invoke đồng bộ trả về một IViewComponentResult.
  • 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.

Danh sách 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.
  • 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ục ToDo 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ức InvokeAsync 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ệp Views/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:

danh sách việc cần làm 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
        });
}

các mục ưu tiên từ hành động IndexVC

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.

Thành phần xem ưu tiên

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ành 1Default.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ào Views/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.

Đầu ra ToDo với chế độ xem Thành phần được chia sẻ

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.

» Tiếp: Bảng (Table) trong WPF
« Trước: Hộp thoại (Dialog) trong C#
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!