---
title: View Components
description: Hướng dẫn chi tiết về View Components trong ASP.NET Core MVC — khái niệm, cách tạo, sử dụng và so sánh với Partial Views.
---

## View Component là gì?

**View Components** là một tính năng mới trong ASP.NET Core MVC, tương tự như **partial views** nhưng mạnh hơn nhiều. Chúng đặc biệt hữu ích cho những phần giao diện có logic phức tạp, như:

- Navigation menus
- Shopping cart sidebar
- Login panel
- Recent posts widget
- Admin dashboards

```mermaid
flowchart TD
    A["🖼️ View"] --> B["@await Component.InvokeAsync"]
    B --> C["📦 View Component Class"]
    C --> D["🔧 InvokeAsync Method"]
    D --> E["📊 Model/Service Layer"]
    C --> F["📄 View (.cshtml)"]
    F --> A
```

> **Lưu ý:** View Components hoàn toàn tương thích với **Razor Pages** và có thể được sử dụng ở bất kỳ đâu bạn cần logic phức tạp kết hợp với rendering.

---

## View Component vs Partial View

| Tiêu chí                 | Partial View              | View Component                     |
| ------------------------ | ------------------------- | ---------------------------------- |
| **Logic**                | Embedded hoặc qua ViewBag | Tách riêng trong class             |
| **Rendering**            | Synchronous               | Synchronous hoặc Asynchronous      |
| **Model binding**        | Via weakly-typed ViewData | Strongly-typed parameters          |
| **Testability**          | Khó unit test             | Dễ unit test                       |
| **Controller required**  | Không                     | Không                              |
| **Dependency Injection** | Không (trừ @inject)       | Đầy đủ DI support                  |
| **Use case**             | Simple rendering          | Complex business logic + rendering |

---

## Tạo View Component

### Bước 1: Tạo View Component Class

Tạo class kế thừa từ `ViewComponent`:

```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentsSample.Models;

namespace ViewComponentsSample.Views.Components
{
    // View Component class
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        // InvokeAsync - main entry point
        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();
        }
    }
}
```

### Bước 2: Tạo Razor View

Tạo view tại đường dẫn:

```text
/Views/{ControllerName}/Components/{ViewComponentName}/{ViewName}.cshtml
/Views/Shared/Components/{ViewComponentName}/{ViewName}.cshtml
```

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

<h3>Priority Items</h3>
<ul>
    @foreach (var item in Model)
    {
        <li>
            @item.Name
            @if (item.IsDone)
            {
                <span>✓</span>
            }
        </li>
    }
</ul>
```

---

## Quy ước đặt tên

### Class Name

| Cách đặt               | Ví dụ                           |
| ---------------------- | ------------------------------- |
| Suffix `ViewComponent` | `PriorityListViewComponent`     |
| Hoặc attribute         | `@[ViewComponent] PriorityList` |

```csharp
// Cách 1: Suffix
public class PriorityListViewComponent : ViewComponent { }

// Cách 2: Attribute
[ViewComponent]
public class PriorityList
{
    public Task<IViewComponentResult> InvokeAsync() { /* ... */ }
}
```

### View Name

Theo mặc định, View Component tìm view tên `Default`:

```text
/Views/Shared/Components/PriorityList/Default.cshtml
```

---

## Gọi View Component

### Từ View

#### Tag Helper (Khuyến nghị)

```cshtml
@using ViewComponentsSample.Views.Components

@* Gọi không có tham số *@
@Component.Invoke("PriorityList")

@* Gọi với tham số *@
@await Component.InvokeAsync("PriorityList",
    new {
        maxPriority = 4,
        isDone = false
    })
```

#### Tag Helper (Razor Class Library)

```cshtml
@addTagHelper *, ViewComponentsSample

@* Tag Helper syntax *@
<vc:priority-list max-priority="4" is-done="false">
</vc:priority-list>
```

### Từ Controller

```csharp
public class HomeController : Controller
{
    public async Task<IActionResult> Index()
    {
        // Render View Component và trả về như partial view
        var result = await Component.InvokeAsync("PriorityList",
            new { maxPriority = 3, isDone = false });

        return View(result);
    }
}
```

### Từ Razor Page

```cshtml
@page
@model IndexModel
@await Component.InvokeAsync("PriorityList",
    new { maxPriority = 4, isDone = false })
```

---

## View Component với Strongly-Typed Data

### Định nghĩa Model

```csharp
public class TodoItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsDone { get; set; }
    public int Priority { get; set; }
}
```

### View Component Class

```csharp
public class TodoListViewComponent : ViewComponent
{
    private readonly ToDoContext _context;

    public TodoListViewComponent(ToDoContext context)
    {
        _context = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(bool showAll)
    {
        var items = showAll
            ? await _context.ToDo.ToListAsync()
            : await _context.ToDo.Where(x => !x.IsDone).ToListAsync();

        return View(items);
    }
}
```

### Razor View với Strong Type

```cshtml
@model IEnumerable<TodoItem>
@* hoặc @model MyNamespace.TodoItemViewModel *@

<h2>Todo List</h2>
<table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Priority</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.Name</td>
                <td>@item.Priority</td>
                <td>@(item.IsDone ? "✓" : "○")</td>
            </tr>
        }
    </tbody>
</table>
```

---

## View Component Synchronous

Nếu không cần async, có thể override phương thức `Invoke`:

```csharp
public class FeaturedBooksViewComponent : ViewComponent
{
    private readonly IBookService _bookService;

    public FeaturedBooksViewComponent(IBookService bookService)
    {
        _bookService = bookService;
    }

    // Synchronous Invoke
    public IViewComponentResult Invoke(int count)
    {
        var books = _bookService.GetFeaturedBooks(count);
        return View(books);
    }
}
```

---

## Các phương thức View Component

### View Method

```csharp
// Trả về view mặc định
return View(books);

// Trả về view cụ thể
return View("SpecialView", books);

// Trả về view với model tùy chỉnh
return View("~/Views/Shared/Components/FeaturedBooks/VIP.cshtml", books);
```

### Content Method

```csharp
// Trả về raw HTML content
return Content("<div class='alert'>Hello World</div>");
```

### Other Methods

| Method                  | Mô tả                                  |
| ----------------------- | -------------------------------------- |
| `View(model)`           | Trả về view với model                  |
| `View(viewName, model)` | Trả về view cụ thể với model           |
| `Content(content)`      | Trả về HTML content string             |
| `EmptyResult()`         | Trả về empty response (204 No Content) |

---

## View Component Context

### ViewContext

Truy cập `HttpContext`, `RouteData`, và các thông tin request:

```csharp
public class UserPanelViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        var userName = User.Identity?.Name;
        var isAdmin = User.IsInRole("Admin");
        var currentUrl = ViewContext.HttpContext.Request.Path;

        var model = new UserPanelViewModel
        {
            UserName = userName,
            IsAdmin = isAdmin,
            CurrentUrl = currentUrl
        };

        return View(model);
    }
}
```

### ViewBag trong View Component

```csharp
public class NavigationViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        ViewBag.CurrentPage = ViewContext.RouteData.Values["action"];
        ViewBag.CurrentController = ViewContext.RouteData.Values["controller"];

        return View();
    }
}
```

---

## View Component trong Razor Class Library

### Cấu trúc thư mục

```text
/Components
  /PriorityList
      Default.cshtml
  /ShoppingCart
      Default.cshtml
```

### Đăng ký trong Program.cs

```csharp
builder.Services.AddViewComponents();
```

### Sử dụng

```cshtml
@addTagHelper *, MyLibrary.Components

<vc:priority-list max-priority="3"></vc:priority-list>
<vc:shopping-cart></vc:shopping-cart>
```

---

## Dependency Injection trong View Component

### Constructor Injection

```csharp
public class WeatherViewComponent : ViewComponent
{
    private readonly IWeatherService _weatherService;

    // DI qua constructor
    public WeatherViewComponent(IWeatherService weatherService)
    {
        _weatherService = weatherService;
    }

    public async Task<IViewComponentResult> InvokeAsync(string location)
    {
        var forecast = await _weatherService.GetForecastAsync(location);
        return View(forecast);
    }
}
```

### Đăng ký Service

```csharp
builder.Services.AddScoped<IWeatherService, WeatherServiceImpl>();
```

---

## Ví dụ: Shopping Cart View Component

### ShoppingCartViewComponent.cs

```csharp
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace WebApp.ViewComponents
{
    public class ShoppingCartViewComponent : ViewComponent
    {
        private readonly ICartService _cartService;

        public ShoppingCartViewComponent(ICartService cartService)
        {
            _cartService = cartService;
        }

        public IViewComponentResult Invoke()
        {
            var cart = _cartService.GetCart(HttpContext.User.Identity?.Name);

            var viewModel = new CartViewModel
            {
                Items = cart.Items,
                TotalItems = cart.Items.Sum(x => x.Quantity),
                TotalPrice = cart.Items.Sum(x => x.Price * x.Quantity)
            };

            return View(viewModel);
        }
    }

    public class CartViewModel
    {
        public List<CartItem> Items { get; set; }
        public int TotalItems { get; set; }
        public decimal TotalPrice { get; set; }
    }
}
```

### Default.cshtml

```cshtml
@model CartViewModel

<div class="cart-panel">
    <h4>
        <span class="oi oi-cart"></span>
        Giỏ hàng (@Model.TotalItems)
    </h4>

    @if (!Model.Items.Any())
    {
        <p class="text-muted">Giỏ hàng trống</p>
    }
    else
    {
        <ul class="list-unstyled">
            @foreach (var item in Model.Items)
            {
                <li>
                    @item.ProductName × @item.Quantity
                    <span class="badge">@item.Price ₫</span>
                </li>
            }
        </ul>

        <hr />
        <strong>Tổng: @Model.TotalPrice ₫</strong>
    }
</div>
```

### Sử dụng trong Layout

```cshtml
<!-- _Layout.cshtml -->
<header>
    <nav>...</nav>

    <div class="cart-summary">
        @await Component.InvokeAsync("ShoppingCart")
    </div>
</header>
```

---

## So sánh: Các cách gọi View Component

```mermaid
flowchart TD
    A["View Component"] --> B["Từ View"]
    A --> C["Từ Controller"]
    A --> D["Từ Razor Page"]
    A --> E["Tag Helper"]

    B --> B1["@await Component.InvokeAsync"]
    C --> C1["return View(component)"]
    D --> D1["@await Component.InvokeAsync"]
    E --> E1["<vc:component-name>"]
```

---

## Tài liệu tham khảo

- [View Components in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components)
- [Tag Helpers in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)
- [View Component API Reference](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.viewcomponents)
